Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

buzzcut

Your agent read .env once, six weeks ago.
It's still sitting on your disk in plaintext.

Stars 161 tests MIT license No dependencies

128 credentials · 1,158 occurrences · 242 files
Found on one working developer machine, accumulated over two months of normal use. Nobody pasted a key anywhere. The agent ran cat .env to debug something, and Claude Code wrote the output to disk — where it stays forever. Methodology.


The problem

Claude Code records every session to ~/.claude/projects/**/*.jsonl. Everything a tool prints goes in — including the contents of any credential file the agent opened. .gitignore does not apply; this is not your repository.

Anthropic knows. Two issues report it with real incidents — #58043 (a PINECONE_API_KEY leaked during routine debugging) and #59094 (live brokerage credentials leaked despite an explicit project rule forbidding it).

Both are closed as not planned. Labels: area:security, stale. Remediation is your problem.

Install

/plugin marketplace add akzh4n/buzzcut
/plugin install buzzcut@buzzcut

Just want to look first? No install, no plugin, no dependencies — Python 3 is all you need:

git clone https://github.com/akzh4n/buzzcut && python3 buzzcut/scripts/buzzcut.py scan

What it does

/buzzcut:scan Reports what's in your transcripts. Read-only. Never prints key values — types, projects and counts only, so you can paste the output into a ticket. Covers every project by default (transcripts are global); narrow with --project
/buzzcut:check Which of those keys still work. Asks each provider over a read-only endpoint. Network access, explicit consent required
/buzzcut:clean Cuts the values out of the files. Mandatory backup, --dry-run first
hook Redacts secrets from tool output before the model sees them — so they never reach the context or the disk

The part nobody else does

Every other tool stops at "this string looks like a key."

That's the wrong end of the problem. 1,158 matches is noise you scroll past. "Three keys an attacker could use right now" is an incident you act on. buzzcut check asks the provider.

The report it produces (shape of the output — check makes network calls, so it only runs on explicit consent):

  LIVE — rotate now
    OpenAI          sk-pro…Xe4     flowly
    GitHub PAT      ghp_4m…kM2     flowly

  DEAD — already rotated, just clean them out

  YOUR CALL
    private key, database URLs, internal service secrets

Before / after

⏺ Bash(cat backend/.env)
  ⎿  NODE_ENV=production
     DATABASE_URL=postgres://flowly:***REDACTED by buzzcut***@10.0.0.4/prod
     OPENAI_API_KEY=***REDACTED by buzzcut: openai***

  ✂ buzzcut: 2 secrets cut from Bash output

The command still ran. The agent got the file it asked for, minus the values. It does not start debugging a "broken" command — which is what happens when a tool rewrites the command itself with a sed filter instead of editing the result.

What this actually found

Numbers above come from a real machine: 4,308 transcripts, 1.9 GB, roughly two months of daily agent use across seven projects. They exclude buzzcut's own project directory, whose transcripts contain this repository's test fixtures rather than anybody's leaks.

A naive scanner reports 373 unique matches on that data. Two thirds of that is noise: PYTHONPATH=/a/b, HEAD=<sha>, WT=/path/DEV-604 — shell variables holding paths and git refs, structurally identical to a secret. A redaction hook that cuts those breaks your agent on every step.

buzzcut gates ENV-shaped findings on the variable name and excludes prefixes that are public by construction (NEXT_PUBLIC_, VITE_, REACT_APP_, EXPO_PUBLIC_ — bundlers ship those to the browser anyway). That leaves 128 unique keys across 1,158 occurrences, and what survives is POSTGRES_PASSWORD, AWS_SECRET_ACCESS_KEY, JWT_SECRET, R2_SECRET_ACCESS_KEY, TIKTOK_CLIENT_SECRET. Real ones.

False positives cost more than misses here — a hook that cuts the wrong string hands your agent corrupted data and it starts debugging a problem that does not exist. So the test suite has more cases asserting what must not be cut than what must.

One detail that costs other scanners a third of the findings

In a .jsonl transcript a newline is stored as the two characters \ and n, not as a newline. So a cat .env dump lives on one physical line:

{"stdout":"OPENAI_API_KEY=sk-proj-…\nDB_PASSWORD=…\nAWS_SECRET_ACCESS_KEY=…"}

To a regex, …\nDB_PASSWORD is a single word — the word boundary is gone, and everything after the first variable silently disappears. Grepping these files naively finds the first secret in each dump and misses the rest. On this corpus that was 38 keys, including an AWS secret access key and a Postgres password.

buzzcut normalises those escapes before matching, using a replacement of identical length so match offsets stay valid against the original bytes — clean edits the file in place by offset, and a shifted offset would corrupt the line instead of redacting it. That invariant has its own test.

Limits

  • Cleaning does not un-leak anything. A key that reached a transcript is compromised. Rotate it. check tells you which ones still matter.
  • AWS, Google API keys, database URLs and internal secrets are not probed automatically — that needs request signing or a connection to your own production. Those are listed for you to judge.
  • The detector is a heuristic. It does not replace gitleaks on your repo; it covers a surface gitleaks never looks at.

Development

python3 -m pytest tests/ -q        # 94 tests — the plugin
python3 -m pytest research/ -q     # 67 tests — the measurement code behind the numbers above

No dependencies, standard library only.

MIT.

About

this little tool finds the .env, API keys which Claude Code accidentally wrote to disk, checks which still work, and cuts them out

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages