How to Keep Secrets Comot From AI Agents
AI agent fit leak API keys with one tool call. Use scoped, short-lived tokens behind a credential gateway, instead of exposing real keys to the process.
Wetin e mean to keep secrets comot from AI agents
An AI agent na normal Linux process wey dey run commands. Any environment variable wey that process get, the code wey e run fit read am. So, if API key dey inside the agent environment, the agent fit send the key go any host wey e fit reach. To keep secrets comot from the agent, 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 the network boundary.
This one no be story about model wey turn hostile. The mechanism simple pass that. Agent fit read web page, README, or issue comment wey get instructions, and e go follow dem, because language model no dey see difference between text wey you write and text wey e fetch. Na prompt injection be this. Once e happen, the damage get limit based on only one thing: 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 top.
The threat model wey plain terms
Run this as di 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 di 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 dat data comot. Four ordinary paths fit do di work, and all four go look like normal work for di log:
- An outbound
curlorfetchgo any host, with di value inside query string. - A
git commitandgit pushgo repository wey di agent fit write to. - Package install script, wey dey run arbitrary code as di agent user.
- DNS lookup of hostname wey contain di value, wey still go comot even when HTTP egress dey blocked.
You no fit review your way out of dis. Di fix na to make sure say nothing valuable dey within reach.
Secret wey dey inside working tree na secret wey dey context window
Agent dey read files. Agent go read any .env file wey dey repository wey e dey work inside. Once e read am, e dey inside context window. This mean say e dey transcript, any log wey you keep, and anything wey agent write next.
Before, when key dey inside tree wey agent dey work in:
cd ~/projects/billing
cat .env
# STRIPE_SECRET_KEY=sk_live_...
# DATABASE_URL=postgres://app:hunter2@db.internal:5432/billingAfter, when you move 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/.envAgent user no fit open the file again, because working tree no get am anymore. Deny rules for agent own config na second 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 stops honest mistake wey agent fit make when e dey explore and open file. But e no stop injected instruction from running base64 .env, because na shell command be that, no be file read. Treat config as guardrail, and filesystem permission as the wall. This same separation dey apply inside containers too: env files and secrets inside Docker Compose dey cover the version of this problem wey dey one layer down.
Give every agent its own unprivileged user
If agent dey run as you, e inherit your SSH keys, your cloud credentials, and your shell history. Separate user na just one command cost, and e remove all of dem.
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 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 NOPASSWD rule wey broad pass the one command wey e really need. Least privilege users for VPS explain the group and sudoers details.
One more boundary dey worth adding for cloud VPS. Instance metadata service dey answer for 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 dey rejected before e comot from the box.
Boundary wey credential go enter
The pattern wey really solve this na credential injection. Agent no dey hold real key at any time. E dey send request through local gateway, and gateway dey replace placeholder with real secret as request dey go out. Secret dey inside gateway storage, for another process, and different user own am.
OneCLI na one open source implementation of this method. Apache-2.0 license cover am, and e dey run as container beside agent. As of July 2026, project document this setup:
git clone https://github.com/onecli/onecli.git
cd onecli
docker compose -f docker/docker-compose.yml up -d --waitDashboard dey listen on port 10254, and gateway dey listen on 10255. You store real credential once. After that, you give each agent placeholder value instead of the key, plus e own scoped access token. Agent dey send the token inside Proxy-Authorization header. Gateway dey match outbound request by host and path, decrypt the credential wey match, and replace the placeholder with am. Agent environment no hold anything wey person fit steal.
The main value no be the encryption. Na say the question “which credential this agent use, and when?” don turn to log query. You read one audit trail instead of guessing which of six environments get copy of the key.
Give the secret to the process, no be environment
If you dey run the agent under systemd, you no need environment variables at all. LoadCredential= dey put the secret for one private directory wey na only that service fit read, e dey show as %d for the unit file and as $CREDENTIALS_DIRECTORY inside the process. The value no dey ever appear for /proc/<pid>/environ, so ps eww no fit show am, and the directory dey disappear when the service stop.
Encrypt the credential to the machine first. These commands come from the systemd documentation and dem dey work for systemd 250 or newer, wey 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 dey 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 one moment. Environment variable dey last for the whole life of the process, for every child wey e start.
Prefer tokens wey dey expire quick pass keys wey dey last long
Key wey no dey ever expire still dey valid whenever e show up later for log or transcript, even after months. 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 im own gh login with a 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. Scope by resource first, then by time.
Verify, then keep verifying
Anytime you change agent setup, you fit run these three checks. 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. The second one suppose print ls: cannot open directory '/home/you/': Permission denied. The third one go show you the identity wey the agent network path dey present. Na this question the gateway pattern dey answer: 401 mean say the agent no carry any GitHub credential of e own, while 200 mean say e carry one. So, you suppose know which token e dey use. If you dey run agents unattended, how to control AI agent costs on a VPS explain the budget limits wey go work with these access limits.
FAQ
I fit trust the model make e no go leak my keys?
No, because model no be the attacker for this threat model. The agent dey read text from web pages, repositories, and issue trackers, and that text fit contain instructions. Model no get reliable way to know the difference between your instructions and text wey e fetch. Any control wey depend on model to choose correctly go fail the first time an injected instruction look convincing, so the control must dey inside operating system or network instead.
Environment variables really bad reach for agent secrets?
Dem bad for one specific reason: dem dey inherit. Every child process wey agent spawn get copy, including build script, test runner, and any package install hook. The variables too dey readable through /proc/<pid>/environ by the same user, so anything wey agent run fit read dem without agent passing dem along. File wey you read only when you wan use am, with LoadCredential= or a gateway, limits the exposure to that moment.
If I put secrets inside vault, e solve this by itself?
Only partly. Vault fixes storage. E no fix the last step, where something pulls the secret from vault and hands am to agent as an environment variable, wey put you back for where you start. Wetin matter na who dey perform the substitution. If agent fetch the secret, agent get the secret. If gateway or init system perform the substitution outside agent process, agent never hold am.
How I go know whether agent don already leak something?
Usually you no fit know after the event, and na this one be the reason for using gateway. Without gateway, your evidence dey scattered across shell history, agent transcript, and outbound connection logs wey you probably no dey keep. With credential gateway, every use of credential na one line with agent identity and timestamp. If you suspect leak, rotate the key first and investigate later. 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 inside, and create one unprivileged user for each agent. Those two changes take about ten minutes and close the most common path: agent reading credential file wey no get 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 how to run autonomous agent safely for VPS.