Skip to main content

ranwhat

A flight recorder for AI agents, and a scanner for the authority they hold. AI coding agent security for Claude Code, run on your own machine. No account, no telemetry, no dependencies.

Website and docs: https://ranwhat.com

Your coding agent has your shell, your keys and your repo. ranwhat reads what it actually ran and surfaces the handful of irreversible actions worth knowing about.

$ ranwhat watch --days 90

  ranwhat watch  · local agent flight recorder
  ----------------------------------------------------------------------------
  4 transcript(s) scanned, last 90 days

  2 critical  2 high

  * Credential material accessed   2026-09-30 18:13:00  Bash
      cat ~/.ssh/id_rsa
      -> The agent read a file whose only purpose is to hold secrets. Whatever
         it read is now in a model context you do not control.

  * Bulk or recursive deletion   2026-09-28 14:42:00  Bash
      …rm -rf '@'
      -> Recursive deletion. Recoverable only if something else was backing it
         up.

  * Package or release published   2026-09-21 11:05:00  Bash
      npm publish
      -> Something was pushed to a registry other people may install from.
         Supply-chain reach, and usually irreversible.

  * Destructive git operation   2026-09-15 17:30:00  Bash
      git push --force origin main
      -> History rewriting or branch deletion. This is the class of action
         that destroys the record of what else happened.

  ----------------------------------------------------------------------------
  Read locally. Nothing was transmitted.

Claude Code deletes the transcript of a session you have not used for longer than cleanupPeriodDays, 30 days by default. So --days 90 finds more if you raised it, if a session you resumed within that period has older actions in it, or for sessions started or last continued in Claude Desktop or Cowork, which Claude Code v2.1.248 and later keep at any age by default.

Install

Run it once, without installing anything:

uvx ranwhat check

Or put it on your path:

pipx install ranwhat
pip install ranwhat

Then ranwhat demo shows an authority scan on a bundled example.

Python 3.9+. No dependencies, and nothing is built on your machine.

Commands

ranwhat check: everything worth knowing, in one read-only pass

Runs watch and clean together and changes nothing.

On a terminal, check, watch and clean keep one status line on stderr while they read, counting transcripts through each pass: indexing secrets ((first run) the first time), checking actions, looking for secrets. Nothing is written there when stderr is not a terminal, or with --json.

ranwhat watch: audit what Claude Code ran

Reads what Claude Code already wrote to disk. No wrapper, no proxy, nothing in your critical path.

