Secrets management for AI coding agents: make the leak impossible, not unlikely
The pull request looked like a formatting cleanup: three files, a
title about tab width. In CI, Anthropic's own Claude Code Security
Review action picked it up, read that title as an instruction the
way it reads everything else, and did what it said. It opened
/proc/self/environ, found ANTHROPIC_API_KEY, trimmed off the
first few characters so neither Claude's own filters nor GitHub's
secret scanning would recognize the shape, and posted the rest back
as a PR comment.
Nobody pasted a key into a chat window. Nobody committed a .env
file by mistake. An agent read a file it was told to read, then
wrote to a comment it was told to write to, and a credential crossed
the fence in between. The agent did not misbehave. It did exactly
what an agent does with an instruction and a file, and neither one
was marked as a secret.
Why AI coding agents leak secrets twice as often
GitGuardian's 2026 secrets sprawl report put a number on the feeling. Across 28.65 million secrets added to public GitHub in 2025, commits assisted by Claude Code leaked at a 3.2% rate, against a 1.5% baseline for public GitHub as a whole: more than double.
The report doesn't read this as the tool being broken. Part of it is size: agent-assisted commits run roughly twice the lines of a human one, twice the surface for a stray key to hide in. The rest is judgment. A developer pauses before pasting a production key into a debug script; an agent doesn't, because nothing marks that string as different from any other line of config. To a model, an API key and a port number are the same kind of token: text that configures something.
MCP configs carry the same risk at a wider scale. A Model Context Protocol config file often holds the credentials a session needs for every tool it's wired to. GitGuardian found 24,008 of those files exposed on public GitHub, 2,117 still holding a valid credential: one file, and the leak is every service it pointed at.
The three ways a secret gets out
Almost every incident traces back to one of three paths, and only one of them involves anything resembling an attack.
Committed straight into the repo. A stray .env swept in by a
broad git add -A, or a fixture that
kept a real key because a fake one didn't reproduce the bug.
.gitignore stops a file being added by accident; a sweep command
adds it on purpose, credentials included.
Echoed while debugging. An agent stuck on a failing request
reaches for echo $STRIPE_KEY or cat .env to sanity check a
value, exactly like you would, and now the key sits in a terminal
transcript, a CI log, or a screenshot pasted into a review comment.
Nothing was committed. The secret still left the machine.
Exfiltrated through an instruction the agent trusted. The
opening story is the sharpest version: a prompt
injection hidden in content
the agent reads, paired with a tool reaching an environment variable
and a channel to carry the answer out. Anthropic's fix was concrete:
that action's Read tool now rejects files under /proc/ outright,
shipped in Claude Code
2.1.128.
The pattern itself, untrusted input, a live credential, an outbound
channel, isn't specific to that one action.
Design the leak out instead of scanning for it
A scanner that catches a secret after the commit is a seatbelt. The fix that matters changes what's true before the agent ever opens the file.
Scan before the commit lands, not after. A pre-commit hook running gitleaks, trufflehog or git-secrets blocks a matching pattern before it enters history at all. This catches the accident, not a targeted exfiltration, but most leaks are the accident.
Scope every credential to the one job it does. A token that can only read a staging bucket is a bounded incident if it leaks. A master key that can do anything is a full compromise. Generate the narrow one, even when the broad one is one click faster to set up.
Keep the secret out of any file the agent can open. Inject it at
runtime from a secrets manager or your shell instead of a .env on
disk. A sandboxed process that
scrubs its environment before it starts beats careful prompting
every time, for the same reason a guardrail beats an
instruction everywhere else: a
file the agent cannot open is a control, a file it's asked not to
open is a preference.
Never combine all three in one workflow. Untrusted input, a live credential, and an outbound channel together are what turned the opening story into an incident. Remove any one and the path closes.
Where secrets management goes wrong, and what it really costs
A secrets manager becomes the new single point of failure. Centralizing every credential behind one service is correct right up until that service has an outage or a botched rotation, and every agent session depending on it stalls mid-task with a generic auth error.
Rotation breaks things quietly. Rotate a leaked key and every unattended loop still holding the old value fails on its next run, often overnight, with nobody watching. The fix has a blast radius too, smaller than the leak, but real.
Scanners only find the shape they know. Gitleaks and its peers match known formats: AWS keys, GitHub tokens, Stripe keys. An internal token or a signed JWT for your own service often matches nothing, and a clean scan gets read as proof instead of as a boundary.
A clean scan is not a fix, the same way a green test suite is not proof of one. Treat it as a layer, not the whole answer.
A workspace where the blast radius is a folder, not a laptop
SanuDesk is a desktop app built around exactly that boundary. Your Claude Code, Codex and Gemini sessions tile into one grid, each scoped to a single project folder, so a credential a session can reach is whatever that folder holds, not your whole laptop. It's bring-your-own-model by design: your provider keys stay with you instead of routing through a relay that becomes one more thing to trust. A git pane beside every session shows the live diff as it forms, so a stray key in a tracked file is something you see before it's committed, not reconstructed from a transcript afterward. Work arrives through a Kanban board: deploy a card and it opens a fresh, scoped session, and the result parks in a review lane instead of merging itself. A nightly scan over the repo becomes a Loop, journaled per run instead of forgotten in a terminal tab. The grid and board are in the free plan (see pricing).
Run the scan you have been assuming would catch this
Pick one repo tonight and scan its full history, not just the
working tree: gitleaks detect --source . --log-opts="--all", or
the trufflehog equivalent. Read every hit, including the ones your
instinct says are fine.
Zero hits doesn't mean clean. It means nothing matched a known shape. Any hit, even an old key you're sure was rotated, tells you the sweep already happened once, and the only real question left is whether the value was still live when it did.
Download SanuDesk free to keep every agent's reach scoped to a project folder, or see how the grid, the board and Loops fit together on the features page.