Environment variable leaks in AI tool output: the base64 trap
Your agent runs a command that fails. It diagnoses by printing the environment: env, printenv, a build script that echoes configuration, a test that dumps os.environ to help debugging. Any one of these can put a live API key into the agent’s context window, and from there the key travels to the model provider, into session logs, and into your transcript files. The leak is not exotic — it is the most common way agents lose secrets. The interesting part is that most naive attempts to fix it do not work.
Why the leak happens
Secrets reach tool output because subprocesses inherit the agent’s environment, and the agent’s tools are, by design, chatty. Debuggers print what they are given. Build tools echo variables. Test frameworks dump the environment on failure. None of this is malicious. The agent is doing exactly what you asked: gathering information. The environment happens to be full of credentials, because that is where credentials live.
Exact-match redaction fails on encoding
The obvious fix is to scan output for known secret values and replace them. That fails the moment the secret changes shape. An API key beginning with sk- that is base64-encoded becomes a string of characters sharing no substring with the original — the sk- prefix is gone, the length is different, and an exact-match scan sees nothing to redact. URL encoding does the same: a + becomes %2B, a space becomes %20. Agent run logs are full of encoded values, because logs and transcripts are exactly where such strings end up.
The base64 trap
The base64 trap is that encoded secrets look like ordinary noise. The naive scanner sees c2stYWJjMTIz... and has no reason to flag it. But the encoded secret retains the properties of the original: it is long, random-looking, and high in entropy. That is the property detection can use. A sanitizer that relies only on known values is blind to every transformation; a sanitizer that also measures randomness catches the transformed copies.
Detecting the encoded key
Practical detection combines three layers. A keyword pre-filter narrows the work to lines that look interesting — lines containing key-like words or long tokens. A regex pass matches known secret formats, such as the sk- style prefixes used by many APIs. Then a Shannon entropy check flags strings whose randomness is high: the default threshold in the trustless DLP pipeline is 3.5, with a minimum length of 8, which catches base64 and URL-encoded variants that no pattern list can anticipate. The pipeline ships 40 rules derived from the gitleaks set, and each rule can be tuned, logged, or disabled per deployment.
Sanitizing at the source, and after the fact
The strongest fix removes the secret from tool output before it is captured. trustless run injects secrets into the subprocess environment and sanitizes stdout and stderr, including base64 and URL-encoded variants, so the value never reaches the agent’s context. For transcripts and databases that already contain leaked values, trustless dlp scrub-text and scrub-db apply the same two-layer redaction — known values plus patterns — dry-run by default, with a --apply flag and a backup of original files before any change is made. trustless is a single static Go binary with zero runtime dependencies, and its command reference documents both the run-time and scrubbing modes.
The next time your agent prints the environment, assume a key came with it. How would you know — and would your current redaction catch it after it was base64-encoded?