TL;DR: AgentCore Harness is AWS's service for running AI agents. When a tool needs a stored login token, the harness pulls it out of the vault as plaintext into its own memory. Its built-in shell tool is on by default, and in a lab test by Palo Alto Networks' Unit 42 it ran as root and could read that memory. Using a permissive model, the researchers hid one line in a support ticket, took the token and used it from a laptop to read customer records. AWS closed the report, pointing to tool scoping and outbound filtering as controls customers own.
Also this week: how attackers test stolen AWS keys, what AWS's automatic block on leaked keys misses, and a Rust tool that can put CI secrets into a build cache.
Attack of the Week: the shell tool reads the process that holds the secret
Niv Rabin at Unit 42, "A Vault with a Heap-View: The Uncomfortable Space Between AgentCore Harness and Identity" [2026-09-18].
Rabin built a support agent for a made-up company, in what Unit 42 calls a default-configuration deployment. It looks up customers through an MCP server, a common way for an agent to call tools that run outside it. That server wants a login token.
The token is stored in AgentCore Identity, the vault. The harness config points at it by ARN, AWS's ID for a resource:
Authorization: Bearer ${arn:aws:bedrock-agentcore:us-east-1:{account-id}:token-vault/default/apikeycredentialprovider/my-mcp-service-cred}That's the pattern AgentCore documents. At call time the harness swaps the ARN for the real token, inside its own process.
Step 1: the shell is already there. AWS's docs said in late August, per Unit 42, that the built-in shell and file_operations tools "are available in every session unless you restrict them with allowedTools."
Step 2: get the agent to use it. Asked directly to run commands, the model refused twice. The harness can use a different model on each call, so Rabin switched to a more permissive one. Then came the prompt injection, text that smuggles instructions to the agent: an HTML comment in a support ticket telling the agent to curl a script into python3, and not to mention it.
Step 3: look around. whoami said root. With no ps installed, the script read the process list from /proc, a folder Linux fills with one entry per running process. PID 1, the container's first process, is python3.10 -m loopy.server: the harness itself. Rabin: "The same user identifier (UID) runs the entire chain, and every process is root."
Step 4: read the harness's memory. Linux can expose a process's memory as a file, /proc/{pid}/mem, and access checks decide who can read it. Here, PID 1's memory file was readable. A second script, sent the same way, searched it for the MCP server's URL and a JWT, the signed text string the server accepts as a login. It found one JWT, 1,034 bytes long, and posted both to the attacker's webhook.
Step 5: use it from anywhere. From a laptop, "with no AWS credentials required," Rabin called the MCP server's lookup_customer tool with that token and got customer records back. The token belonged to mcp-service, the operator's service account, which every user session shares. So the token works for anything mcp-service can do, no matter who filed the ticket.
The platform fix. Unit 42's rule for whoever builds the runtime: "Any credential the runtime resolves has to live somewhere the shell tool cannot read, or the shell tool has to run in a sandbox isolated from the process that resolves it."
AWS's answer. Unit 42 reported it on May 19 through HackerOne, a vulnerability-report platform. On June 10, AWS closed it as "informative," HackerOne's label for a report that "contains valid information, but the information provided doesn't require action." AWS pointed to its shared responsibility model and to "allowedTools scoping and egress filtering as customer-side controls." And allowedTools works per call: it "only scopes tool selection at InvokeHarness time, not at CreateHarness."
One caveat: the post doesn't show whether the model that refused would have followed the hidden comment, and Unit 42 doesn't report seeing this in real attacks.

Ship This Week. Pass a scoped allowedTools list on every InvokeHarness call. Leave out shell and file_operations wherever a session doesn't need them. Put a server-side wrapper in front of InvokeHarness so our code always supplies the list, since a call without one gets both tools back.
Limit the harness's outbound traffic to the services it's set up to call, and alert on the rest: per Unit 42, any other destination "is evidence of an active injection, not configuration drift." Give each vault credential only the access its integration needs.

