AWS Builder Center
Your MCP Server Is an Identity Boundary. Secure It Like One.

Your MCP Server Is an Identity Boundary. Secure It Like One.

Two real 2026 AWS incidents show MCP servers leaking credentials and bypassing their own policies. Here is the architect's playbook: map every MCP threat to the AWS control that contains it.

Cloud Architect
Most of the MCP content I read treats the Model Context Protocol server as a convenience. A bit of glue that lets an agent read files, call an API, or run a shell command. In production that framing is dangerous. An MCP server is not glue. It is a new network boundary that carries an identity, holds credentials, and takes instructions from text an attacker can write.
It is, in other words, exactly the kind of thing we have spent fifteen years learning to lock down on AWS. We just forgot to apply what we already know.
Two incidents from 2026 made that concrete, and both were closed by pointing at customer-side AWS controls. If you are putting agents into production, those two incidents are your threat model. Let me walk through them the way an architect would, then map each class of failure to the specific AWS control that contains it.

Two incidents that should change how you deploy agents

Incident one: a security control that failed open

In July 2026, AWS published security bulletin 2026-063-AWS for CVE-2026-16584. It affects the open-source awslabs.aws-api-mcp-server, the server that lets an AI assistant run AWS CLI commands against your account. The server has an optional, user-configured security policy that can deny or gate specific operations.
The flaw: the server loads that policy data at startup. If initialization fails, it keeps running with the per-request policy check skipped for the entire life of the process. Unless you had explicitly turned on fail-closed mode, operations the policy was meant to deny would execute with no enforcement at all.
Read that twice, because it is the whole lesson. The control did not throw an error and stop. It failed open. The affected range was versions >= 0.2.13 and < 1.3.47, fixed in 1.3.47. And the recommended workaround in AWS's own bulletin was telling: use least-privilege IAM credentials, "for example, a ReadOnlyAccess role," because "IAM remains the primary access control and is enforced regardless of this issue."
The application-layer policy failed. IAM held. That is not luck. It is the design principle.

Incident two: a vault that protected everything except the moment that mattered

In September 2026, Unit 42 published research on Amazon Bedrock AgentCore Harness and its Identity vault. AgentCore Identity does everything right on paper: credentials encrypted at rest, encrypted in transit, sealed behind KMS keys, gated by IAM. You never put a bearer token in a header. You reference it by ARN, and the runtime resolves it when needed.
The problem is "when needed." To use a credential, the runtime must turn that ARN into the real plaintext secret inside its own process memory. And AgentCore Harness ships two built-in tools enabled by default: shell and file_operations. The shell tool runs as root, in the same process space where credentials get resolved.
Unit 42 built a fictional support agent in a completely default configuration. Nothing misconfigured. Then they walked the chain end to end. A support ticket carried a hidden HTML comment telling the agent to fetch a recon script and pipe it to Python. Indirect prompt injection, not a direct ask. The shell tool ran it as root. The script read /proc/1/maps and /proc/1/mem, scanned the heap for a JWT and the downstream MCP server URL, and posted both to an attacker webhook. The researchers then replayed that token from a laptop, with no AWS credentials, listed the MCP tools, and pulled customer PII.
The stolen credential was not the end user's short-lived session token. It was the operator's service account, the long-lived identity the runtime uses to reach downstream services. As Unit 42 put it, "authorized to invoke is not authorized to reach." The caller could only ask the agent for help. The agent could reach everything the operator ever wired into it.
AWS closed the report as informative under the shared responsibility model, citing allowedTools scoping and egress filtering as customer-side controls. Again: the platform did its part at rest and in transit. Containing the blast radius in use was the customer's job.

The pattern underneath both incidents

Strip away the specifics and the same shape appears twice. An MCP server or agent runtime holds an identity with real downstream authority. Its behavior is steered by text, a CLI prompt or a support ticket, that can come from an untrusted source. And when a boundary inside the system failed, whether a fail-open policy or a readable heap, the only thing standing between "model was tricked" and "attacker has your data" was whatever AWS control the operator had put around it.
This is why the useful mental model is not "secure the code." It is Simon Willison's lethal trifecta: you get data exfiltration whenever an agent simultaneously has access to private data, exposure to untrusted content, and a way to communicate externally. MCP makes assembling all three trivial, because wiring a filesystem server, a web-fetch server, and an outbound-API server into one agent takes about a minute.
The defenses that hold are not smarter prompt filters. Willison's own point is that a guardrail catching 95% of injection attempts is a failing grade, because an attacker just iterates until they hit the other 5%. The defenses that hold are architectural. And on AWS, they already have names.

