Back labs.zenity.io Agentcorruption Credential Theft From Aws Secrets Manager And More
Up to now, we showed how far we could get once we had the over-privileged role in hand: we were able to pull container images, talk to other agents that we were never allowed to, read private conversations, plant short- and long-term memories to control agents’ behavior and delete user data. All of this stays within the AWS region boundary.
But agents don't live in isolation.
They need tools to execute tasks and get work done. Through MCP servers, connectors or APIs, tools allow agents to connect to external services that require authentication, such as GitHub, Slack or Salesforce.
In this post we break the boundary and gain access to an organization's resources and connected third-party services that live outside of AWS. We go deeper and show how we harvested credentials and API keys used by agents and AgentCore gateways (AgentCore's managed service for exposing external APIs and services to agents as tools), by scraping agents' source code, reading environment variables and enumerating them by brute force. Much of this comes down to a single permission we found on the agent's default execution role that allowed direct access to AWS Secrets Manager.
There are two main ways to connect external tools to agents running on AgentCore: directly in the agent's code, or through the AgentCore gateway.
In both cases, these tools often require some sort of authentication. AgentCore handles this through AgentCore Identity and Outbound Auth that stores credentials and manages token issuance. It currently supports both API key and OAuth authentication methods.
As the diagram below shows, you can use Outbound Auth directly from your agent’s code or add it to the gateway target.
To add a new Outbound Auth, the user has to provide:
A name for the provider, or a default name suggested by AWS.
A secret — the API key or OAuth client secret.
In response, AgentCore creates:
A new Outbound Auth entry that holds metadata (provider type, name, ARN, OAuth parameters - but not the secret itself) and is visible in the AWS console under AgentCore.
A secret, created automatically in AWS Secrets Manager, that holds the API key or client secret.
Outbound Auth Credential harvesting - Scraping agents’ code
This is the permission to access AWS Secrets Manager:
At first glance this looks reasonably scoped - it's limited to the bedrock-agentcore-identity secrets. But the resource paths end in /oauth2/* and /apikey/* , which matches every provider across the whole region, not just the ones used by the compromised agent. One agent's role, in other words, can read every outbound secret - if you can figure out the secret names. We did, two ways: by brute-forcing the default provider names, and by scraping the agents' source code we already had locally, which is the focus of this section.
Let’s first understand how an Outbound Auth provider name maps to its secret name and ARN in Secrets Manager.
This table shows exactly this:
{ OUTBOUND_AUTH_ARN_PREFIX }/apikeycredentialprovider/{ provider_name }
bedrock-agentcore-identity!default/apikey/{ provider_name }
{ SECRET_ARN_PREFIX }/apikey/{ provider_name }-{5_length_random_string}
{ OUTBOUND_AUTH_ARN_PREFIX }/oauth2credentialprovider/{ provider_name }
bedrock-agentcore-identity!default/oauth2/{ provider_name }
{ SECRET_ARN_PREFIX }/oauth2/{ provider_name }-{5_length_random_string}
OUTBOUND_AUTH_ARN_PREFIX = “arn:aws:bedrock-agentcore:{region}:{account}:token-vault/default”
SECRET_ARN_PREFIX = “arn:aws:secretsmanager:{region}:{account}:secret:bedrock-agentcore-identity!default/”
As we saw above, agents can use Outbound Auth directly from the agent’s code. This can be done in several ways that we cover .
Extract Credential Providers through decorators
AgentCore’s identity module provides two decorators that wrap the agent’s tool code and automatically get a token before the tool call happens:
requires_access_token decorator - used for OAuth access tokens.
requires_api_key decorator - used for API-key auth.
Here is a simplified example:
In both cases the provider_name is right there in the decorator arguments. Once we've scraped it from the source, we plug it into the mapping from the section to construct the secret name and read it straight out of Secrets Manager - and because we already pulled every agent's image back in this blog , we run this across all of their source code at once.
Extract Credential Providers through SDKs Clients
Decorators are just syntactic sugar - under the hood they call the same SDKs. Alternatively, agents can request an API key or an access token directly from the code without the decorators - by using raw clients from bedrock_agentcore and boto3 SDKs.
Code snippet to get tokens using bedrock_agentcore SDK:
Code snippet to get tokens using boto3 SDK:
Whichever SDK the developer reaches for, the tell is the same: the provider name is passed as an argument ( provider_name or resourceCredentialProviderName ), sitting in the source we've already scraped. From there it's the same play as before - construct the secret name from the mapping, call GetSecretValue , and repeat across every agent's code we pulled from ECR.
Extract Secrets Names
Instead of going through Outbound Auth, an agent's code can read the secret straight from Secrets Manager - by either secret name or ARN.
Here, we get the secret name or ARN directly in hand.
In the sections above, we saw the different ways provider names and secrets are exposed in the source code we collected. By scraping the source code of every agent, we gathered the provider names and secret references, mapped them to their secret names, and pulled the actual values straight from Secrets Manager - all enabled by a single secretsmanager:GetSecretValue permission on the execution role.
Credential harvesting through enumeration
Brute force matters because not every credential shows up in source code. A provider referenced only by a gateway target - never from an agent's own code - leaves no trace in any code for us to scrape.
Guessing the default provider name is how we reach those.
AWS suggests a default provider name for each new Outbound Auth provider, which the user can accept as-is or replace with their own.
The naming convention depends on the provider type:
resource-provider-api-key-XXXXX
Now all that is left to do is to iterate through all options and hope for some hits. But first, rather than guessing the suffix blindly, we need to understand how it's generated - that shrinks the space and improves our odds.
We found that this logic is implemented on the client side - meaning the default name is rendered in the browser and never comes from the backend.
Reading the minified client code, we found the function that builds the suffix - Pb() :
Then following the Pb() function definition:
We now understand that the 5-character suffix consists of digits (0-9) and lowercase letters with repetition allowed, and are ready to solve an easy combinatorics exercise:two provider types, each with a 5-character suffix: 2*(10+26) 5 = 120,932,352 total secrets to enumerate.
120 million sounds like a lot, but it's a small enough space to iterate through. The real constraint isn't compute, it's Secrets Manager's GetSecretValue throttling. At the rate we could sustain, a full enumeration takes several hours, and that's the problem: the STS credentials we pulled from IMDS expire long before the enumeration finishes.
But those credentials don't just vanish - the agent has to keep running, so the platform keeps fresh ones available at IMDS. Knowing this, we built an automatic process that renews the STS credentials by programmatically querying the agent’s IMDS endpoint, analyzing its response and replacing the existing credentials with fresh ones, so the enumeration process runs end-to-end without interruption.
At this point we have a collection of Outbound Auth credentials - some scraped from agents' source code, others recovered by brute-forcing the default provider names to reach credentials that no agent references directly.
, we show how we harvest more credentials from agents’ environment variables.
API Keys theft from environment variables
Environment variables are name-value pairs injected into a process at runtime that influence how it behaves. Programs commonly use them to hold configuration such as domain names or database URLs, and to keep secrets like API keys and tokens out of the source code.
Embedding secrets into the source code is an anti-pattern, and a real danger when it comes to agents. We’ve seen in the wild many attacks, especially on coding agents, where an attacker could trick the agent into spelling out secrets living in local files.
One common mitigation is to move secrets out of the code and into environment variables - so they never sit in a file that the agent can read.
AgentCore allows you to do so easily with the agentcore cli. So, for example to connect your agent to an LLM provider, say Anthropic or OpenAI, you provide your API key and run your agent with:
At startup, the agent reads the API key and initializes the model.
For example, an agent developed with Strands SDK (an open source framework from AWS for building AI agents) using gpt-4o - an OpenAI model, would look like this:
Which means: if we can read an agent's environment variables, we can likely harvest more API keys and secrets.
We noticed that environment variables are exposed through the IMDS’s user-data endpoint, and were able to read all of them with this simplified prompt:
Note that, all variable names are prefixed with CONTAINER_ENV_ including our OPENAI_API_KEY variable that holds the API key to authenticate to OpenAI.
It all started with a single prompt to one exposed agent that could make HTTP requests - our door to the Instance Metadata Service (IMDS) and, through it, to the agent's temporary STS credentials.
With those credentials in hand, we could assume the agent's IAM role and act as if we were it.It turns out that the agent’s default execution role is overprivileged and scoped to the whole AWS region.
In blogs we laid out the vulnerability itself, then walked the blast radius: pulling every agent's image and source code, invoking agents we were never meant to reach and moving laterally across the region, reading private conversations across all users and sessions, planting memories to hijack agent behavior and maintain persistent control, and finally, in this blog, harvesting the outbound credentials and API keys that reach past AWS into the organization's third-party connected services.
As enterprises race to adopt AI agents, the cloud is becoming the default place to run them, and that creates a fundamental tension between business and security: agents need their creative space to be useful, while cloud security is built around least privilege.
In this research we show what happens when that balance breaks and how a single design flaw can collapse the entire cloud security model:
A single agent becomes an entry point to everything around it, including internal agents running in the same environment, organizational resources, and sensitive data.
AgentCorruption shows this for AgentCore, but the problem is not limited to it.Applying defense-in-depth principles when deploying AI agents is non-negotiable. Organizations should take this seriously: scope and review the permissions they grant their agents, adhere to least privilege, continuously improve their posture, and monitor runtime activity.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
