How to manage secrets for AI agents
Learning how to manage secrets for AI agents is different from managing secrets for a normal service, because an agent reads its own environment, runs shell commands, and often writes files and logs on your behalf. A web server keeps your API key to itself. An agent might echo $GITHUB_TOKEN into a log, paste it into a pull request, or hand it to a tool you did not anticipate. That is the whole problem in one sentence: the thing holding your secrets is also the thing improvising with them.
This tutorial covers where secrets leak, how secret managers and scoping contain the damage, and how rotation plus audit turn a leaked key from an incident into a shrug. Everything here assumes you already have an agent running; if not, start with host an AI agent on a server.
What counts as a secret here
For an AI agent the sensitive material usually includes:
Model provider API keys (Anthropic, OpenAI) that map directly to a spend limit.
Source-control tokens (
GITHUB_TOKEN, deploy keys) that grant write access to code.Cloud credentials (AWS, GCP) the agent uses to provision or inspect infrastructure.
Third-party tool tokens: Slack, Jira, a database connection string.
Every one of these is both an authorization and a liability. The more capable the agent, the more of them it holds at once, which is why scoping and rotation matter more here than for a typical service.
Why environment variables leak
The most common way to hand a secret to an agent is the most dangerous one: an environment variable baked into the launch command or the image. It is convenient and it is everywhere in tutorials. Here is the leaky pattern:
# Leaky: the token is in your shell history, the process list,
# the container spec, and probably a log line somewhere.
docker run -e GITHUB_TOKEN=ghp_realTokenValue123 my-agent:latest
# Equally leaky: baked into an image layer, shipped to a registry.
# ENV GITHUB_TOKEN=ghp_realTokenValue123 (never do this in a Dockerfile)# Leaky: the token is in your shell history, the process list,
# the container spec, and probably a log line somewhere.
docker run -e GITHUB_TOKEN=ghp_realTokenValue123 my-agent:latest
# Equally leaky: baked into an image layer, shipped to a registry.
# ENV GITHUB_TOKEN=ghp_realTokenValue123 (never do this in a Dockerfile)An --env GITHUB_TOKEN=ghp_xxx value, or a hardcoded env line, escapes through at least five channels:
Shell history.
~/.bash_historynow contains your production token in plaintext.The process list. Anyone who can run
ps auxeon the host sees the environment.The container spec.
kubectl describe podanddocker inspectprint env vars in full.Image layers. A token baked with
ENVorARGships to every registry pull, forever, even if you "remove" it in a later layer.The agent's own output. Agents summarize their environment, echo variables while debugging, and write logs. A single
envcommand in a tool call can dump every secret into a transcript you later share.
The last one is unique to agents and it is the one people forget. You can lock down the host perfectly and still lose the key because the agent pasted it into a GitHub comment while "explaining what it did."
Mounted secrets: better, still not free
The standard improvement is to mount secrets as files instead of environment variables, and reference them rather than inline them. In Kubernetes that looks like:
apiVersion: v1
kind: Secret
metadata:
name: agent-credentials
type: Opaque
stringData:
GITHUB_TOKEN: ghp_realTokenValue123
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: openclaw-agent
spec:
template:
spec:
containers:
- name: agent
image: ghcr.io/openclaw/openclaw:latest
volumeMounts:
- name: creds
mountPath: /var/run/secrets/agent
readOnly: true
volumes:
- name: creds
secret:
secretName: agent-credentialsapiVersion: v1
kind: Secret
metadata:
name: agent-credentials
type: Opaque
stringData:
GITHUB_TOKEN: ghp_realTokenValue123
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: openclaw-agent
spec:
template:
spec:
containers:
- name: agent
image: ghcr.io/openclaw/openclaw:latest
volumeMounts:
- name: creds
mountPath: /var/run/secrets/agent
readOnly: true
volumes:
- name: creds
secret:
secretName: agent-credentialsNow the agent reads the token from /var/run/secrets/agent/GITHUB_TOKEN at the moment it needs it, the value is not in the process environment by default, and it never touches your shell history or an image layer. This is strictly better. But note what it does not fix: the raw value still sits in the Kubernetes Secret object (base64, not encrypted, unless you enable encryption at rest), and a curious agent can still cat the file and print it. Mounting reduces the number of leak channels. It does not make the secret un-leakable.
Secret managers and scoping
Mounting solves delivery. It does not solve lifecycle: who can read this, how do we rotate it, and where is the record. That is what a dedicated secret manager is for. Three worth naming:
HashiCorp Vault is the heavyweight. Dynamic secrets are its standout feature: instead of a long-lived database password, an agent requests a credential that Vault generates on demand and revokes after a TTL. Self-hosted Vault is operationally heavy; HCP Vault (managed) starts around $0.03/hour for the base cluster plus usage.
Doppler is the developer-friendly option. It syncs secrets into environments and CI with a clean CLI (
doppler run -- your-agent), good scoping per project and config, and a free tier for small teams, with paid plans around $18 per user per month for the team tier.AWS Secrets Manager is the default if you already live in AWS. It charges about $0.40 per secret per month plus $0.05 per 10,000 API calls, integrates with IAM for scoping, and supports scheduled rotation via Lambda.
The common thread is that the agent never holds a static secret it can leak permanently. It holds a short-lived token, or it fetches the secret at runtime from a system that logs the access and can revoke it.
Scope by the principle of least privilege
Scoping is the control that limits blast radius. An agent that only needs to read one repository should not hold an org-wide token. Concretely:
Issue per-agent or per-task credentials, not one shared key for the fleet. When you are scaling AI agents to dozens of instances, a single shared key means one leak burns everyone and you cannot revoke selectively.
Prefer fine-grained tokens. GitHub fine-grained PATs, scoped to one repo with read-only contents, beat a classic
repo-scope token that can push anywhere.Separate read from write. Give the research agent read-only credentials and reserve write tokens for the one agent that actually opens pull requests.
Match secret visibility to human roles. Not every developer on the team should see every production credential.
That last point is where platform-level scoping earns its keep. Sokko keeps your API keys in secure storage and hands them to each agent at launch, never baking them into an image. One org's secrets are walled off from every other org's, so one team's agents can never read another's. Admins rotate and revoke, and the raw value stays hidden in the dashboard, so a dev launching an agent can use the credentials they are allowed to use without ever seeing the key itself. It maps directly onto least privilege instead of fighting it. For how those roles fit together, see RBAC and team management.
Rotation and audit
A secret you cannot rotate is a secret you cannot recover from. Since agents genuinely do leak keys, the realistic posture is not "never leak" but "rotate fast and know who touched what."
Rotation should be routine, not an emergency. Set a rotation schedule (30 to 90 days for standard keys, shorter for high-value ones) and make sure a rotation does not require redeploying every agent. This is another argument for fetch-at-runtime: if agents read the current value from Vault or a platform secret store on each run, rotating is a single update, not a fleet-wide restart. AWS Secrets Manager can automate the rotation itself on a schedule; Vault's dynamic secrets rotate by expiring.
When a key leaks, the response is:
Revoke the leaked credential immediately (this is why you scoped it small).
Issue a replacement to the affected agents only.
Check the audit log for what that credential did between leak and revocation.
Audit is the part teams skip until they need it and cannot reconstruct it. You want a record of who created, read, rotated, or revoked each secret, with a timestamp. When an agent does something surprising with a production credential, the audit log is how you answer "was this us, and when did it start." Sokko pairs admin-driven rotation and revocation with the RBAC roles above for exactly this reason: every change to a secret is made by a named admin, not an anonymous token.
Here is how the storage options compare on the three properties that matter:
| Storage method | Leak risk | Rotation | Audit trail |
|---|---|---|---|
| Env var in launch command | High: shell history, process list, agent output | Manual, requires restart | None |
.env file in repo/image | Very high: committed, shipped in layers | Manual, easy to forget | None (or git blame at best) |
| Kubernetes Secret (mounted) | Medium: base64 at rest, readable in-pod | Manual, restart or reload | Limited (API server audit if enabled) |
| Secret manager (Vault, Doppler, AWS) | Low: fetched at runtime, short-lived | Automated / dynamic | Full access log |
| Platform secret store (Sokko) | Low: scoped by role, value hidden from devs | Admin-driven | Role-scoped: every change is tied to a named admin |
The pattern is clear: leak risk drops as you move from static env vars toward runtime-fetched, role-scoped secrets, and the audit trail improves at the same time.
A secrets checklist
Before you point an agent at a production credential, run down this list:
No secret is baked into an image layer or a
DockerfileENVline.No secret is passed as a literal on a command line you keep in history.
Secrets are mounted or fetched at runtime, not held in the process environment where the agent can dump them with
env.Every credential is scoped to the narrowest resource and permission the task needs.
Read and write credentials are separate identities.
There is a rotation schedule, and rotating does not require a fleet redeploy.
An audit log records who created, read, rotated, and revoked each secret.
Developers cannot see production secrets they do not need; role controls enforce it.
Egress is restricted so a leaked key cannot be exfiltrated to an arbitrary host.
If you can check every box, a leaked key becomes a rotation task instead of a breach.
The bottom line
Knowing how to manage secrets for AI agents comes down to accepting that the agent will, eventually, do something clumsy with a credential, and designing so that clumsiness is cheap. Keep secrets out of environment variables and image layers. Fetch them at runtime from a real secret manager. Scope every credential to the smallest job. Rotate on a schedule and keep an audit log so revocation is fast and attribution is possible. Vault, Doppler, and AWS Secrets Manager each give you most of that; a platform like Sokko folds the secure storage and per-agent scoping into the same place you launch the agents. Either way, the goal is the same: make a leaked key a five-minute rotation, not a Monday-morning postmortem.