VibeOps Blog
Five places secrets leak in AI coding workflows
Ask a developer how a secret could leak into an AI workflow and you get one answer: "I'd have to paste it." That's the loud path, and it's the one people avoid.
The quiet paths are more common. Here they are, roughly in order of how often they actually happen.
1. Command output
The biggest one, and the least defended.
An agent runs a command and reads the result back to reason about it. That's the whole loop. But commands print things:
envandprintenv— the entire environment, verbatim.docker inspect— the container's env block, including every secret you passed in.vercel env pull,wrangler secret list, most CLIconfigsubcommands.- Verbose flags on almost anything.
curl -vprints yourAuthorizationheader. - Stack traces. A failed Postgres connection prints the connection string, password included.
None of these require anyone to paste anything. The agent ran a reasonable command, and the credential arrived in the context window as output.
The fix: scrub on the way back. Every value the system holds gets replaced with a placeholder in stdout and stderr before the output reaches the model. This has to be based on the values you hold, not on regex pattern-matching for things that look like keys — patterns miss, and the one they miss is the one that leaks.
2. Repo context
Agents read your repository to understand it. Your repository contains .env.local, terraform.tfstate, serviceAccount.json, config/database.yml, and a docker-compose.yml with the dev database password in it.
A file-reading tool with no exclusions will eventually read one of them, usually while looking for something else entirely.
The fix: deny-list the well-known secret-bearing filenames at the tool layer, not in the prompt. "Please don't read .env" is a request. A refusal in the file-read tool is a guarantee.
3. Generated code
The model writes code. Sometimes that code contains a credential — because it saw one in context earlier and helpfully inlined it, or because it wrote a plausible-looking placeholder that you never replaced.
Then you commit it. Now the key is in git history, and if the repo is public, in a scraper's index within minutes. Public-repo key scanners are fast; the practical window between push and abuse is measured in minutes for cloud provider keys.
The fix: a pre-commit secret scan. gitleaks or trufflehog in a hook is ten minutes of setup and catches this whole category, AI-generated or not.
4. Transcripts and session logs
Everything above is recoverable if the secret only existed in one place. It never does.
The context window becomes a session file on disk. The session file becomes a crash report, a bug report attachment, a shared "look at what it did" screenshot, or a cloud-synced history. Provider-side, it's a request log with whatever retention that provider promises.
The fix: this one is structural. You cannot clean up transcripts reliably, so the answer is that the secret must never have been in the context to begin with — see below.
5. The obvious one
Pasting the key into the chat box. Still happens, still bad, and now it's in all four of the above at once.
The structural fix
Four of these five leak paths close if the credential is never in the model's context in the first place.
The pattern is straightforward: keep secrets in the OS keychain, let the model plan with a reference ($DATABASE_URL, a name and nothing more), and resolve that reference into the child process's environment at execution time, locally, after you approve the command. The command gets the real value. The transcript gets the placeholder.
With that in place, the remaining exposure is generated code — a pre-commit scanner's job — and command output, which is why scrubbing has to be built on the same set of held values.
VibeOps is built this way: secrets live in your OS keychain, the model gets references, output is scrubbed against the held values, and every write waits for your approval. The model plans the deploy without ever holding a credential.