What Are You Doing With Your Secrets? The #1 Risk Nobody Talks About With AI Agents
What Are You Doing With Your Secrets? The #1 Risk Nobody Talks About With AI Agents
I’m a 25-year enterprise security professional. I’ve built IAM frameworks for large financial institutions. I know better.
And this week, I found secrets being exposed in AI session output, an API key being injected into an agent subprocess that was supposed to be using a subscription, and credentials sitting in plain text in a Windows environment variable.
In my own project. On my own machine. With guardrails I personally designed.
Here’s what happened, why it happens, and what you actually need to do about it.
The Problem With AI Agents and Secrets
When you run AI agents — whether that’s Claude Code, Paperclip, or anything else that executes code and calls APIs on your behalf — you are handing a reasoning engine a set of tools and saying “go figure it out.”
The model doesn’t have a security conscience. It has an objective. And it will do whatever it takes to achieve that objective. If it needs a credential to complete a task, it will find the credential. If you haven’t told it where to look, it will look everywhere. If it finds one in an environment variable, it will use it. If it finds one in a .env file, it will use it. If the only path to completing the task is to print a secret to the screen so a human can confirm it, a model that hasn’t been explicitly constrained will print it.
You might have a soul file that says “never hardcode credentials.” You might have system prompts that say “don’t expose secrets.” But if a debugging session gets messy, if a session context is long, if the agent is spinning and trying things — guardrails drift. The model focuses on the problem. The guardrails become background noise.
I watched this happen to myself.
What We Found This Week
Incident 1: API Key Injected Into Agent Subprocess
After upgrading Paperclip from v0.2.7 to v0.3.1, our agents started failing with “Credit balance is too low.” We had been running on a Claude Pro/Max subscription — no API charges — for weeks. Suddenly we were burning through API credits.
Root cause: Paperclip was injecting ANTHROPIC_API_KEY as an environment variable into the Claude subprocess. The model picked it up automatically. OAuth subscription auth was bypassed entirely. Every agent run was hitting the Anthropic API directly, billing against the key.
The .env file on the EC2 instance had the key written into it by the Terraform user_data.sh bootstrap script — line 235, quietly writing the key every time the instance provisioned. We hadn’t noticed. The agents were designed to use OAuth. But the key was there, it was accessible, and the model used it.
Fix: Wrapper script at /usr/local/bin/claude-sub that unsets ANTHROPIC_API_KEY before executing claude. EC2 .env file stripped of the key. The model uses what it’s given — so stop giving it the wrong thing.
Incident 2: Running as Root Without Knowing It
During a credential rotation session, we deleted and recreated the terraform-deployer IAM user mid-session. New credentials weren’t wired up. Local AWS CLI fell back to the SSO session — which was authenticated as the management account root.
We ran infrastructure commands as root for longer than we should have, without realizing it. Nothing catastrophic happened. But in security, “nothing catastrophic happened” is not the same as “this was acceptable.”
Fix: We did an immediate security review. MFA confirmed on root. Root has no access keys. Root is locked from day-to-day work. But we didn’t stop there — this incident triggered a broader audit of how both Jared and the agents access AWS. We’re reviewing every access pattern, every tool, every agent workflow. IAM Identity Center is the next step — named users, scoped permission sets, short-lived STS tokens. Beyond that, we’re actively looking at Just-In-Time (JIT) access and Zero Standing Privilege (ZSP) models. The question isn’t just “how do we fix the root issue” — it’s “what access model actually makes sense for a company where humans and AI agents share the same infrastructure?”
Incident 3: Keys Appearing in Session Output
During a debugging session, AWS credentials appeared in Claude Code tool output. Not because I asked for them. Because I was clicking through a fast-moving debugging loop and didn’t catch the moment where the agent printed a value to confirm it was working.
This is the click-next-next-next problem. AI moves fast. You’re focused on the outcome. You approve a tool use without reading it carefully. The next thing you know, a secret is sitting in a session log.
Fix: Hard rule — never confirm a secret works by printing its value. Print length. Print the last 4 characters. Print an HTTP status code. Never the value itself. This rule is now in every agent soul file and every debugging SOP.
What a Real Secrets Strategy Looks Like
You need three things: a place to store secrets, a way to rotate them, and guardrails that actually hold.
1. One Source of Truth: SSM Parameter Store
Every secret lives in AWS SSM Parameter Store as a SecureString. Not in .env files. Not in environment variables on developer machines. Not in hardcoded config files. Not in chat history.
# Retrieve a secret by name — never by value
aws ssm get-parameter \
--name "/paperclip-app/anthropic/api-key" \
--with-decryption \
--query Parameter.Value \
--output text
The naming convention matters:
/{project}/{service}/{key-type}
/paperclip-app/anthropic/api-key
/paperclip-app/github/pat
/abt/linear/api-key
/abt/replicate/api-token
Bootstrap credentials (the keys you need to reach SSM) live in GitHub Secrets only — AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, GH_PAT. Everything else comes from SSM at runtime.
2. A Key Inventory
You can’t rotate what you can’t find. Maintain a live inventory of every secret:
| Key | Location | Last Rotated | Owner |
|---|---|---|---|
| ANTHROPIC_API_KEY | SSM /paperclip-app/anthropic/api-key | 2026-03-28 | Paperclip |
| GITHUB_PAT | SSM /paperclip-app/github/pat | 2026-03-24 | jcalone-abt |
| LINEAR_API_KEY | SSM /abt/linear/api-key | — | needs migration |
If a key appears anywhere other than its listed location, that’s a finding. Fix it immediately.
3. Know How to Rotate in Under 5 Minutes
When a secret gets exposed — and at some point, it will — you need to rotate it before the attacker does anything with it. That means you practice this before it happens.
For every key in your inventory, you should be able to answer:
- Where do I go to revoke the old key?
- What breaks immediately when I revoke it?
- How do I generate the new key?
- How do I push the new key to SSM?
- How do I verify everything is working?
If you can’t answer those five questions for every key in your inventory, you are not ready.
The Bigger Question Nobody Is Asking
I’ve been in enterprise security for 25 years. I’ve implemented PAM platforms, IAM governance frameworks, and secrets vaults for Fortune 500 companies. And I’m still finding credential exposure issues in my own AI project.
Which makes me wonder: what is everyone else doing?
Most people building with AI agents are not security engineers. They’re developers, founders, product people. They’re moving fast. They’re clicking “ok” on agent tool approvals without reading what’s in them. They’re pasting API keys into chat because it’s the fastest way to get something working. They have no rotation process because nothing has broken yet.
The enterprise answer is tooling: PAM platforms, dynamic secrets, JIT access, zero standing privilege. A few worth knowing:
CyberArk Secrets Manager is the heavyweight here — the dominant enterprise PAM platform. It vaults credentials centrally, enforces least-privilege, provides full audit trails, and integrates with just about every CI/CD and cloud platform. If you’ve worked in a large financial institution or regulated industry, you’ve probably touched CyberArk. It’s comprehensive, battle-tested, and priced accordingly.
HashiCorp Vault is the developer-native alternative — open source core, strong API, dynamic secrets (it can generate a database credential that expires in 15 minutes and never existed as a static value). Vault is what a lot of engineering teams reach for when they want enterprise-grade secrets without the enterprise price tag.
Britive takes a different angle — zero standing privileges. Instead of storing secrets, it eliminates long-lived credentials entirely. Access is granted JIT, scoped precisely, and revoked automatically. Built specifically for cloud and multi-cloud environments where the old PAM model doesn’t translate well.
All three exist specifically to eliminate long-lived credentials and ensure that access is granted only for the duration it’s needed — and revoked automatically after.
But here’s the honest question: is enterprise PAM tooling the right answer for AI-native teams, or do we need something new?
Native AWS gets you most of the way there today — SSM for secrets, IAM Identity Center for human identity, STS for short-lived tokens, CloudTrail for audit. JIT and ZSP are achievable without a third-party platform if you’re disciplined. But the gap is AI agents. IAM doesn’t know the difference between a human and an agent. There’s no concept of “this session was initiated by Claude, not Jared.” The audit trail doesn’t tell you which tool approval caused the credential exposure.
That’s the real unsolved problem. And I don’t have a clean answer yet — just a commitment to keep tightening the model until one emerges.
If you’re building with AI agents and you’ve figured out a secrets model that actually holds, I want to hear it.
One Practical Fix: Make the Health Check Part of Your Dev Ritual
One of the concrete things we built after this week: a dev session startup script that runs before any work begins.
Every morning when we sit down to work with Claude or open a session with our agents, we run dev-session.ps1. It does four things in sequence:
- Confirms AWS auth — checks
sts get-caller-identityand warns explicitly if you’re running as root - Opens the Paperclip port forward — SSM tunnel to the EC2 instance, auto-reconnects if it drops
- Checks the repo state — current branch, unpushed commits, dirty files
- Runs a secrets health check — hits every SSM parameter in the inventory and prints OK or MISSING
That last step is the one that matters for secrets. The check-secrets.ps1 script loops through every parameter path in the inventory and confirms it exists and is readable. It never prints values — just the name and status:
[ Secrets Health Check ]
Paperclip admin password OK
Paperclip Anthropic key OK
Paperclip Better Auth secret OK
Paperclip GitHub PAT OK
Claude Code API key OK
Replicate API token OK
Test bot Anthropic key MISSING
...
One or more secrets missing. See SECRETS-RUNBOOK.md to fix.
The point isn’t to verify the secret works. It’s to confirm it exists before you start a session where an agent might need it. If something shows MISSING before the session starts, you fix it before the session starts — not after an agent has already tried to find a workaround.
It also catches drift. If a key gets deleted during a rotation, or a new service got added to the inventory but the SSM parameter was never created, it surfaces at session start instead of mid-task when an agent is spinning trying to figure out why it can’t authenticate.
The root detection piece is also important. If the AWS auth check returns an ARN with “root” in it, it prints a yellow warning. You can still proceed, but you see it. That one check alone would have caught the incident described above before any infrastructure commands ran.
Small automation. But it changes the security posture of every working session.
The Guardrail That Actually Works
After everything this week, here’s the rule that holds across every context — development session, agent run, debugging loop, whatever:
If a secret needs to appear somewhere to make something work, that’s a design flaw. Fix the design.
Keys injected as environment variables? Wrapper script. Keys in .env files? Pull from SSM. Keys printed to confirm they work? Print the status code instead.
The model does what it needs to do to complete the task. Your job as the human in the loop is to make sure the design never puts the model in a position where using a secret insecurely is the easy path.
Make the secure path the only path. Everything else is just hoping the guardrails hold.
We’re building ABT in public, which means we document the failures alongside the wins. Secrets management is unglamorous, but getting it wrong is how you lose the whole thing.
Comments