An agent with shell access doesn't need to be malicious to leak a secret — it just needs to do something ordinary with an environment that happens to contain one. It can echo $ENV while debugging, paste a config file into its own reasoning to explain an error, or shell out to a tool that logs its full argument list including the token you passed on the command line. None of that requires the agent to be compromised or to disobey an instruction; it's the normal behavior of a thing that reads and repeats text, applied to an environment that wasn't scoped to assume it would. The fix isn't "tell the agent to be careful with secrets" — that's the same category of instruction as "don't have bugs," unenforceable because it's not a mechanism. The fix is not putting secrets somewhere the agent's normal operation can reach them in the first place.
A shell tool inherits the full environment of the process that launched it by default, which means every credential exported anywhere upstream — a database URL, a cloud provider key, a deploy token — is readable by any command the agent runs, whether or not that command has anything to do with the secret's purpose. env, a stack trace that dumps process environment, or a debugging command as innocuous as printenv | grep -i key all surface the same data. If an agent only needs to call one API, it should be launched with an environment containing only that one credential, not inherit the parent shell's full set because scoping it down felt like unnecessary setup.
A secret passed as a CLI argument (curl -H "Authorization: Bearer sk-...") lands in shell history, process listings visible to other users on the same host, and often the agent's own transcript or log file verbatim — and all three persist well after the command finishes, unlike an in-memory value that goes away when the process exits. The safer shape is a secret read from a file or a credential helper at the moment it's needed, passed to the tool via stdin or a config file with restrictive permissions, never typed into the command line or into a prompt where it becomes part of a transcript that gets stored, retried, or shown to a human reviewing the agent's work later.
Whatever an agent prints — including a secret it echoed while diagnosing a "why is this env var empty" problem — usually ends up somewhere persistent: a CI log artifact, a session transcript saved for review, a monitoring pipeline that ingests stdout. None of those systems were necessarily built with the assumption that a stray credential might appear in them, so they retain it at whatever default retention and access policy applies to ordinary output — often far broader than the credential's own access policy. Treat "will this ever be printed" as the operative question for any value the agent's shell can see, not "will the agent try to print it," because the second question gets answered by accident far more often than on purpose.
| Common setup | What to do instead |
|---|---|
| Agent inherits the full parent environment | Launch with an explicit allowlist of only the variables that specific task needs |
| One long-lived API key used for everything | Short-lived, narrowly-scoped tokens per task — a leaked one expires or can't reach much |
| Credential passed as a CLI argument | Read from a file with 0600 permissions or a secrets manager at call time |
| Same credential for read and write operations | Read-only scope by default; a separate, explicitly-granted token for anything that mutates state |
| Trusting the agent not to paste secrets into its reasoning | Assume it will eventually happen and make sure the blast radius is small when it does |
Deciding what a headless agent is allowed to run, how wide its tool grant is, and deciding what it's allowed to read are the same underlying problem: don't rely on the agent choosing correctly in the moment, design the environment so the costly mistake isn't reachable. A diff that leaked a secret would also be one of the deletions-and-additions worth flagging in a pre-merge review — a credential appearing in a committed file or a log fixture is exactly the kind of thing that's easy to skim past because it looks like ordinary configuration.
The goal isn't zero risk — an agent that can't read any credential usually can't do its job. The goal is making sure the credential it can reach is the smallest one that still lets it work, scoped narrowly enough that an accidental leak costs little instead of costing everything.