The most common credential in a production agent is also the worst one: a long-lived API key, pasted into an environment variable, valid forever, and revocable only if you remember it exists. If the agent leaks it — a prompt injection that exfiltrates env vars, a logged request, a debug dump — the blast radius is everything that key can touch, for as long as it takes you to notice. GitGuardian found 29 million secrets on public GitHub in 2025 precisely because keys like this escape.

The fix is not "a better place to hide the key." It's to stop holding a durable secret at all. Give the agent its credential at run time, scoped to one job, expiring in minutes. Which mechanism depends on what the agent is reaching. Here are the three you need.

Pattern 1 — Cloud STS / OIDC (for AWS and GCP roles)#

When the agent needs a cloud role, don't give it an access key. Have it exchange a short-lived identity token for temporary credentials.

On AWS, if the agent runs in GitHub Actions (or any OIDC issuer you trust), it presents that token to STS:

# The runtime already has an OIDC token; trade it for temp creds.
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::123456789012:role/agent-readonly \
  --role-session-name "agent-run-$(date +%s)" \
  --web-identity-token "$OIDC_TOKEN" \
  --duration-seconds 900          # 15 minutes, the minimum

You get back an access key, secret, and session token that die in 15 minutes. There is no standing AWS key in the agent's environment to steal. Scope the role (agent-readonly) to exactly what the job needs.

On GCP, impersonate a narrowly-scoped service account and cap the token's lifetime:

gcloud auth print-access-token \
  --impersonate-service-account=agent@project.iam.gserviceaccount.com \
  --lifetime=900s

Pair this with Workload Identity Federation so the agent authenticates from its platform identity (a GKE service account, a GitHub OIDC token) with no downloaded key file at all.

Pattern 2 — Vault dynamic secrets (for databases and third-party APIs)#

When the agent needs a database or an API behind a secrets engine, let Vault mint a credential that never existed before the request. Configure the engine once:

vault write database/roles/agent-analytics \
  db_name=appdb \
  creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
     GRANT SELECT ON analytics.* TO \"{{name}}\";" \
  default_ttl=15m max_ttl=1h

Then, at run time, the agent asks for credentials and gets a brand-new database user scoped to SELECT on one schema, leased for 15 minutes:

vault read database/creds/agent-analytics
# → username: v-agent-analytics-xY9k...   password: ...   lease_duration: 900

When the lease expires — or you revoke it the instant an agent misbehaves — Vault deletes the underlying user. Nothing durable remains.

Pattern 3 — A token broker (for provider keys that don't expire)#

Anthropic and OpenAI keys can't be made short-lived at the provider, so don't hand them to the agent at all. Put a small broker in front: it holds the real key, authenticates the agent by its platform identity, and either mints a per-run scoped token or proxies the call so the agent never sees the upstream key.

# broker.py — the agent presents its identity, not a secret.
@app.post("/llm")
def call(req: Request, agent=Depends(verify_agent_identity)):
    if not policy.allows(agent, req.model, req.max_tokens):
        raise HTTPException(403, "outside this agent's scope")
    # The real provider key lives here, never in the agent.
    return anthropic.messages.create(**req.body, api_key=SECRET)

verify_agent_identity checks the agent's OIDC token or service-account identity — the same platform identity from Patterns 1 and 2. The agent holds nothing durable; the broker holds one key you rotate on a schedule as defense in depth.

The rule underneath all three#

Every pattern here follows one principle: the agent proves who it is, then receives a credential scoped to one job that expires on its own. Its identity comes from the platform it runs on — a Kubernetes service account, a GitHub OIDC token, an instance role — never from another static secret one hop upstream.

Do this and the leaked-key incident stops being a breach and becomes a shrug: the credential in the logs expired fifteen minutes ago. That's the whole point of governing a non-human identity instead of just hiding it — and it's move four of the founder playbook, made concrete.