ranwhat clean

Your agent read your .env. The output is still on disk.

When an agent runs cat .env, the result is written into its transcript in plaintext: your database password, your signing secrets, your provider tokens, sitting in files Claude Code keeps for 30 days by default. Secret scanning on your repository never looks there. ranwhat clean does.

$ ranwhat clean
Install
01 / The point

Redaction is not remediation.

Masking a value here does not un-expose it. It was already written to disk and already sat in a model context you do not control.

Claude Code's own documentation says it directly: “If a tool reads a .env file or a command prints a credential, that value is written to projects/<project>/<session>.jsonl.”

So the report leads with what to rotate and where, and treats masking as the secondary act. A tool that printed “cleaned: 47 secrets” and left you feeling safer would be actively harmful.

ranwhat cleandry run
47 distinct secret(s) in 312 place(s)

These must be rotated.
They have been written to disk in plaintext and sat in a
model context you do not control. Masking them here stops
them leaking again. It does not make them safe.

* Stripe live secret key   sk_…dc  32 chars  seen 8x
* DATABASE_URL             pos…pp  42 chars  seen 8x
* PUSHER_APP_SECRET        b99…0a  64 chars  seen 2x

Dry run. Nothing was changed.
02 / Review

The session stays open.

Scanning a real history takes a while and the findings are already in memory. Re-running the whole scan to act on what you just read wastes that.

ranwhat> list           the findings again
ranwhat> show 3         where it appears, and what to roll it at
ranwhat> mask 3         mask just that one
ranwhat> mask all       mask everything listed
ranwhat> keep 3         leave it alone, and do not report it again
ranwhat> rotate         grouped by provider, with each page
ranwhat> open           open those pages in your browser
ranwhat> revoke 3       revoke it now, at GitHub, Slack or Google

rotate names the place to roll each one, whether that’s the IAM console, Stripe’s API keys page or BotFather, rather than leaving you to work out where a 64-character string came from. open opens each of those pages in your browser, or open 3 just that one’s. Over SSH, or with no browser, it lists them instead. revoke 3 revokes a GitHub, Slack or Google token on the spot, after you type yes: GitHub, Slack and Google are the providers that let anyone holding a token revoke it with one call.

ranwhat clean                   report, then open the review
ranwhat clean --no-interactive  report and exit
ranwhat clean --apply           mask everything, backups first
03 / Restraint

A secret is a literal, not code that mentions one.

Run against a machine that does real work, the first build reported 185 secrets and roughly two thirds were not credentials at all.

ReportedWhat it actually is
process.env.DB_PASSWORDa reference to a secret, not its value
crypto.randomBytes(32)a call that produces one
[A-Z]{20}a regex that matches one
${env}-session-ida template literal
os.environ['JWT_SECRET']a Python environment read
/Users/you/projecta path assigned to PWD

A value now has to be a literal: no code syntax, no environment reference, not a path, not a regex, not already masked. What survives must clear three bits per character of Shannon entropy unless it matches a known credential shape. Generated secrets sit well above that line; words and code sit below.

Measured 15 of 18 false positives before, 0 after, with all eight genuine credentials still found.
Safety Reports by default. --apply writes a timestamped backup to ~/.ranwhat/backups first, then parses the rewritten file back before replacing the original.
Scope Masking one finding rewrites only that value, not every other secret in the same file.
Reads The history of every agent watch reads, for the last 30 days by default (--days N). For Claude Code that is its transcripts, ~/.claude/projects/*/*.jsonl and the transcripts subagents write beside them, or the same under $CLAUDE_CONFIG_DIR/projects when that is set. Not the large tool outputs in tool-results/ yet.
Masks Only in a file the agent lets it rewrite: plain JSONL, JSON or text that nothing has written to in the last two minutes. That covers Claude Code, Codex, Gemini CLI, Antigravity, GitHub Copilot CLI, GitHub Copilot in VS Code, Windsurf, Cline, Roo Code, Kilo Code, OpenCode, Continue, Aider, Goose, Qwen Code, CodeBuddy, Grok Build, Droid, Kimi Code, Kimi CLI, Pi, Oh My Pi, Muse Code, Mistral Vibe and DeepSeek Harness. Read only: Cursor, Zed, Warp, Amazon Q and OpenClaw, whose histories are databases, or for Amazon Q in an IDE a file its language server rewrites every second, and any agent's databases and compressed files, such as Codex's thread index, the databases OpenCode, Kilo Code and Goose keep now, Mistral Vibe's unified sessions and DeepSeek Harness's compressed ones. The report names each of those files that holds a secret, and how to remove it in the agent instead.
Recognises Stripe sk_live_ and rk_live_ keys, sk- style OpenAI and Anthropic keys, GitHub ghp_, gho_, ghu_, ghs_, ghr_ and github_pat_ tokens, Slack xox tokens, AWS AKIA and ASIA key IDs, Twilio SIDs, SendGrid keys, PEM private keys and JWTs, passwords inside connection strings, passwords typed where a command takes one (mysql -pPASSWORD, sshpass -p, redis-cli -a, docker login -p, curl -u user:password, --password and the like), and KEY=value pairs whose key names a secret.
Index check and watch hide every secret clean finds, anywhere in your history whatever --days says, in what they print. They know them by an index in ~/.ranwhat/known that holds salted fingerprints, never a secret, and read a transcript again only when it changes.
04 / Next

Then see how it got there.

clean finds the values. ranwhat watch finds the calls that read credential files in the first place, and ranwhat check runs both in one read-only pass.

Rotate first: what to do when a secret has already leaked. To stop the next read, keep Claude Code out of your .env file with deny rules and the sandbox.