Source Location Format
Claude Code ~/.claude/projects/*/*.jsonl and each session's subagents/**/agent-*.jsonl, or the same under $CLAUDE_CONFIG_DIR/projects when set JSONL
OpenClaw $OPENCLAW_STATE_DIR/agents/*/agent/openclaw-agent.sqlite SQLite

Nine rules: credential access, secret-shaped strings in tool calls, package publishing, cloud resource changes, financial API calls, log tampering, destructive git, recursive deletion, and local files uploaded with curl.

ranwhat watch --days 30
ranwhat watch --source claude-code
ranwhat watch --json

ranwhat clean: find secrets in Claude Code transcripts

When an agent runs cat .env, the output is written into the transcript: your database password, your JWT secret, your provider tokens, in plaintext, in a file Claude Code keeps for 30 days by default.

ranwhat clean               # report, then open a review session
ranwhat clean --apply       # mask everything without asking

Scanning a real history takes a while, so the session stays open on what it just found rather than making you re-scan to act on it:

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
ranwhat> rotate         what to rotate, grouped by provider

Each finding says which project it was found in and, when the transcript names it, the file it was read out of, because a 64-character string is useless without knowing which .env it escaped:

* Stripe live secret key   sk_…dc  32 chars  seen 8x
      read from api/.env
      in         /Users/you/Desktop/app

Redaction is not remediation. Masking a value here does not un-expose it. It was already on disk and already sat in a model context you do not control. The rotation is the fix; masking only stops it leaking a second time. The report says so rather than implying safety.

Only masks a value when the key beside it names it as a secret or the value carries a recognisable credential shape. Placeholders, template files and ordinary config are left alone, and so are published documentation examples (AWS's AKIAIOSFODNN7EXAMPLE) and obvious test fixtures such as AKIA1234567890ABCDEF. Backups go to ~/.ranwhat/backups, and the rewritten file is parsed back before it replaces the original.

A password typed into a command, with no key beside it, is found where the command takes it: mysql -pPASSWORD (and mysqldump, mysqladmin, mariadb), sshpass -p, redis-cli -a, docker login -p, curl -u user:password, --password, and the same places in sqlcmd, mongosh, ldapsearch, htpasswd, keytool, ConvertTo-SecureString -AsPlainText and smbclient. A variable there, such as -p$MYSQL_PWD, is left alone.

The secrets index

check and watch hide every secret clean finds anywhere in your history, whatever --days says, wherever a copy of one shows up. To know them without reading every transcript on every run, they keep an index in ~/.ranwhat/known/ ($RANWHAT_HOME/known/ when that is set), one file per transcript directory, and read a transcript again only when its size or modification time changes. The first run reads them all, and says so. check fills it from its own search for secrets, so no transcript is read for them twice.

The index holds salted fingerprints, never a secret: for each, 16 bits of a keyed BLAKE2b hash of its first six characters (a tag a great many beginnings share, so it narrows a guess at them by no more than 16 bits), its length, and keyed hashes of the whole value and of the fingerprint its mask keeps. The key is random, made once per machine, and sits beside the index. Both files are readable by you alone. clean adds each secret before it masks it, so a copy it missed stays hidden too, however the session ends.

If the index is deleted, damaged or loses its key, the next run builds it again from the transcripts. A secret clean has already masked cannot be learned again that way: the transcript keeps only its mask's fingerprint. A copy the mask missed is then still hidden where it stands apart from what is around it, and where it is glued into a command check or watch shows, if it is no longer than 64 characters. Glued into anything else, it is not. So keep the index unless you are starting over.

ranwhat scan: score what an agent's credentials can do

Reads the credentials an agent holds, read-only, and scores the three things that determine exposure.

Axis Question
Authority What is it allowed to do?
Observability Can you reconstruct a named past action?
Reversibility Can a wrong action be undone?

Observability vetoes the overall verdict. An agent that cannot reconstruct its own tool calls is indistinguishable from the worst case.

ranwhat demo                                # see it on a worked example
ranwhat scan profile.json --html report.html

read -rs RANWHAT_GITHUB_TOKEN                # paste it: not echoed, not saved to history
export RANWHAT_GITHUB_TOKEN
ranwhat live                                # read-only introspection

read -rs RANWHAT_STRIPE_TOKEN
export RANWHAT_STRIPE_TOKEN
ranwhat scan profile.json --pull-usage

Capability catalogues for Google, GitHub, GitLab, Microsoft 365, Slack, Discord, Stripe, Shopify, HubSpot, Atlassian, Sentry and AWS. Unrecognised scopes are classified by action verb and flagged unclassified, never assumed safe.

A fine-grained GitHub token or a restricted Stripe key comes back from live as a finding, not a score: neither provider lists its permissions through its API. Copy them into a profile as that credential's scopes and scan it.

ranwhat update: refresh the capability catalogue

ranwhat update refreshes the capability catalogue from ranwhat's feed. It needs a Plus subscription, which is not available yet, and sends only the subscription token.

Precision is the feature

A watcher that cries wolf gets muted in a day, and a muted watcher records nothing anyone reads. So these are not treated as actions:

Not an action Why
grep "rm -rf" src/ Searching for a string isn't running it
python3 -c "print('rm -rf /')" The payload is Python source, not shell
echo "rm -rf /" An echo argument is literal text
cat > f.sh <<'EOF' … EOF A heredoc body is data being written
git rm --cached x Unstages; never touches the working tree
# rm -rf ~/x A comment
rm -rf build rm -rf /tmp/x Deleting build output is not an incident

bash -c is the exception: its payload really is shell, so the parser recurses into it. And severity follows the target, not the verb: rm -rf /tmp/x is silent, rm -rf ~/Documents is high, rm -rf / is critical.

The grep, Python, heredoc and build rows came from running the tool on real history and finding it wrong; the others came from probing a released build. The first watch build flagged 6 findings, and 3 were false positives: a grep for rm -rf, the same string inside a Python one-liner, and a heredoc being written to a file. A later release reported 15 findings on one machine's 90-day history, and 11 were true deletions of build and temp directories that nobody would want to read. Once deletions were ranked by target, the same history gave 4, and all four were real.

Handling credentials

Pass tokens through the environment, not the command line. Anything in argv is readable by every user on the machine through the process table, and is written to your shell history.

read -rs RANWHAT_STRIPE_TOKEN       # paste it: not echoed, not saved to history
export RANWHAT_STRIPE_TOKEN
ranwhat scan profile.json --pull-usage

Read it in and export it first, because VAR=x ranwhat ... on one line still puts the value in that shell's own command line, and a typed export VAR=x puts it in your shell history. --stripe env:MY_VAR and --stripe - (read one line from stdin) also work. Passing a token as a flag value still works and prints a warning saying why it shouldn't.

Reports are written mode 600 and never through a symlink: a report maps an agent's entire authority surface, which is useful to somebody other than you.

What goes online

No account needed. live and --pull-usage ask only the provider that issued each token, and update only fetches the catalogue. Everything else reads locally and sends nothing.

  • Provider credentials are held in memory for one call and never written down. The only token ranwhat stores is your own subscription token, and only with update --save-token (mode 0600).
  • Live introspection talks only to the credential's own issuer
  • Scans never exercise a permission and never need a write-scoped token
  • No runtime dependencies, so there is nothing to audit before you point this at your keys

What it does not read yet

  • The large tool outputs Claude Code stores in <session>/tool-results/
  • ~/.claude/history.jsonl
  • Events the SQLite source stores compressed (event_zstd), and the cold transcript archives in agents/<agentId>/sessions/cold/ under the same state directory, which its database layout says also hold history

When CLAUDE_CONFIG_DIR is set, transcripts are read from $CLAUDE_CONFIG_DIR/projects, and --root PATH reads any other directory. A run that reads nothing says that nothing was checked, rather than that nothing was found.

Say what you don't know

Usage evidence has three states, reported distinctly, because collapsing them makes the report contradict itself:

State Meaning
Verified Pulled from the provider's own audit trail
Self-attested Declared in the profile, not independently pulled
Unverified No evidence at all, and scopes are not assumed safe

Usage pulls: AWS IAM service-last-accessed, Stripe events, Google Admin SDK, GitHub org audit log. Slack has no usage pull, so Slack usage is only ever self-attested or unverified.

Status

Alpha, honestly. The local tools ship and are tested; hosted collection and evidence retention are not built yet.

python3 -m unittest discover -s tests -v

The suite is written as invariants rather than expected output. Most tests exist because something on this page was once wrong.

Links: Website · Changelog · Issues

Licence

MIT. Built by cenner.

Metadata

Release files for ranwhat 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ranwhat 0.4.0
File Size Uploaded
ranwhat-0.4.0.tar.gz 358.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ranwhat 0.4.0
File Interpreter ABI Platform
ranwhat-0.4.0-py3-none-any.whl Python 3 none any Details

Total release size: 525.7 kB

Release files / ranwhat-0.4.0.tar.gz

Download URL ranwhat-0.4.0.tar.gz
Size 358.5 kB
Tags Source
SHA-256 checksum
How to use checksums
a45cdb0296374504b6327b0c33cbff51aebd07bc6dc728008f1741ec51e69e61
BLAKE2b-256 checksum
How to use checksums
616841d545d9d9b3ba92b4f2e700fd3668c0c11fab3e4f091ecedb32f6edaeac
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / ranwhat-0.4.0-py3-none-any.whl

Download URL ranwhat-0.4.0-py3-none-any.whl
Size 167.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d6c995b6df37b7d86262f5c116812801cdf86dd6b2f56ec0cbbdf9ba577f6ce9
BLAKE2b-256 checksum
How to use checksums
85da584f4fe98ef4fd8fafe9fe567146ca9e225d194c1d5a6812f62a5c0940cc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.17 {"installer":{"name":"uv","version":"0.12.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.7.0

2 release files

0.6.0

2 release files

0.5.0

2 release files

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page