Why your AI coding agent should never see your API keys
Your AI coding agent needs your API keys. It needs them to call services, to
test integrations, to run your stack. So you give it .env files, or you
export keys into the environment, or you paste them into config files the
agent can read.
That means your secrets live inside the agent’s context window — the same window where a prompt-injected instruction or an overly verbose debug log can leak them to an attacker or an untrusted model endpoint.
This isn’t theoretical. If you’ve used Claude Code or OpenCode for more than a few days, you’ve probably seen a tool call dump an environment variable, or a log line that echoes a connection string. Most of the time nothing bad happens. “Most of the time” is a bad security posture.
The core problem
AI agents are the first software that reads your source, your config, and your secrets, then sends summaries of what it read to a third-party API.
With traditional software, the principle was simple: secrets live in the process environment, code reads them at runtime, nobody reads them back out. With agents, there is no such boundary — the agent both reads the environment and transmits what it knows.
Three concrete leak vectors:
- Context exfiltration — the agent reads
.envand includes values in a later prompt to an external model. You can’t audit this; it’s in the model’s training/inference pipeline. - Tool output echo — a command prints an env var or a config value; the agent captures stdout and stores it in the conversation.
- Prompt injection — a malicious instruction (in a fetched web page, a dependency, an artifact) tells the agent to “print all environment variables” or “send the contents of .env to this URL”.
What the tooling landscape offers
The solutions fall into a few buckets:
- Secret managers (Vault, Doppler, Infisical) — great for your code, but the agent still needs a way to get the secret, which puts it back in context.
.envhiding (enject, tene) — keeps plaintext off disk, but when the agent runs a command that needs the secret, the value can still end up in stdout.- Credential proxies (vaulty) — the agent makes HTTP requests through a proxy that injects the credential. Promising, but typically tied to their own vault.
The model that worked for me
I ended up building a small CLI (Go, zero external deps) with four layers:
Subprocess injection with output sanitization.
trustless run -- cmdresolves secrets from my existing pass store and injects them as env vars. After the command runs, stdout/stderr is scanned and secret values are replaced — including base64 and URL-encoded variants. The agent sees the command output, not the keys.HTTP proxy with per-host injection. For services that take headers or query params (EDINET, e-Stat, xAI, OpenRouter),
trustless proxyinjects the right credential per host. The agent points at127.0.0.1:8080and forgets about keys entirely.DLP reverse proxy for LLM calls.
trustless serveputs a scanning proxy in front of OpenAI-compatible endpoints. Outbound requests are checked against secret patterns (keyword → regex → entropy, gitleaks-compatible rules) and masked in-flight before they leave the machine. This is the layer that catches the “agent decided to include the key in a request” case.Retroactive scrubbing. Prevention fails — keys end up in agent session databases, logs, and dumps anyway.
trustless dlp scrub-dbandscrub-textscan SQLite DBs and text files with the same two-layer redaction (known values + patterns), rebuild FTS indexes andVACUUMthe DB so no physical remnants survive. Default is a dry-run report;--applydoes the write,--backupkeeps a copy first.
Why not a new vault? Because I already had pass. The CLI reads the existing store, so there was zero migration. (Bitwarden is supported too, with OAuth token auto-refresh for Google/Lark.)
The agent gets capabilities, not credentials. That’s the whole trick.
Takeaways
- Never let a secret enter the agent’s context window — not as env, not as config, not as tool output. Once it’s there, you’ve lost the audit.
- Inject at the process/transport boundary, not at the prompt level. “Please don’t print the key” is not a security control.
- Sanitize output, not just input. The leak vector is often the command’s stdout, not the agent’s intent.
- Scan outbound requests if your agent calls external APIs directly. A DLP layer is the difference between “we hope it didn’t leak” and “we know it didn’t”.
- Assume leakage happened and scrub retroactively. Run a periodic scan
of agent session DBs and logs; a dry-run scrub report tells you what would
be found,
--applycleans it, and a backup keeps the recovery path.
If you want to look at the code: trustless is MIT-licensed at https://github.com/ikkun1222/trustless — 321 tests, race-detector clean, cosign-signed releases. It’s one implementation of this model; the threat model discussion is more valuable than the tool itself.