How to Keep API Keys Comot from AI Agents
AI agent fit leak API key with one tool call. Use scoped, short-lived tokens behind credential gateway, instead of real keys inside environment.
Wetín e mean to keep secrets comot from AI agents
An AI agent na normal Linux process wey dey run commands. Every environment variable wey that process get, the code wey e run fit read am. So, if API key dey inside agent environment, agent fit send the key go any host wey e fit reach. To keep secrets comot from agent mean say you give am a handle instead of the key: a short lived scoped token, or placeholder wey another thing go replace with the real value for network boundary.
This no be story about model turning hostile. The mechanism dey more ordinary. Agent dey read web page, README, or issue comment wey get instructions, and e follow dem, because language model no fit tell difference between text wey you write and text wey e fetch. Na prompt injection be this. Once e happen, exactly one thing determine how much damage fit happen: wetin the process fit read. If you never set boundary yet, how to run coding agent safely for server explain the isolation levels wey this guide dey build on.
The threat model for simple terms
Run this as the user wey your agent dey run as.
tr '\0' '\n' < /proc/self/environ | grep -iE 'key|token|secret|password'Every line wey e print na one HTTP request away from stranger server. Now check wetin dey for disk near the agent.
grep -rIl --exclude-dir=.git -e 'API_KEY' -e 'SECRET' ~/projects
find ~ -maxdepth 3 -name '.env' -o -name 'credentials' -o -name '*.pem'Agent wey get shell no need clever exploit to move that data comot. Four ordinary paths fit do the work, and all four go look like normal work for the log:
- One outbound
curlorfetchgo any host, with the value for query string. - One
git commitandgit pushgo repository wey agent fit write to. - Package install script, wey dey run arbitrary code as agent user.
- DNS lookup of hostname wey contain the value, wey still go comot even when HTTP egress dey blocked.
You no fit review your way out of this. The fix na to make sure say nothing valuable dey within reach.
Secret wey dey inside working tree na secret wey dey inside context window
Agent dey read files. If .env file dey inside repository wey e dey work on, e go read am. Once e read am, e don enter context window. This means say e dey inside transcript, any log wey you keep, and anything wey agent write next.
Before, when key dey inside tree wey agent dey work on:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingAfter you move the file comot from where agent fit reach:
sudo install -d -m 750 -o root -g agent-review /etc/agent-review
sudo install -m 640 -o root -g agent-review ~/projects/billing/.env /etc/agent-review/billing.env
rm ~/projects/billing/.envUser of the agent no fit open the file again, because working tree no longer get am. Deny rules for agent own config na second protection layer, no be the first one. Claude Code dey read permission rules from .claude/settings.json inside project:
{
"permissions": {
"deny": ["Read(./.env)", "Read(./secrets/**)", "Read(./**/*.pem)"]
}
}This one stop honest mistake where agent open file while e dey explore. E no stop injected instruction from running base64 .env, because that one na shell command, no be file read. Whether dem go ask you before that command run depend on session permission mode. And auto mode go become the Claude Code default for August 2026, so server wey you no dey monitor go run more of those commands without prompt. This same limit apply to anything wey shape agent habits instead of permissions: skill wey make agent stick to the smallest change wey work fit stop one run from entering files wey e no get reason to open, but na still advice wey person fit persuade the model to ignore. Treat config as guardrail, and filesystem permission as wall. This same separation apply inside containers: env files and secrets for Docker Compose cover the version of this problem one layer below.
Give each agent its own unprivileged user
If agent dey run as you, e go inherit your SSH keys, your cloud credentials, and your shell history. Separate user need only one command, and e remove all those access.
sudo adduser --disabled-password --gecos "" agent-review
sudo chmod 700 /home/agent-review
sudo -u agent-review cat ~/.ssh/id_ed25519The last line suppose fail with cat: /home/you/.ssh/id_ed25519: Permission denied. If e print a key instead, your home directory dey readable by group or everybody, and chmod 700 ~ go fix am. No add the agent user to sudo, and no give am any NOPASSWD rule wey wider than the one command e truly need. Least privilege users for VPS explain the group and sudoers details. Remember this separation after you run more than one session for the box, because one Claude Code session fit send text direct go another one, and anything wey the first session get fit cross that channel inside one message.
One more boundary make sense to add for cloud VPS. Instance metadata service dey answer for one fixed link local address, and e often dey give role credentials to anything wey ask.
sudo iptables -A OUTPUT -m owner --uid-owner agent-review -d 169.254.169.254 -j REJECTCheck am from the agent side. sudo -u agent-review curl -s --max-time 3 http://169.254.169.254/ suppose print nothing and exit non zero, because packet go reject before e comot the box.
Put the credential for boundary
The pattern wey really solve this na credential injection. The agent no dey hold real key at any time. E send request through local gateway, and the gateway replace placeholder with the real secret as e dey go out. The secret dey inside gateway storage, for another process, and different user own am.
OneCLI na one open source implementation of this pattern. Apache-2.0 license cover am, and e dey run as container beside the agent. As of July 2026, the project document this setup:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitThe dashboard dey listen on port 10254, and the gateway dey listen on 10255. You store the real credential one time. Then you give each agent placeholder value instead of the key, plus its own scoped access token. The agent send this token inside a Proxy-Authorization header. The gateway match the outbound request by host and path, decrypt the matching credential, then substitute am. The agent environment no hold anything wey person fit steal.
The important thing here no be the encryption. Na say the question “which credential this agent use, and when?” don become log query. You read one audit trail instead of guessing which one of six environments get copy of the key.
Secret go the process, no be environment
If you dey run the agent under systemd, you no need environment variables at all. LoadCredential= go put the secret for private directory wey only that service fit read, expose am as %d for the unit file and as $CREDENTIALS_DIRECTORY inside the process. The value no go ever show for /proc/<pid>/environ, so ps eww no fit display am, and the directory go disappear when the service stop.
First encrypt the credential to the machine. These commands come from systemd documentation and dem work for systemd 250 or newer. This one cover Ubuntu 24.04 and Debian 13:
echo -n 'sk-example-value' > /tmp/plain.txt
sudo systemd-creds encrypt --name=api_key /tmp/plain.txt /etc/credstore/api_key.cred
shred -u /tmp/plain.txt
sudo systemd-run -P --wait -p LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred \
systemd-creds cat api_keyThe last command go print sk-example-value. This prove say the encrypted file fit decrypt for this host. Then reference am from the unit:
[Service]
User=agent-review
LoadCredentialEncrypted=api_key:/etc/credstore/api_key.cred
Environment=AGENT_KEY_FILE=%d/api_key
ExecStart=/usr/local/bin/agent-workerYour agent code go open the file for $AGENT_KEY_FILE when e need the value. File read na momentary thing. Environment variable go remain for the whole life of the process, including every child process wey e start.
Prefer tokens wey get short lifetime over keys wey get long lifetime
Key wey no dey expire still dey valid whenever e show up months later for log or transcript. If the service get session token, use the session token and set the shortest lifetime wey the work fit allow.
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/agent-readonly \
--role-session-name agent-review \
--duration-seconds 900Fifteen minutes na the minimum wey AWS STS (security token service) dey accept, and e usually enough for one agent task. For GitHub, give the agent user its own gh login with fine grained token wey scope to the single repository wey e dey work on, so gh auth token inside that session go return something wey no fit touch anything else. First scope am by resource, then by time.
Verify, then keep verifying
Check three things after any change to an agent setup. Run dem as the agent user, no be yourself.
sudo -u agent-review env | grep -iE 'key|token|secret'
sudo -u agent-review ls -la /home/you/ 2>&1 | head -3
sudo -u agent-review curl -s -o /dev/null -w '%{http_code}\n' --max-time 5 https://api.github.com/userThe first one suppose print nothing at all. The second one suppose print ls: cannot open directory '/home/you/': Permission denied. The third one show which identity the agent network path dey present. Na this question the gateway pattern dey answer: 401 mean the agent no carry any GitHub credential of its own, while 200 mean e carry one. So you suppose know which token e be. If you run agents unattended, how to control AI agent costs on a VPS explain the budget limits wey go pair with these access limits.
FAQ
I fit just trust the model make e no leak my keys?
No, because for this threat model, na not the model be the attacker. The agent dey read text from web pages, repositories, and issue trackers, and that text fit contain instructions. The model no get reliable way to know which one na your instructions and which one na text e fetch. Any control wey depend on the model to choose correctly go fail the first time an injected instruction sound convincing. So, the control suppose dey inside the operating system or the network instead.
Environment variables really bad for agent secrets?
Dem bad for one specific reason: dem dey inherit. Every child process wey the agent start gets a copy, including build script, test runner, and any package install hook. The same user fit also read the variables through /proc/<pid>/environ, so anything wey the agent run fit read dem without the agent passing dem along. If you read a file only when you need am, with LoadCredential= or a gateway, you limit the exposure to that moment.
If I put secrets for vault, e solve this by itself?
Only partly. Vault solves the storage problem. If you self host the vault, you need harden am separately, because people usually breach a Vaultwarden server through its admin token or backup file, not through the encrypted items wey e hold. E no solve the last step, where something pulls the secret from the vault and gives am to the agent as an environment variable. That one puts you back for the same problem. The important thing na who performs the substitution. If the agent fetch the secret, the agent get the secret. If gateway or init system performs the substitution outside the agent process, the agent never hold am.
How I fit know whether agent don leak something already?
Most times, you no fit know after the event, and na this be the reason to use gateway. Without gateway, your evidence dey scattered across shell history, the agent transcript, and outbound connection logs wey you probably no dey keep. With credential gateway, every credential use na one line with agent identity and timestamp. If you suspect say leak happen, rotate the key first and investigate after. Rotation cheap, but certainty no be.
Wetin be the minimum wey I suppose do today?
Move every .env file comot from the directories wey your agents dey work in, and create one unprivileged user for each agent. These two changes fit take about ten minutes and close the commonest path: agent reading credential file wey no reason to dey beside the code. Gateway and short lived tokens na the next step, no be the first one. This same starting point apply to any agent runtime, including running an autonomous agent safely on a VPS.