Rule of the Week: how attackers test a stolen AWS key
Sparks found 2 attacker dashboards for stolen secrets, Loot and UltraVault, open without authentication when observed. "The UltraVault operator's own inventory has claimed 42,286 secrets, of which only 2.7% are listed as live within the platform."
The server behind them tested stolen keys against Amazon Bedrock, AWS's service for calling AI models. Sparks: "Confirm the identity works, enumerate what the account can reach, then start calling models." After that, "a burst of InvokeModel calls to multiple Anthropic model versions, repeated dozens of times across regions in under a minute."
same userIdentity.accessKeyId:
GetCallerIdentity
-> ListFoundationModels or ListInferenceProfiles
-> InvokeModel in 2+ awsRegion values within ~60 seconds
count failed calls too (any errorCode)
raise severity if userAgent contains "kali-cloud"
or is exactly "Python-urllib/3.13"
include temporary (ASIA) keysThe 60-second window and the 2-region count are guesses to start from. Tune them on real traffic.
Count failed calls too: once AWS blocks a leaked key, its InvokeModel calls come back denied. Check that CloudTrail, AWS's API audit log, covers every region and that read-only events reach our alerting, since the first 2 steps are reads.
Datadog didn't publish its indicators of compromise, "to protect the exposed credentials of many victims." Alert on the call sequence. User agents are easy to change. Treat them as a weaker extra signal.
Defender's Corner: know what AWS's quarantine leaves open
When AWS finds one of our access keys in public, it attaches a deny policy to that key's IAM user. Alert when that happens. Margaret Kelley at Unit 42, "From Exposure to Lockdown: How AWS Neutralizes Compromised IAM Credentials through Managed Policies" [2026-09-21].
In Kelley's public-repo test, "AWS attached the AWSCompromisedKeyQuarantineV3 managed policy within 10 seconds of the access key exposure."
The user field names the leaked user. CloudTrail's AttachUserPolicy event puts the leaked user, TestUser, in userIdentity, "even though TestUser did not perform this action." Its userIdentity.invokedBy, sourceIPAddress and userAgent read AWS Internal, which shows it was AWS.
It's a deny list. AWS added InvokeModel and 4 other Bedrock actions in October 2024, influenced by research from the security firm Permiso. Anything else the user is allowed to do still works: this "allows advanced threat actors to use the exposed credentials for any actions the policy does not explicitly deny."
Do this Monday. Alert on AttachUserPolicy where requestParameters.policyArn contains AWSCompromisedKeyQuarantine, and treat it as a live incident. Leave the policy attached, as its description says, and deactivate the key. Check what the key did in CloudTrail outside the deny list: aws iam get-policy on arn:aws:iam::aws:policy/AWSCompromisedKeyQuarantineV3 gives the default version id, and aws iam get-policy-version returns the list.
AWS's Health alert and Support case don't land in CloudTrail, so route them to security; Kelley suggests Systems Manager Explorer for the Support cases.
Agent Bench: find the runtimes where a secret and a shell share a user
Point a coding agent at the code that deploys and calls our agents: harness configs, InvokeHarness call sites, container specs, Kubernetes manifests and Terraform. Ask for 3 lists, each item at file:line:
Secrets each runtime can put in its own process or filesystem (vault references resolved at runtime, secret environment variables, role credentials, mounted secret files), and the user that code runs as.
Tools the agent can call that run commands or read files, including built-in defaults the config never names, and which user runs each.
Whether tool access is restricted on each call.
What you walk away with: the agent runtimes where a plaintext secret and a command-running tool run as the same user, or as root, in the same container.
Expect 2 mistakes. Coding agents guess the runtime user from config, and they miss default tools the config doesn't list, like AgentCore's shell. So check one live container by hand.
In a test session where command tools are allowed, run id and read the Uid: line of /proc/{pid}/status for the process holding the secret, both through the agent's tool. In AgentCore that's PID 1; elsewhere it may be another process. A match, or id saying root, is the warning sign. Treat a refusal as inconclusive: Unit 42's model refused twice.
Try it this week: start with an agent that reads outside text, like tickets, email or pull requests.
Also on the Radar
Manish Goregaokar for the Rust Security Response Team, "GitHub Actions leaking secrets when Miri output is cached" [2026-09-21].
Miri, Rust's checker for undefined behavior, needs to save some environment variables between runs. Instead it saved all of them in the target/ build folder. Some Rust projects cache target/ in GitHub Actions, and a typical setup has main writing the cache and pull requests reading it.
That puts any secret the Miri step could see into a cache pull requests can read. A maintainer has to approve CI on a contributor's first PR, but later PRs run it on every push. Anyone who has landed one change can read the cache, then "cover their tracks by pushing a second commit." The Rust team says Miri in the 2026-09-22 nightly fixes it.
Their wider warning: "Many tools do not have special handling for secrets, and assume the entire environment can be written to the filesystem." Keep secrets out of any job that writes a cache PRs can read, including workflow-level env: secrets and ones an earlier step left in the environment. If a job already had secrets, clear the cache and consider rotating them.
Which of these 4 would our own logs have caught? Hit reply and tell me.
Full issue with sources: join.defensive.works/p/your-ai-agent-can-read-its-own-vault-secret
Until next Tuesday,
R.K.
// end of issue 023
Sponsored content may appear below. Not part of Weekly Recon editorial.
Stop rewriting prompts. Start engineering loops.
Most developers still babysit AI one prompt at a time. Top engineers don't. They build loops: systems where AI plans, executes, and self-corrects while they focus on what matters. The Code built The Ultimate Guide to Loop Engineering to give you the exact techniques Silicon Valley engineers use to ship faster.
Sign up for The Code and get:
The Ultimate Guide to Loop Engineering, the patterns that turn AI from assistant into engine, plus real workflows you can set up today
The Code newsletter (5 min daily) to keep learning the agentic techniques keeping top engineers 6 months ahead

