AI 编码代理的飞行记录器和密钥扫描器。读取 Claude Code、Codex、Gemini CLI 及其他 9 个工具的运行记录,并标记高风险操作和泄露的密钥。本地运行,无遥测,MIT 许可。
AI 代理的飞行记录仪,以及对其所持权限的扫描器。 面向 Claude Code 及其他十一种编码代理的 AI 编码代理安全工具,在你自己的机器上运行。 无需账户,无遥测,无依赖。
网站与文档:https://ranwhat.com
你的编码代理拥有你的 shell、你的密钥和你的仓库。ranwhat 读取它实际运行了什么,并浮现出少数几个值得了解的不可逆操作。
$ ranwhat watch --days 90
ranwhat watch · local agent flight recorder
----------------------------------------------------------------------------
Read Claude Code: 4 transcripts, 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 会删除你超过
cleanupPeriodDays
未使用的会话记录,默认 30 天。因此,如果你调高过该值,如果在该期限内恢复的某个会话包含更早的操作,或者对于在 Claude Desktop 或 Cowork 中启动或最后继续的会话(Claude Code v2.1.248 及更高版本
默认保留任意时长),--days 90 会找到更多内容。
运行一次,无需安装任何东西:
uvx ranwhat check
或者把它放到你的 PATH 中:
pipx install ranwhat
pip install ranwhat
然后 ranwhat demo 会在一个内置示例上展示权限扫描。
Python 3.9+。无依赖,且不会在你的机器上构建任何东西。
ranwhat check:一次只读遍历,了解所有值得知道的内容同时运行 watch 和 clean,不改变任何东西。
在终端上,check、watch 和 clean 在读取时会在 stderr 上保留一行状态,统计每一轮的记录:indexing secrets(首次运行时为 (first run))、checking actions、looking for secrets。当 stderr 不是终端时,或使用 --json 时,不会在那里写入任何内容。
ranwhat watch:审计 Claude Code 及其他代理运行了什么读取 Claude Code 及其他编码代理已经写入磁盘的内容。没有包装器,没有代理,不在你的关键路径上。下面每个代理默认都会被读取,各自从其保存历史的位置读取(方括号中的变量会移动它,就像它移动代理本身一样);不在你机器上的代理会被跳过。
| 来源 | 位置 | 格式 |
|---|---|---|
| Claude Code | ~/.claude/projects/*/*.jsonl 以及每个会话的 subagents/**/agent-*.jsonl,或在设置时 $CLAUDE_CONFIG_DIR/projects 下的相同内容 | JSONL |
| Codex(CLI、IDE 扩展、桌面应用) | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl 和 archived_sessions/($CODEX_HOME);history.jsonl、shell_snapshots/ 及其 SQLite 线程索引会被搜索以查找机密 | JSONL;SQLite 只读;.jsonl.zst 只读,在 Python 3.14 上或使用 zstd 命令时 |
| Gemini CLI | ~/.gemini/tmp/<project>/chats/session-*.jsonl 及更早的 session-*.json($GEMINI_CLI_HOME) | JSONL、JSON |
| GitHub Copilot CLI | ~/.copilot/session-state/<session>/events.jsonl($COPILOT_HOME) | JSONL |
| Qwen Code | ~/.qwen/projects/<project>/chats/*.jsonl 及更早的 tmp/<hash>/chats/session-*.json($QWEN_RUNTIME_DIR、$QWEN_HOME) | JSONL、JSON |
| Grok Build | ~/.grok/sessions/<folder>/<session>/updates.jsonl($GROK_HOME) | JSONL |
| Droid | ~/.factory/sessions/*.jsonl,以及其下的 -<cwd>/*.jsonl 和 btw/*.jsonl($FACTORY_HOME_OVERRIDE) | JSONL |
| Kimi Code | ~/.kimi-code/sessions/<folder>/<session>/agents/*/wire.jsonl($KIMI_CODE_HOME) | JSONL |
| Kimi CLI | ~/.kimi/sessions/<folder>/<session>/wire.jsonl 和 context*.jsonl($KIMI_SHARE_DIR) | JSONL |
| Pi | ~/.pi/agent/sessions/--<cwd>--/*.jsonl($PI_CODING_AGENT_DIR) | JSONL |
| Muse Code | ~/.local/share/muse/sessions/YYYY/MM/DD/<session>/session.jsonl($XDG_DATA_HOME/muse) | JSONL |
| OpenClaw | $OPENCLAW_STATE_DIR/agents/*/agent/openclaw-agent.sqlite | SQLite,只读 |
Meta Muse 在 Meta 的云中运行,不在你的机器上保留任何内容,因此没有什么可读取的;Muse Code,Meta 的编码 CLI,受支持。 Grok Bot 将其历史保存在 xAI 的云中,即使是它在你的机器上运行的命令也是如此;Grok Build,xAI 的编码 CLI,受支持。当前的 Amp 将其线程保存在 ampcode.com 上。Cursor 是下一个,一旦其格式对照主要来源检查完毕。
九条规则:凭据访问、工具调用中的机密形态字符串、包发布、云资源变更、金融 API 调用、日志篡改、破坏性 git、递归删除,以及用 curl 上传的本地文件。 每个代理的工具调用都由相同的规则判定;每个操作都会说明是哪个代理运行的。
ranwhat watch --days 30
ranwhat watch --source codex # one agent (repeatable)
ranwhat watch --path codex=~/work/.codex # an agent's history elsewhere
ranwhat watch --json
ranwhat sources # every agent, where it looked, what it found
--root 和 --state-dir 仍然可用,作为 --path claude-code= 和 --path openclaw= 的名称。
ranwhat clean:在 Claude Code 及其他代理的记录中查找机密当代理运行 cat .env 时,输出会被写入记录:你的数据库密码、你的 JWT 密钥、你的提供商令牌,以明文形式,保存在 Claude Code 默认保留 30 天的文件中。
ranwhat clean # report, then open a review session
ranwhat clean --apply # mask everything without asking
clean 搜索每个代理的历史,并且只在代理允许它重写的文件中遮蔽值:一个普通的 JSONL、JSON 或文本文件,且在过去两分钟内没有任何东西写入过。数据库(Codex 的线程索引、OpenClaw 的代理数据库)和压缩文件是只读的:报告会列出每一个持有机密的文件,以及如何在代理中移除它。
扫描真实历史需要一段时间,因此会话会停留在它刚刚发现的内容上,而不是让你重新扫描才能对其采取行动:
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
每个发现都会说明它是在哪个项目中被发现的,并且当记录中指明时,还会说明它是从哪个文件中读出的,因为一个 64 字符的字符串如果不知道它从哪个 .env 逃逸出来就毫无用处:
* Stripe live secret key sk_…dc 32 chars seen 8x
read from api/.env
in /Users/you/Desktop/app
遮蔽不是补救。 在这里遮蔽一个值并不会使它不再暴露。它已经在磁盘上,并且已经存在于你无法控制的模型上下文中。轮换才是修复;遮蔽只是阻止它第二次泄露。报告会这样说明,而不是暗示安全。
只有当值旁边的键将其命名为机密,或者该值带有可识别的凭据形态时,才会遮蔽该值。占位符、模板文件和普通配置会被保留,已发布的文档示例(AWS 的 AKIAIOSFODNN7EXAMPLE)和明显的测试夹具(如 AKIA1234567890ABCDEF)也是如此。备份会保存到 ~/.ranwhat/backups,重写后的文件在替换原文件之前会被解析回来。
输入到命令中、旁边没有键的密码,会在命令接收它的地方被找到:mysql -pPASSWORD(以及 mysqldump、mysqladmin、mariadb)、sshpass -p、redis-cli -a、docker login -p、curl -u user:password、--password,以及 sqlcmd、mongosh、ldapsearch、htpasswd、keytool、ConvertTo-SecureString -AsPlainText 和 smbclient 中的相同位置。那里的变量,例如 -p$MYSQL_PWD,会被保留。
check 和 watch 会隐藏 clean 在你历史中任何地方、任何代理中找到的每一个机密,无论 --days 和 --source 怎么说,无论某个机密的副本出现在哪里:在 Codex 会话中读取的密码会在 Claude Code 输入它的地方被隐藏。为了在每次运行时无需读取每份记录就能知道它们,它们会在 ~/.ranwhat/known/(设置时则为 $RANWHAT_HOME/known/)中维护一个索引,每个 Claude Code 记录目录一个文件,并且仅当文件的大小或修改时间变化时才再次读取该文件。首次运行会读取全部内容,并会说明这一点。check 会从它自己的机密搜索中填充它,因此不会为查找机密而重复读取任何记录。
索引保存的是加盐指纹,绝不是机密:对于每个机密,保存其前六个字符的带密钥 BLAKE2b 哈希的 16 位(这是一个大量开头共享的标签,因此对它们的猜测最多只能缩小 16 位)、其长度,以及整个值和其掩码所保留指纹的带密钥哈希。密钥是随机的,每台机器生成一次,就放在索引旁边。这两个文件只有你可以读取。clean 在遮蔽每个机密之前会先将其加入,因此它遗漏的副本也会保持隐藏,无论会话如何结束。
如果索引被删除、损坏或丢失密钥,下一次运行会从记录中重新构建它。clean 已经遮蔽的机密无法通过这种方式再次得知:记录只保留其掩码的指纹。掩码遗漏的副本,如果它与其周围内容分开,并且在 check 或 watch 显示的命令中粘连,且长度不超过 64 个字符,那么它仍然会被隐藏。如果粘连到其他任何东西中,则不会。所以除非你要重新开始,否则请保留索引。
ranwhat scan:为代理凭据的能力打分只读地读取代理持有的凭据,并为决定暴露程度的三个方面打分。
| 维度 | 问题 |
|---|---|
| 权限 | 它被允许做什么? |
| 可观测性 | 你能重建一个具名的过去操作吗? |
| 可逆性 | 一个错误操作能被撤销吗? |
可观测性对总体结论具有否决权。一个无法重建自身工具调用的代理与最坏情况无法区分。
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
Google、GitHub、GitLab、Microsoft 365、Slack、Discord、Stripe、Shopify、HubSpot、Atlassian、Sentry 和 AWS 的能力目录。 无法识别的范围会按操作动词分类并标记为未分类,绝不假定为安全。
细粒度 GitHub 令牌或受限的 Stripe 密钥从 live 返回时会作为发现,而不是分数:两个提供商都不通过其 API 列出其权限。将它们作为该凭据的 scopes 复制到配置文件中,然后 scan 它。
ranwhat update:刷新能力目录ranwhat update 从 ranwhat 的 feed 刷新能力目录。它需要 Plus 订阅,现已上线,每个组织每月 €12(定价),并且只发送订阅令牌。
在终端上,scan、live 和 update --status 可能会在 stderr 上添加一行关于 feed 的暗色提示,且绝不与 --json 一起使用。RANWHAT_NO_HINTS=1 会将其关闭。