Every HTTP Tool You Give an Agent Is an SSRF Primitive

Zenity's AgentCorruption went from one prompt to every AgentCore agent in a region. Every link was a default. Here's the control that breaks each one.


AgentCorruption as a kill chain: a prompt to a public agent drives its HTTP tool to the metadata endpoint, the returned role credentials work from anywhere, the region-wide default role reaches every agent, and memory writes make the compromise persistent — each link paired with the control that breaks it

Zenity Labs published AgentCorruption yesterday, alongside a talk at SecTor in Toronto. The short version: one prompt to one public-facing agent on Amazon Bedrock AgentCore, and the researchers could list every AgentCore agent in the same account and region, pull their container images, invoke them, read their users’ private conversations, plant instructions in their long-term memory, and pull secrets out of Secrets Manager.

AWS’s response, as quoted by The Next Web, is that the research “inaccurately paints expected and documented behavior as a vulnerability.”

I think both sides are right, and that’s the part worth your time. Nothing in this chain is exotic. Every link is a default, and every default has been documented somewhere. That’s precisely why it generalises to whatever platform you run agents on.

The technical write-up is unusually clear, so here it is as a kill chain, with the control I’d want at each link.

#What happenedThe default that allowed itThe control that breaks it
1A prompt asked the agent’s built-in http_request tool to fetch http://169.254.169.254/latest/meta-data/iam/security-credentials/The microVM didn’t restrict traffic to the metadata serviceDeny link-local and metadata addresses on the tool’s network path
2The agent returned the execution role’s temporary credentials; the researchers used them from their own machineCredentials worked from anywhereBind credentials to their origin where your platform supports it; short lifetimes regardless
3With that role they found agents, pulled images, invoked agents, read conversationsThe default role covered all AgentCore resources in the account’s regionOne role per agent, resources pinned to that agent’s ARNs
4user-data exposed the image URI; images held source and sometimes secretsImages treated as build output, not as sensitive dataNo secrets in images; repository policy scoped per agent
5A forged conversation event was stored in memory as a lasting instructionMemory writes allowed for any holder of the roleMemory writes as a privileged path, with provenance and audit
6Tool API keys and Secrets Manager values, including credentials for services outside AWSRegion-wide GetSecretValueSecrets scoped per agent; assume the blast radius leaves the cloud account

Zenity says the same result came through the shell tool running curl, which matters for the next section.

Two links deserve more than a table row.

Link 3 is the multiplier. Without it, this is one agent leaking its own credentials, which is bad but bounded. According to Zenity, the default execution role granted actions like bedrock-agentcore:InvokeAgentRuntime, bedrock-agentcore:ListEvents, bedrock-agentcore:CreateEvent and secretsmanager:GetSecretValue at region scope, not agent scope. That turns “an agent was tricked” into “every agent in the region is compromised,” and it’s the difference between an incident and a breach.

Link 5 is the one that should change how you think about agent memory. The planted memory told the agent to visit a researcher-controlled page before every answer. After that, the researchers could change the agent’s behaviour by editing a web page — no further memory writes, no further access to AWS. The persistence moved outside the environment entirely. You could rotate every credential involved and the agent would still be taking instructions from a stranger. I wrote about the five kinds of agent memory as a design choice. This is the threat model that goes with it: memory is a write path into future behaviour, and it needs the same scrutiny as a deploy pipeline.

Why IMDSv2 isn’t the fix

According to both AWS and Zenity, newly deployed AgentCore agents have run IMDSv2-only since 14 February 2026. That’s good. It’s also easy to over-read.

IMDSv2 was designed against open WAFs, open reverse proxies and SSRF bugs — situations where an attacker controls a URL that something else fetches, but can’t choose the HTTP method or add headers. Getting a token needs a PUT with a specific header, and a URL-only SSRF can’t produce one.

An agent’s HTTP tool isn’t a URL-only SSRF. The Strands http_request tool used in the research takes the HTTP method as a free-form parameter — its own schema description lists PUT — and takes arbitrary headers too. A shell tool can run any curl you like. Once the attacker is steering a complete HTTP client in natural language, the PUT is one more sentence in the prompt.

I haven’t tested that path against current AgentCore deployments, and I’m not claiming it works there today. The point is about which layer you’re relying on. IMDSv2 defends against a constrained attacker. Agent tools remove the constraint.

There’s a structural problem underneath. AWS’s own statement is that “AgentCore agents can access the credentials for their own execution role through the metadata service” — the agent’s SDK needs that endpoint. So you can’t just block it for the whole runtime. What you can do is make sure the code path that executes model-chosen network requests is not the code path that can reach the credential endpoint. Route tool traffic through an egress proxy, or run tools in a separate sandbox with no metadata route. The agent’s own SDK keeps its credentials; the tool that fetches arbitrary URLs never sees them.

