On 8 October, Zenity Labs published AgentCorruption. The research shows how a single crafted prompt to one public-facing agent on Amazon Bedrock AgentCore let the researchers take over every AgentCore agent in the same AWS account and Region. They read private conversations, copied agents' source code, pulled stored secrets and planted memories that changed how the agents behaved (The Next Web).
AWS disputes the framing. Its statement says the research "inaccurately paints expected and documented behavior as a vulnerability" and recommends that customers "grant their execution roles only the permissions their agents need." No CVE was assigned. Both things can be true at once: the behaviour was documented, and the default made it dangerous. For anyone deploying agents the practical lesson is the same either way. The controls that would have stopped this belong to the deployer.
What happened
As reported by The Next Web and Zenity's own write-up, the chain went like this:
- A prompt told the agent to use an HTTP-capable tool to request 169.254.169.254, the instance metadata service (IMDS) that hands temporary credentials to cloud workloads.
- The virtual machines running the agents did not block that traffic, so the agent returned the credentials for its execution role.
- The researchers used those credentials from their own machine to list every agent in the account and Region.
- The default role let them download container images and source code, and read API keys and AWS Secrets Manager values, including credentials for external services.
- They called internal agents they were never meant to reach. They also wrote fake conversation events into agent memory, which acted as persistent instructions pointing agents to an attacker-controlled page.
- In the demonstration, agents forwarded ongoing conversations to the researchers' server while users kept working normally.
The timeline matters. Zenity reported the metadata access on 25 December 2025 and the broad default role on 12 January 2026. AWS moved new agents to IMDSv2 on 14 February and closed the metadata report as "informative" in April. By 29 September it had narrowed the default role, removing the permissions to invoke other agents, read conversations and access Secrets Manager (LetsDataScience). Agents created before those changes may still carry the old defaults.
Why it happened
Two ordinary decisions stacked up. First, the agent's network reach included a link-local address that hands out credentials to whatever asks. Second, the role behind those credentials was sized for the platform's convenience, not for the agent's job. Zenity lists bedrock-agentcore:InvokeAgentRuntime across the Region, ListEvents (reading conversations), CreateEvent (writing memory), GetResourceApiKey and secretsmanager:GetSecretValue.
Neither problem is new. Stealing metadata credentials through server-side request forgery is a well-known cloud attack, and it is why AWS built IMDSv2's session token as defense in depth against open firewalls, reverse proxies and SSRF. What changed is who sends the request. An agent is not a confused web server forwarding a single GET. It is a program built to follow instructions with whatever tools it has. Zenity notes that any agent tool able to make HTTP requests was enough, and that the move to IMDSv2 was only a partial fix.
That distinction matters most for coding agents. IMDSv2 requires a PUT to get a session token, then a GET that carries the token. A coding agent with a shell can do both. AWS's own documentation shows the whole handshake as two curl commands.
This is not only an AgentCore problem
Replace "AgentCore runtime" with the places your coding agents actually run: a cloud development VM, a self-hosted CI runner, a container on a build host, or a developer laptop with long-lived keys in ~/.aws/credentials. In each one a credential sits one request or one file read away. The question is always the same: what can the agent reach, and what does that identity let it do?
The injection doesn't have to be clever either. Coding agents read READMEs, issues, web pages and tool output as part of their job, and any of those can carry instructions. You cannot count on catching every instruction. You can count on controlling the action the instruction asks for.
The controls that prevent it
- Least privilege per agent identity. Give each agent or workload its own role, scoped to the resources it touches. No region-wide invoke rights and no read-all-secrets. Treat AWS's tightened default as a floor, and audit agents deployed before 29 September.
- Close the metadata path at the network layer. Require IMDSv2, keep the PUT response hop limit at 1 where containers don't need IMDS, or block 169.254.169.254 from the agent's network namespace altogether.
- Deny the action where the agent executes. A coding agent rarely has a legitimate reason to call a metadata endpoint or print a cloud credentials file. Deny both by default and make any exception explicit and owned.
- Ask before privileged cloud commands. Send calls such as aws sts, aws secretsmanager get-secret-value and aws iam to a human for approval instead of letting them run silently.
- Keep the audit trail outside the agent. Log every attempt, including the denied ones, somewhere the agent cannot edit. A denied metadata request from a coding agent is a strong sign that something injected an instruction.
For EC2-based dev boxes and runners, the first two checks take minutes:
# List instances that still accept IMDSv1
aws ec2 describe-instances \
--query "Reservations[].Instances[?MetadataOptions.HttpTokens=='optional'].InstanceId" \
--output text
# Require IMDSv2 and keep the token on the host (hop limit 1)
aws ec2 modify-instance-metadata-options \
--instance-id i-0123456789abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1Where allow, ask and deny fit
Policy guardrails turn those controls into a verdict at the moment the agent acts. With DarkControl, a lightweight endpoint agent sits between the coding agent and the operating system and checks each shell command, file write and network call against a central policy. A reasonable baseline for this class of attack:
- Deny: network calls from coding agents to 169.254.169.254, [fd00:ec2::254] and metadata.google.internal, plus shell commands that read ~/.aws/credentials, gcloud credential stores and similar files.
- Ask: cloud CLI calls that mint tokens, read secrets or change IAM, and kubectl against production contexts.
- Allow: the package installs, builds and test runs that make up most of a developer's day.
- Default: anything unmatched goes to Ask, so a new path to a credential lands in front of a human first.
- Audit: every attempt is logged immutably, with CSV and SIEM export, so a blocked metadata call becomes an alert and not just a silent no.
Be clear about scope. Endpoint policy governs coding agents on machines you manage. It does not fix the IAM model of a cloud-hosted agent platform, which stays with your cloud team. And whatever tool you use, test coverage before you claim a block. An agent can send the same request from a Python script it just wrote as easily as from curl, so run a watch-only period and check which of those paths your policy actually sees.
The compliance angle
None of this needs new obligations. NIS2 Article 21(2)(i) already expects access control policies, and ISO 27001:2022 controls A.5.15 (access control) and A.8.2 (privileged access rights) already apply to non-human identities. An AI agent holding a role is a privileged identity. Auditors will increasingly ask what it could reach, who approved its elevated actions and where that record lives.
Want to see what your coding agents try to reach today? DarkControl's free 7-day watch-only audit blocks nothing and shows every command, file write and network call across up to 10 devices.