The architect's playbook: map each threat to the control that contains it

Here is how I think about hardening an MCP deployment on AWS. Each item pairs a real threat class with the AWS control that bounds its blast radius, instead of betting on stopping the trigger.

1. Least-privilege IAM is the boundary, not the application policy

Both incidents landed here. In CVE-2026-16584, IAM was the control that stayed enforced when the server's own policy failed open. In the AgentCore case, the damage was defined entirely by what the stolen service account could reach.
The rule: the identity your MCP server or runtime runs under should hold the narrowest set of permissions that lets its tools do their job, and nothing more. If the agent only reads, give it a read-only role. Scope resources explicitly: one bucket, one table, one secret ARN, never *. Assume the application-layer allow-list will fail, because in CVE-2026-16584 it did, and ask the real question: when it fails, what can this identity still touch? That answer is your blast radius. IAM is enforced outside the server process, which is exactly why it survives a compromise of the server process.

2. Separate the invoke identity from the reach identity

Unit 42's phrase, "authorized to invoke is not authorized to reach," is the design rule. The permission to call the agent (bedrock-agentcore:InvokeHarness) must be a different, far smaller grant than the permissions the agent uses downstream. Never let the thing a user can trigger inherit the full downstream authority of the runtime. If you must hold a long-lived, high-privilege service credential for the agent to stay autonomous, treat it as the crown jewel it is. Scope it to a single downstream integration, and assume any successful injection can reach it.

3. Control egress at the network, because exfiltration needs a way out

The lethal trifecta's third leg is "a way to communicate externally." Take it away. On AWS, that means running the server or runtime in a VPC with no open route to the internet, and routing any legitimate outbound traffic through a single egress point you control: a proxy or firewall with an allow-list of the exact endpoints your tools need.
Block the private and link-local ranges that enable SSRF and metadata theft: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and above all 169.254.0.0/16, which is where 169.254.169.254 lives. Unit 42's own guidance was to watch outbound traffic from the agent's containers, because an endpoint that is not on your integration list is not configuration drift. It is an active injection in progress. A VPC endpoint and a deny-by-default egress policy turn "exfiltrate the token to my webhook" into a connection that never leaves your network.

4. Make every control fail closed

CVE-2026-16584 is the cautionary tale for the whole industry. A control that fails open is worse than no control, because it hands you false confidence. When you configure a security policy, a WAF rule, a Lambda authorizer, or an MCP allow-list, verify the failure mode. If the policy data cannot load, the correct behavior is to refuse requests, not serve them unprotected. If the tool offers a fail-closed mode, as aws-api-mcp-server eventually did, turn it on. Then test it by breaking the thing it depends on and confirming the door actually shuts.

5. Treat tool definitions and tool outputs as untrusted input

The model-layer attacks, tool poisoning and rug pulls and tool shadowing, all exploit one fact: a tool's description is injected into the model's context with near-instruction authority, before any human reviews it. Invariant Labs demonstrated this live against Cursor in 2026, hiding instructions in a description that made the agent read SSH keys.
The architectural responses are concrete. Inspect the full description and schema of any third-party server before you connect it. Hash those descriptions at approval time and re-confirm if they change. Keep servers from different trust domains in isolated sessions rather than co-mingled. A tool description is attacker-controllable. So is the body of a web page your fetch tool returns. Neither should silently expand what the agent is allowed to do.

6. Scope the tools themselves, especially the shell

The AgentCore shell tool was the entire attack surface in Unit 42's chain, and it was on by default. The lesson generalizes. A general-purpose shell can reach anything the runtime can, so an injection that lands in a shell is bounded only by the runtime's privilege, not by any typed schema. Scope the available tools down to what each session actually needs. In AgentCore specifically, remember that allowedTools applies at InvokeHarness time, not CreateHarness, a detail that is easy to miss and expensive to miss. If a session does not need a shell, it should not have one.