Do the check at connection time, after DNS resolution and after following redirects. A URL denylist that inspects the string 169.254.169.254 loses to a hostname that resolves there, or a public URL that redirects there.

Both sides are right, which is the problem

AWS’s position, in full: an agent can reach its own role’s credentials by design, cross-account access requires explicit grants on both sides, and customers should grant execution roles only the permissions their agents need. All true. Least privilege is the customer’s job under the shared responsibility model.

Zenity’s position: the default role wasn’t least privilege. By their timeline, they reported the metadata access in December 2025 and the broad role in January 2026; AWS closed the first report as “informative” in April; the role was unchanged when they checked in June; and on 29 September they found it substantially narrowed.

Here’s my problem with “documented and expected.” Most agents in production were deployed by someone following a quickstart, under time pressure, who accepted the default role because the agent worked. For most deployments, the default is the policy. A permission set that’s documented as broad and expected to be narrowed is, in practice, a permission set that ships broad.

It’s the same lesson as the 47th approval click, seen from the vendor’s side: a control that depends on someone doing the careful thing every time decays into whatever the easy path produces, regardless of what the documentation recommends.

What I’d do this week, on any platform

None of this is specific to AgentCore. Swap in your agent runtime of choice.

Count your SSRF surface. List every tool, on every agent, that can make a network request: HTTP, shell, browser, code interpreter, “fetch URL,” anything that takes a URL or a hostname. That list is your SSRF surface. Most teams have never written it down, and it’s usually longer than they think.

Deny metadata and link-local at the network layer for the tool path. On AWS that’s 169.254.169.254 and the IPv6 endpoint fd00:ec2::254. Google Cloud and Azure also serve metadata from 169.254.169.254. Deny the whole 169.254.0.0/16 range and your private ranges unless a tool genuinely needs them, enforce it somewhere the agent process can’t configure, and check after resolution and redirects.

One execution role per agent. Resources pinned to that agent’s ARNs, nothing at region scope. Then go and look at the role your existing agents actually have. Neither AWS’s statement nor Zenity’s write-up says whether agents deployed before the changes picked up the narrower default, so don’t assume yours did.

Treat memory writes as privileged. Record which principal wrote each memory record and from which session. Alert on memory writes that don’t come from the agent’s own conversation path. A memory record that tells an agent to fetch instructions from a URL should be a detection rule in its own right.

Separate customer-facing and internal agents by account. Zenity’s closing advice cautions against running them side by side. An account boundary is the blast-radius boundary that still holds when the role is wrong — and the role is eventually wrong.

The old bug in the new client

SSRF used to need a bug: a URL parameter nobody validated, an image proxy that fetched anything. Agents with general-purpose HTTP tools make SSRF a feature. The request goes wherever the conversation leads, and the conversation is attacker-influenced by construction.

That’s the same shape as yesterday’s OWASP roundup: a boundary that existed as an assumption, met by a component that improvises. The fix is the same too. Don’t rely on the agent declining to make the request. Make sure the request goes nowhere when it does.


Related: Nine Agentic Exploits, One Sentence Worth Memorising · Your agents need identities, not API keys · Secrets Management: Scope, Lifetime, and the Blast Radius of a Leak

Frequently asked questions

What is AgentCorruption?

A chained attack on Amazon Bedrock AgentCore that Zenity Labs published on 8 October 2026 and presented at SecTor 2026. A single prompt asked a public-facing agent to use its HTTP tool to fetch the instance metadata endpoint, which returned temporary credentials for the agent's execution role. Because the default execution role was scoped across all AgentCore resources in the account and region, those credentials let the researchers list and invoke other agents, pull their container images, read private conversations, write to agent memory, and retrieve secrets.

Did AWS fix it?

AWS disputes that it was a vulnerability, describing it as documented and expected behaviour and recommending least-privilege execution roles. According to AWS and Zenity, newly deployed AgentCore agents have used IMDSv2 only since 14 February 2026, and Zenity observed on 29 September 2026 that the default execution role had been substantially narrowed. Neither source states whether agents deployed before those changes were updated.

Why is an agent's HTTP tool an SSRF risk?

Server-side request forgery is getting a server to make a request on an attacker's behalf, usually to an internal address the attacker can't reach directly. Classic SSRF needs a bug. An agent with a general-purpose HTTP tool makes requests to whatever URL the conversation leads it to as a feature, so anyone who can influence the conversation — a user, or text the agent reads — can steer requests at internal endpoints such as the cloud metadata service.

Does IMDSv2 stop this?

It raises the bar, but it is the wrong layer to rely on. IMDSv2 requires a PUT request with a specific header to obtain a session token, which defeats SSRF where the attacker controls only a URL. A general-purpose agent HTTP tool typically accepts the method and headers as parameters, and a shell tool can run curl, so the attacker is steering a full HTTP client rather than a URL. The durable fix is that the process executing model-chosen network requests should not be able to reach the credential endpoint at all.

Comments