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

> Your agent ran cat .env; the key stays on disk. Find leaked secrets in Claude Code, Codex and twenty-eight more agents' transcripts, see what to rotate, mask them.

Canonical: https://ranwhat.com/clean

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](https://ranwhat.com/guides/claude-code-history). Secret scanning on your repository never looks there. ranwhat clean does.

```
$ ranwhat clean
```

[Install](https://ranwhat.com/install)

## 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](https://code.claude.com/docs/en/claude-directory#plaintext-storage) 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.
```

## 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
```

## 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.

| Reported | What it actually is |
| --- | --- |
| `process.env.DB_PASSWORD` | a 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-id` | a template literal |
| `os.environ['JWT_SECRET']` | a Python environment read |
| `/Users/you/project` | a 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](https://ranwhat.com/watch) reads, for the last 30 days by default (`--days N`). For Claude Code that is [its transcripts](https://ranwhat.com/guides/claude-code-history), `~/.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.

## Then see how it got there.

`clean` finds the values. [ranwhat watch](https://ranwhat.com/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](https://ranwhat.com/guides/ai-coding-agent-security#leaks). To stop the next read, [keep Claude Code out of your .env file with deny rules and the sandbox](https://ranwhat.com/guides/claude-code-env-secrets).
