One API Key, Five Agents: How to Stop the Blast Radius

A VentureBeat piece landed last week claiming 69% of enterprises run AI agents that share credentials, and my first thought was: that number feels low. Every agent codebase I've been asked to audit in the last year has the same problem. One OPENAI_API_KEY in a .env file, five worker processes reading it, and a Slack bot, a scraper, an internal RAG endpoint and two cron jobs all authenticating as the same principal downstream. When one of those agents gets prompt-injected into calling an unintended tool, the credential it uses has the union of everything all five workflows are allowed to do. That is the actual attack surface, and it is almost entirely self-inflicted.
I want to walk through how I scope credentials per agent, how I keep tokens short-lived without breaking long-running jobs, and what a usable audit trail looks like when the "user" is a piece of software talking to another piece of software. This is the setup I now use by default on new builds and the retrofit I recommend when I come in as a fractional AI engineer on an existing system.
Why shared keys are worse than shared passwords
A shared human password is bad because you lose non-repudiation. A shared agent credential is worse because it also collapses the permission model. Humans usually have narrow roles. Agents accumulate scopes: one needs Gmail send, another needs S3 read, a third needs write access to your CRM. Put them behind one key and the effective identity of every agent is the union of all those scopes.
Then add the forensic problem. If Anthropic or OpenAI sends you a "we detected anomalous traffic" email at 3 a.m., and your logs say agent-worker made 40,000 calls, you cannot answer the only question that matters: which workflow went rogue? I have watched a team spend two full days bisecting cron schedules to figure out which of their agents was leaking a system prompt into user output. They eventually found it. They also could have found it in ten minutes if the credential had been unique to the workflow.
The rule I've settled on: one credential per agent per environment, with the minimum scope that agent's happy path requires, and nothing else. If two agents need the same downstream permission, they still get two credentials. Cost of managing credentials is near zero when it is automated. Cost of not being able to answer "who did what" during an incident is very high.
How I scope per-agent credentials in practice
Here is the concrete pattern I use on AWS-hosted agent systems. It generalizes to any cloud.
- Every agent gets its own IAM role. Not a shared "agents" role. If you have a research agent, a writer agent, a publisher agent, that is three roles.
- Downstream API keys live in Secrets Manager, one secret per agent per provider. So
content-studio/writer/anthropic-api-keyis a different secret fromcontent-studio/researcher/anthropic-api-key, even though both hit the same Claude endpoint. - The IAM role can only read its own secret. The writer role has
secretsmanager:GetSecretValueon exactly one ARN. If the writer container is compromised and the attacker tries to read the researcher's secret, IAM says no and CloudTrail logs the attempt. - Provider-side, each key gets a distinct project or workspace. In Anthropic and OpenAI you can create workspace-scoped keys with independent spend limits, rate limits and usage dashboards. Use them. This is where the "which agent went rogue" question gets answered without touching your own logs.
- For third-party tools without workspace concepts (a lot of SaaS APIs still fail here), I create a distinct service account per agent and route all calls through a small internal proxy that adds an
X-Agent-Idheader, logged on the proxy side.
The extra work is real but bounded. On the ContentStudio system I built for BizFlowAI, going from three shared credentials to fourteen scoped ones took about a day of Terraform and a rotation runbook. The payoff came six weeks later when a scraping agent started hitting a rate limit and I could see, in one dashboard, exactly which key and exactly which workflow, without correlating anything.
Short-lived tokens without breaking long-running agents
The instinct after reading a piece like the VentureBeat one is to slap short TTLs on everything. This breaks agents. A long-horizon agent doing a multi-step research plan might run for twenty minutes. A batch summarizer might run for two hours. If your token expires mid-run and the agent has no refresh path, you have traded one problem for another.
Here is the split I use:
| Credential type | TTL | Refresh strategy |
|---|---|---|
| LLM provider API keys | 30-90 days, rotated on schedule | Automated rotation via Secrets Manager, agents re-read on 401 |
| Cloud service credentials (S3, DB) | 1 hour (STS assume-role) | SDK auto-refreshes, no agent code needed |
| Third-party SaaS OAuth tokens | Provider default, usually 1 hour | Refresh token stored separately, refresh handled by a sidecar |
| Human-in-the-loop approval tokens | 15 minutes | Not refreshable, agent pauses and re-requests |
The pattern that saves you: agents should never cache a credential in memory beyond a single unit of work. Read the secret, do the call, drop the reference. If the underlying secret rotates, the next unit of work picks up the new one automatically. This makes rotation a non-event. I rotate LLM keys monthly on the systems I run and nobody notices.
The gotcha I hit early: a long-running agent that batched 500 LLM calls in one loop held the same key value for hours. When I rotated the secret, the old key kept working until the loop ended, which meant my "rotated" credential was still live in memory. Fix was to move the secret fetch inside the loop and cache with a 60-second TTL. Negligible latency, real security win.
The audit trail that actually helps during an incident
Most agent logging I see captures the wrong things. It logs the prompt and the completion. Useful for debugging quality. Useless during a security incident. What you need is a structured record of every tool call, every credential use and every external side effect, keyed by a stable agent identity and a per-run trace id.
The minimum schema I put on every agent action:
{
"trace_id": "run_01HXYZ...",
"agent_id": "writer-v3",
"credential_id": "content-studio/writer/anthropic-api-key",
"credential_version": "v14",
"tool": "anthropic.messages.create",
"tool_args_hash": "sha256:...",
"external_resource": "api.anthropic.com",
"outcome": "success",
"tokens_in": 8421,
"tokens_out": 512,
"latency_ms": 3204,
"timestamp": "2026-07-15T09:14:22Z"
}
Notice what is not there: the prompt text. Prompt content goes in a separate, access-controlled store. This log is the security surface. You want it queryable, cheap to store and safe to grant broader access to.
The two queries this schema makes trivial:
- "Between 02:00 and 04:00 UTC, which agent_ids made calls using credential_id X?" This is the "who went rogue" question.
- "For trace_id Y, what is the full sequence of external side effects?" This is the "what did the attacker actually do" question.
I run these on a small ClickHouse instance for the systems that generate real volume, and on Postgres with a good index for smaller ones. Either works. What matters is that the schema is stable and the agent_id is never null.
Prompt injection is a credential problem in disguise
Everyone talks about prompt injection as a model safety problem. It is really a credential design problem. A prompt injection is only dangerous in proportion to what the compromised agent's credential can do. An agent whose key can only call one LLM endpoint and read one S3 prefix is a nuisance if injected. An agent whose key can send email, post to your CRM, delete files and spin up cloud resources is a breach.
The defensive design I use has three layers:
- Capability tokens, not raw credentials, get handed to the agent. The agent asks a small privilege broker: "I need to send this email to this address." The broker checks policy, mints a one-time-use token scoped to that exact action, and the agent uses it. The underlying SMTP credential never touches the agent's process.
- Every write action goes through a typed tool wrapper that validates arguments before the credential is used. If the writer agent's
publish_posttool suddenly gets called with a URL outside the allowed domain list, the wrapper refuses and logs it. The credential is never presented to the downstream API. - A separate "shadow" evaluator watches the tool call stream in near real time and flags patterns that look off: a research agent suddenly calling write tools, a spike in a rarely-used tool, a call with arguments that don't match historical distribution. This catches injection attempts the static policy misses.
None of this is exotic. It is the same principle as least privilege in traditional systems, applied to a world where the "user" is a language model that will do whatever a maliciously crafted document tells it to.
What I'd do if I inherited a system with shared agent keys
Concretely, in order, over a two-week window:
- Day 1-2: inventory. List every agent, every credential, every downstream scope. Do not skip this. Half the shared-key systems I've seen have credentials nobody remembers creating.
- Day 3-4: split the highest-blast-radius credential first. Usually the one with write access to customer-facing systems. Give each agent its own key with its own workspace, cut the shared key's scope.
- Day 5-7: instrument the audit log schema above on every tool call. Backfill nothing. Start clean.
- Day 8-10: move all remaining shared credentials to per-agent secrets. Automate rotation. Test that a rotation doesn't wake anyone up.
- Day 11-14: introduce a privilege broker for the write actions that matter most. You don't need to broker read-only LLM calls on day one. Focus on side effects.
The full "capability tokens for everything" design is a bigger project, usually four to six weeks depending on how many downstream systems you touch. But the first two weeks alone will collapse your blast radius by an order of magnitude, and you will have the forensic capability you didn't have before.
The uncomfortable truth is that most enterprises with shared agent credentials know it. They just haven't been bitten yet. The 69% number will drop when it starts costing people their weekends and their customer trust, not before.
If you are running agents in production and any of this sounds like your setup, I'd rather you fix it before the incident than after. I take on a small number of AI security and architecture engagements each quarter, usually as a fractional engineer or reviewer. If that's useful, reach out via lazar-milicevic.com/#contact, or read more of what I've written about running agents in production on the blog.
Frequently asked questions
Why is sharing one API key across multiple AI agents dangerous?
Sharing a single API key across agents is worse than sharing a human password because it collapses your permission model in addition to destroying non-repudiation. Each agent typically needs different scopes (Gmail send, S3 read, CRM write), and a shared key gives every agent the union of all those permissions, which is exactly what a prompt-injected agent will exploit. It also makes incident response nearly impossible: when a provider flags anomalous traffic, you cannot tell which workflow went rogue. My rule is one credential per agent per environment, scoped to only what that agent's happy path requires.
How do I scope credentials per AI agent on AWS?
I give every agent its own IAM role rather than a shared 'agents' role, and store each downstream API key as a separate secret in Secrets Manager (e.g., content-studio/writer/anthropic-api-key vs content-studio/researcher/anthropic-api-key). Each IAM role gets secretsmanager:GetSecretValue permission on exactly one ARN, so a compromised container cannot read another agent's secret, and CloudTrail logs any attempt. On the provider side, I create workspace-scoped keys in Anthropic or OpenAI with independent spend and rate limits. For SaaS tools without workspace concepts, I use distinct service accounts routed through an internal proxy that adds an X-Agent-Id header.
What TTL should I use for AI agent credentials without breaking long-running jobs?
Different credential types need different TTLs: LLM provider API keys work well at 30-90 days with scheduled rotation, cloud service credentials should use 1-hour STS assume-role tokens with SDK auto-refresh, third-party OAuth tokens follow provider defaults (usually 1 hour) with refresh handled by a sidecar, and human-in-the-loop approval tokens should expire in 15 minutes with no refresh. Blanket short TTLs will break long-horizon agents that run for twenty minutes to two hours. The key is matching TTL to credential type and having an automated refresh path, not just making everything short-lived.
How do I rotate API keys without breaking agents that hold credentials in memory?
Agents should never cache a credential in memory beyond a single unit of work: read the secret, make the call, drop the reference. This way, when the underlying secret rotates, the next unit of work automatically picks up the new value and rotation becomes a non-event. I learned this the hard way when a long-running agent batched 500 LLM calls in one loop and kept the old key alive for hours after rotation. The fix was moving the secret fetch inside the loop with a 60-second cache TTL, which added negligible latency but made rotation actually work.
How much effort does it take to migrate from shared to per-agent credentials?
The migration effort is real but bounded. On a production system I built, going from three shared credentials to fourteen scoped ones took about one day of Terraform work plus writing a rotation runbook. The payoff showed up six weeks later when a scraping agent hit a rate limit and I could identify the exact key and workflow from one dashboard without correlating logs. Compared to spending two days bisecting cron schedules during an incident, which I've seen teams do, the upfront investment is trivial.
Building something hard with AI or automation? I am open to talk.
Get in touch