A coding agent with AWS access grabs a secret the obvious way and drops the plaintext into its context. AWS Secrets Manager shipped a skill that blocks that and hands the agent a safe path instead. I ran it against a live account in Claude Code and pushed on the edges.
Give a coding agent shell access with AWS credentials, ask it to hit a database and watch what it does. It reaches for the password the obvious way.
$ aws secretsmanager get-secret-value --secret-id agent-toolkit-blog-demo/api-token --query SecretString --output text --region us-east-1
{"username":"demo_user","host":"demo-db.example.internal","password":"FAKE-pw-asdf","token":"FAKE-token-asdf"}
That value is now definitely in the model's context window and conversation history. It could be sitting in a session transcript on disk and end up passing into whatever tool call the agent makes next. You thought you were using Secrets Manager because you wanted to follow best practices and not have a password sitting around.
I'll admit I've done this. I've even pasted secrets straight up into the agent. When you gotta move fast, sometimes you let security take a backseat. Don't be me. Now when you're using agents to build on AWS, you can be fast and secure!
AWS Secrets Manager shipped this nifty feature: a secret safety skill in the aws-core plugin of the Agent Toolkit for AWS. The idea here is simple. An agent should be able to use a secret without ever seeing it. I spent an afternoon running it against a throwaway secret in a real account, then trying to get around it. Here is what happened.
Before I installed the plugin I told Claude Code the secret was a fake throwaway and asked it to fetch the value and reply in chat. It did it without blinking. With the plugin enabled the same prompt died on the spot, and the part I did not expect is that the skill shut it down before the hook even ran.
What the toolkit is, in one screen
If you're doing stuff with AWS using an agent and have not yet set up the Agent Toolkit for AWS, you need to get it set up. Below is some of the "what" around the toolkit:
- AWS MCP Server. A managed endpoint your agent talks to for all things AWS. Model training data can be stale, this gives you the capability of running the right AWS CLI commands, searching current AWS docs and logging calls in CloudTrail.
- Skills. Task runbooks in markdown, loaded on demand, written by people who ran the workflow and found where agents get stuck. The secret safety skill is one of these.
-
Plugins. A single install that bundles the MCP Server config and a set of skills.
aws-coreis the one to start with.
Using the toolkit is free. Skills and doc search work without credentials. API calls need AWS credentials. You pay for any AWS resources your agent deploys.
The demo
I created a throwaway secret with fake values so nothing real was ever at risk.
aws secretsmanager create-secret \
--name agent-toolkit-blog-demo/api-token \
--secret-string '{"username":"demo_user","host":"demo-db.example.internal","password":"FAKE-pw-asdf","token":"FAKE-token-asdf"}' \
--region us-east-1
You already saw the before. get-secret-value hands back the plaintext and it enters the agent's world.
Here's the command to install the toolkit in Claude Code:
/plugin install aws-core@claude-plugins-official
If that comes back with Plugin not found, your local marketplace index is stale. Run /plugin marketplace update claude-plugins-official and install again. If the marketplace is missing rather than stale, update will not help and you need /plugin marketplace add anthropics/claude-plugins-official first.
The skill activates on its own. So does a second piece I will come back to. One thing to know before you try it. Hooks load when the session starts, so restart your agent after installing or the block will not be there yet. Restart, ask the agent to fetch the secret and the request does not run.
The automatic block
A PreToolUse hook sits in front of the agent's tool calls. When the call would fetch a secret value, the hook denies it before it executes and hands the agent a message that points at the safe path.
Direct secret fetching is blocked. Use {{resolve:secretsmanager:secret-id:SecretString:key}} with asm-exec instead. Run /aws-secrets-manager for details.
Worth being precise about which layer does what here. When I asked in plain language, the agent never tried at all. The skill had already told it not to, so it declined on its own and pointed me at the safe path, and the hook never fired. I only saw the message above once I insisted it actually run the command. The skill is the part that changes the agent's mind and the hook is the backstop for when it tries anyway.
I wanted to know how much this actually covers. I tested ten different scenarios to see where the agent might want to reach for a secret and where I might get the skill to trip up when it shouldn't be flagging at all.
STATUS | expected | got | case
-------+----------+-------+-----------------------------------------
OK | deny | deny | Bash: aws secretsmanager get-secret-value
OK | deny | deny | Bash: aws secretsmanager batch-get-secret-value
OK | deny | deny | use_aws: secretsmanager GetSecretValue
OK | deny | deny | run_script: boto3 get_secret_value(...)
OK | deny | deny | Bash: direct SMA daemon curl localhost:2773
OK | deny | deny | Bash: python3 -c inline boto3 get_secret_value
OK | allow | allow | Bash: asm-exec with {{resolve:...}}
OK | allow | allow | Bash: grep that only mentions get-secret-value
OK | allow | allow | use_aws: secretsmanager CreateSecret (a write, not a fetch)
OK | allow | allow | use_aws: s3 ListBuckets
Results are what I expected, well done agent toolkit. It catches the CLI fetch, the batch fetch, the structured API call, a boto3 call buried in a Python script, a curl straight at the local Secrets Manager Agent daemon and an inline python3 -c one-liner. It does not trip on a grep for the bare string get-secret-value, and it leaves writes and unrelated calls alone. The allowlist for read-only tools is narrower than I assumed though. Search for the full phrase aws secretsmanager get-secret-value and you get denied whether you run it through grep, rg or echo, because the CLI pattern is checked before the allowlist ever applies. I tripped that one myself while grepping my own notes for this post.
What sold me is that both layers push the same direction. The skill has already taught the agent what to do instead, so whether it stops itself or gets stopped by the hook, it does not stall. It rewrites its own command to use the safe path.
Resolve without seeing
The safe path is a dynamic reference and a small wrapper called asm-exec.
asm-exec -- curl -sS \
-H "Authorization: Bearer {{resolve:secretsmanager:agent-toolkit-blog-demo/api-token:SecretString:token}}" \
https://api.example.com/data
The command the agent constructs holds a placeholder, not a value. asm-exec scans the arguments, resolves each {{resolve:...}} reference inside its own process, then hands the resolved arguments to the real command. The plaintext never lands in the model's context window or the session transcript.
I pointed it at a local listener that records what it receives, so I could see both sides at once.
Agent-visible command:
asm-exec -- curl -sS -H 'Authorization: Bearer {{resolve:secretsmanager:agent-toolkit-blog-demo/api-token:SecretString:token}}' http://127.0.0.1:8799/data
Response the agent sees:
{"authenticated": true, "msg": "token accepted"}
Header the listener actually received:
Bearer FAKE-token-asdf
The token reached the target. The agent saw a placeholder going in and an API response coming back. It never saw the token.
To be sure I was not fooling myself, I resolved the reference and piped it straight into shasum instead of a command, then compared against the hash of the known fake token.
$ printf %s "FAKE-token-asdf" | shasum -a 256
4953fa5206a3d4a298e6aab9f9c2f74ae50ec2d6f07f374a2df478386674593e -
$ asm-exec -- sh -c 'printf %s "{{resolve:secretsmanager:agent-toolkit-blog-demo/api-token:SecretString:token}}" | shasum -a 256'
4953fa5206a3d4a298e6aab9f9c2f74ae50ec2d6f07f374a2df478386674593e -
Same hash. The real value was resolved, used and never printed.
Then the part that closes the loop. I grepped the agent-visible transcript from both runs for the fake values.
-- unguarded path (aws secretsmanager get-secret-value) --
FAKE-pw-asdf found
FAKE-token-asdf found
-- safe path (asm-exec + {{resolve}}) --
FAKE-pw-asdf not found
FAKE-token-asdf not found
One path writes your secret into a file on disk. The other runs the exact same task without ever writing it down.
How the pieces fit
One thing I like about the design. asm-exec never shells out to aws secretsmanager get-secret-value itself, so the plaintext is never written to a local process's stdout where something could scrape it.
Where this bites
I've talked about how cool this is but I also promised to go over some rough edges. I want to be clear the skill is useful but it is also a best-effort defense. Even AWS says so in the docs. It stops the common leak but it doesn't mean you should stop thinking about security as high priority in your day-to-day actions.
It reduces exposure, it does not prove a negative. The block covers the fetch shapes I threw at it and more. A determined agent or a creative prompt can still find a path the hook does not model. Treat this as one layer. For real production stuff, you still need to keep IAM least-privilege underneath it, keep CloudTrail on and scope who can read which secret. The skill lowers the odds of a dumb leak. It does not turn the agent into a trusted process.
The safe path still calls GetSecretValue. It is not read-free. asm-exec resolves by calling Secrets Manager, it just does it in its own process instead of handing the value back to the agent. Your identity still needs secretsmanager:GetSecretValue on the secret, and the read still shows up in CloudTrail. When it goes through the MCP endpoint the event lists invokedBy, sourceIPAddress and userAgent all as aws-mcp.amazonaws.com, so you can tell an agent read from one you ran yourself. The difference is where the plaintext lands, not whether the API gets called.
asm-exec times out on slow connections. I got Failed to resolve on a fresh network even with valid creds. The read timeout is 10 seconds. A retry cleared it.
Try it on something you were going to build anyway
To make sure you get the automatic block you saw in the demo above, install the plugin in Claude Code with this command: /plugin install aws-core@claude-plugins-official, then restart your session so the hook loads.
From the Agent Toolkit for AWS site, you'll see this one shot command that works for most agents:
aws configure agent-toolkit
This is the easy way to install the toolkit but it does require AWS CLI 2.35+. This method will auto-detect your agents, install skills, and configure the MCP Server. One catch I hit: it does not install the secret-safety skill you saw here. That one ships with the aws-core plugin (it lives under plugins/aws-core/skills).
Repo and docs below:
Start read-only. Use the aws:CalledViaAWSMCP condition key to keep the agent to reads through the MCP Server so people can get used to it, then open up write access once you trust the pattern. Loosening later is easy. Walking back a broad grant is not.
If you have any feedback on this or general thoughts on the toolkit, drop a comment below. Feel free to connect/reach out on LinkedIn.

Top comments (0)