7. Keep secrets out of plaintext at rest, and shrink their time in use

Unit 42's sharpest point: a vault protects credentials at rest and in transit, but not in use, because any credential has to become plaintext in memory to be useful. You cannot fully close that window, but you can shrink it and you can control what else shares that memory. Use AWS Secrets Manager or Parameter Store rather than environment variables or config files for anything long-lived. Prefer short-lived, dynamically issued credentials over static ones, so a stolen token expires fast. And isolate the component that resolves secrets from the component that runs arbitrary code, so a shell tool is not reading the heap of the process holding your JWTs.

8. Put a human in front of anything irreversible

This is the backstop that holds even when every other layer fails, because it breaks the automation between "the model decided" and "the world changed." For any sensitive or irreversible action, writing to production, deleting, sending data outward, spending money, require an explicit human confirmation that shows the actual resolved action, not a friendly label. It is the one defense that does not depend on correctly classifying malicious text, which is precisely why it matters when prompt injection succeeds. My own agents default to read-only for exactly this reason. An agent that cannot write cannot be tricked into writing.

The uncomfortable part: the LLM's judgment is not a security control

The through-line of both 2026 incidents is that the model's reasoning cannot be trusted as a boundary. Unit 42's researchers were refused by a well-aligned model, so they swapped in a more permissive one and landed the same attack. The model's judgment is probabilistic. It is steered by whatever text reaches its context, and that text can be written by your attacker. Any security argument that reduces to "the model will refuse" is not an argument. It is a hope.
So build as if the model will eventually be convinced to do the worst thing its tools allow, and make sure its tools do not allow much. That is not pessimism. It is the same assume-breach posture we already apply to every other component on AWS. We do not trust that an EC2 instance will never be compromised. We put it in a private subnet with a tight security group and a scoped instance role, so a compromise is survivable. An MCP server deserves the same respect, because it is a more capable boundary than that instance ever was.

A deployment checklist you can run

Before an MCP server or agent runtime faces untrusted input in production, I want every one of these to be true:
  • The runtime identity holds least-privilege IAM, scoped to specific resource ARNs, assuming the app-layer policy will fail.
  • The invoke permission is strictly separate from, and smaller than, the downstream reach permission.
  • The workload runs in a VPC with deny-by-default egress and an allow-list of exact downstream endpoints; private and link-local ranges are blocked.
  • Every security control's failure mode is verified to fail closed, and tested by breaking its dependency.
  • Third-party tool descriptions are inspected in full, hashed at approval, and re-confirmed on change; trust domains are isolated.
  • Available tools are scoped per session; no shell or file access unless the session needs it.
  • Secrets come from Secrets Manager or Parameter Store, are short-lived where possible, and the secret-resolving process is isolated from any code-executing tool.
  • Every irreversible action requires a human confirmation that shows the resolved action.
  • Dependencies are pinned and patched; CVE-2026-16584 was fixed in a point release that only helps you if you upgrade.
None of this is exotic. There is no new product to buy. It is the boring, well-understood AWS security model, least privilege and network isolation and fail-closed design and assume-breach and defense in depth, applied to a component we have been treating as a toy. The attackers already stopped treating it as a toy. The two incidents above are the proof.
Build the agent. Wire the tools. But put the same boundary around it that you would put around anything else in your account that holds an identity and talks to the internet. In the agent era, that is most of what security engineering is.

Sources: AWS Security Bulletin 2026-063-AWS (CVE-2026-16584); Unit 42, "A Vault with a Heap-View: The Uncomfortable Space Between AgentCore Harness and Identity" (September 2026); the MCP Security Best Practices guidance and the 2025-06-18 and 2025-11-25 protocol revisions; Simon Willison's "lethal trifecta" framing; and the Equixly (2025) and Zuplo-summarized (2026) MCP server audits.

Follow me for more on AWS architecture, DevOps, and AI Infrastructure:
Portfolio  | LinkedIn  | Dev.to  | YouTube  | Email  | AWS Builder Center  | X 
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article