ranwhat 上手实测:给 Claude Code 等 12 款 AI 编码代理装上"飞行记录仪",本地扫描越权操作与泄露密钥
当你把 Claude Code、Codex、Gemini CLI 这类 AI 编码代理接上自己的终端,它们拿到的其实是三样东西:你的 shell、你的密钥、你的仓库。代理执行过什么命令、读过哪些敏感文件、是否动过不可逆的操作,大多数开发者并没有留档的习惯。ranwhat 正是一款面向 **AI 编码代理安全 的开源工具——它把自己定位为"AI 代理的飞行记录仪 + 权限扫描器",直接读取各代理写在本机的历史记录,筛出少数真正值得关注的危险动作。整个过程本地运行、无需账户、无遥测、零依赖**,源代码在 GitHub 上以 MIT 协议公开。

一次典型的 ranwhat watch --days 90 输出如上:它会列出近 90 天内代理运行过的所有值得警惕的不可逆操作,并说明每条记录发生在什么时间、由哪个代理执行。官方文档里演示的这组样例中,2 条被判定为严重(critical)、2 条为高(high),包括读取 SSH 私钥、递归删除、发布 npm 包、强推主分支等——每一条都附带了人话解释,告诉你"为什么这件事值得知道"。
产品介绍
ranwhat 是一个用 Python 编写的命令行工具,只做两件事:**回放 AI 代理真实执行过的操作,扫描**这些代理手中凭据所代表的权限面。它不做包装器、不挂代理、不进入你的关键路径,而是直接读取各家代理写在自己历史目录里的记录文件。没有账户体系,不收集任何数据,也没有运行时依赖——在把它指向你的密钥之前,几乎没有需要审计的代码面。
官网与文档:https://ranwhat.com ,项目仓库:https://github.com/MatijaMiki/ranwhat ,许可证为 MIT,要求 Python 3.9 及以上版本。
核心功能
九条判定规则,覆盖 AI 代理的高危动作
ranwhat watch 的核心是一套规则引擎,针对代理的工具调用做逐条判定。九条规则分别是:
- 凭据访问:读取了以保存机密为唯一用途的文件(如
~/.ssh/id_rsa) - 工具调用中的机密形态字符串:命令或参数中出现符合凭据特征的密钥内容
- 包发布:向 npm 等公开注册表推送内容,具备供应链触达能力且通常不可逆
- 云资源变更:创建、修改或删除云基础设施
- 金融 API 调用:触达支付、交易类接口
- 日志篡改:删除或修改审计痕迹本身
- 破坏性 git 操作:改写历史、强推、删除分支
- 递归删除:
rm -rf类批量删除,且严重性跟随目标而非动词——删/tmp/x静默、删~/Documents记为高危、删/记为严重 - 用 curl 上传本地文件:把本机文件送出到外部地址
每个代理的工具调用都按同一套规则判定,每条结果都会标明是**哪个代理**跑出来的。
默认读取 12 款 AI 编码代理的历史
下表是各代理的默认读取位置(方括号中的环境变量会同时移动代理和数据位置;不在你机器上的代理会被自动跳过):
| 代理 | 历史记录位置 | 格式 |
|---|---|---|
| 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 云中、本机不留数据,因此没有可读内容;Meta 的编码 CLI **Muse Code 受支持。Grok Bot 的历史存在 xAI 云端(即使是它在你机器上执行的命令),其编码 CLI Grok Build 受支持**。当前的 Amp 把线程存在 ampcode.com 上;Cursor 的支持已在规划中,待其格式对照一手来源核验后上线。
ranwhat check:一次只读遍历拿到全部信息
check 相当于把 watch 和 clean 合并跑一次,纯只读、不改任何东西,适合作为每日例行巡检:
uvx ranwhat check
在终端上运行 check、watch、clean 时,stderr 会保留一行状态信息,统计每轮读取的记录数:indexing secrets(首次运行会标注 (first run))、checking actions、looking for secrets。当 stderr 不是终端、或使用了 --json 时,这行状态不会写入。
ranwhat watch:审计代理实际跑过什么
ranwhat watch --days 30
ranwhat watch --source codex # 只审某一个代理(可重复使用)
ranwhat watch --path codex=~/work/.codex # 指定代理历史存放在别处的路径
ranwhat watch --json
ranwhat sources # 列出每个代理:在哪找的、找到什么
关于时间范围:Claude Code 默认会删除超过 cleanupPeriodDays(默认 30 天)未使用的会话记录,因此 --days 90 能覆盖更多内容的情况包括:你调高过该值、恢复的会话内含更早的操作,或在 Claude Desktop / Cowork 中启动或最后继续的会话(Claude Code v2.1.248 及更高版本对这类会话默认不限保留时长)。
ranwhat clean:在代理记录中翻出明文机密
代理执行 cat .env 时,输出内容会被写进会话记录——你的数据库密码、JWT 密钥、各平台 token,以明文形式躺在 Claude Code 默认保留 30 天的文件里。clean 负责把它们找出来:
ranwhat clean # 先出报告,再进入交互式评审会话
ranwhat clean --apply # 不问直接遮蔽所有命中项
扫描真实历史需要一些时间,所以评审会话会停留在刚发现的内容上,不需要重新扫描即可逐项处理:
ranwhat> list # 再次列出全部发现
ranwhat> show 3 # 查看第 3 项出现在哪里、该轮换什么
ranwhat> mask 3 # 只遮蔽这一项
ranwhat> mask all # 遮蔽列出的所有项
ranwhat> keep 3 # 保留这一项不动
ranwhat> rotate # 按提供商分组列出需要轮换的内容
每条发现都会标明它出现在哪个项目里,并在记录指明时给出机密是从哪个文件读出来的——不知道一串 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,重写后的文件会先解析校验再替换原文件。Codex 的线程索引、OpenClaw 的代理数据库等数据库与压缩文件是只读的,报告会列出每个持有机密的文件并说明如何在对应代理中移除。
对"裸输入"的密码,clean 会在命令接收它的位置识别: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 在你的历史中、任何代理的记录里找到过的每一个机密——无论在 Codex 会话里读到的密码,还是 Claude Code 输入过的同一个密码,显示时都会被遮盖。为免每次运行都重扫全部记录,这些机密以索引形式维护在 ~/.ranwhat/known/(设置了 $RANWHAT_HOME 时为其下的 known/),每个 Claude Code 记录目录一个文件,仅在文件大小或修改时间变化时才会重新读取。
索引里**只保存加盐指纹,绝不保存机密本身**:每项机密记录其前六个字符的带密钥 BLAKE2b 哈希的 16 位、长度,以及整个值与其掩码保留指纹的带密钥哈希。密钥每台机器随机生成一次,与索引放在同一目录,两个文件只有你可读。clean 在遮蔽每个机密之前会先把它加进索引,因此即便会话中途退出,被遗漏的副本也会保持隐藏。
索引丢失或损坏后,下次运行会从记录中重建。但 clean 已经遮蔽过的机密无法通过这种方式再被识别——记录里只剩掩码的指纹。掩码漏掉的副本,若与周围内容分离、或粘在 check/watch 显示的命令中且不超过 64 个字符,仍会被隐藏;粘在其他内容里则不会。所以除非打算彻底重来,请保留索引。
ranwhat scan:给代理凭据的"能力面"打分
scan 只读地审视代理持有的凭据,从三个维度打分:
| 维度 | 核心问题 |
|---|---|
| 权限(Authority) | 它被允许做什么? |
| 可观测性(Observability) | 你能否重建一次具名的历史操作? |
| 可逆性(Reversibility) | 一次误操作能否被撤销? |
可观测性对总体结论拥有一票否决权:一个无法重建自身工具调用的代理,与最坏情况无法区分。
能力目录覆盖 Google、GitHub、GitLab、Microsoft 365、Slack、Discord、Stripe、Shopify、HubSpot、Atlassian、Sentry 和 AWS。无法识别的 scope 会按操作动词分类并标记为"未分类",**绝不假定为安全**。
使用证据分三种状态分别报告,因为合并它们会让报告自相矛盾:
| 状态 | 含义 |
|---|---|
| 已验证 | 从提供商自己的审计追踪中拉取 |
| 自我声明 | 在配置文件中声明,未独立拉取 |
| 未验证 | 完全没有证据,且不假定 scope 安全 |
使用拉取目前支持 AWS IAM service-last-accessed、Stripe events、Google Admin SDK、GitHub 组织审计日志;Slack 没有使用拉取能力,因此 Slack 的使用记录只能是自我声明或未验证。
ranwhat update 与 ranwhat hook install
ranwhat update 用于刷新能力目录,需要 Plus 订阅(每个组织每月 €12),且只发送订阅令牌。ranwhat hook install 则是唯一的"事前"机制:它在 Claude Code 设置里注册一个 PreToolUse hook,此后每次被 watch 规则评为高危或严重的工具调用,都必须先经过你的确认才能执行——每次判定约 0.2 秒,在本机完成、不外发任何数据,hook 本身故障时会放行调用(fail open),也可用 --mode deny 直接拒绝严重级调用、--scope project 把规则写入仓库的 .claude/settings.json 供团队共享。
技术优势
误报控制被当作核心功能来打磨。 一个天天误报的监视器一天内就会被关掉,而被关掉的监视器什么也记录不到。因此下列内容明确不算操作:
| 场景 | 原因 |
|---|---|
grep "rm -rf" src/ |
搜索字符串不等于执行它 |
python3 -c "print('rm -rf /')" |
载荷是 Python 源码,不是 shell |
echo "rm -rf /" |
echo 的参数是字面文本 |
cat > f.sh <<'EOF' … EOF |
heredoc 主体是正在写入的数据 |
git rm --cached x |
只是取消暂存,不触碰工作树 |
# rm -rf ~/x |
注释 |
rm -rf build、rm -rf /tmp/x |
删构建输出不算事件 |
唯一的例外是 bash -c:它的载荷确实是 shell,解析器会递归进入。官方记录过这段演进:第一个 watch 构建在 6 条发现中误报了 3 条;后来的版本按目标给删除操作排序后,同一台机器 90 天的历史从 15 条发现收敛到 4 条,且 4 条全部属实。
默认离线、最少联网。 无需账户即可使用。live 和 --pull-usage 只向签发对应令牌的提供商发起询问,update 只拉取目录,其余一切本地读取、不发送任何内容。提供商凭据在一次调用期间保存在内存中、绝不落盘;ranwhat 存储的唯一令牌是你主动执行 update --save-token(权限 0600)保存的订阅令牌。实时内省只与凭据自己的签发者通信,扫描从不行使任何权限,也不要求写入权限的令牌。
报告权限收口。 报告以 600 权限写入,且绝不经过符号链接——因为一份报告映射了代理的整个权限面,这份信息对攻击者同样有价值。
如实汇报边界。 目前没有读取的内容包括:Claude Code 存在 <session>/tool-results/ 的大型工具输出、~/.claude/history.jsonl、OpenClaw 的压缩事件(event_zstd)及其状态目录下 agents/<agentId>/sessions/cold/ 的冷记录归档。一次没有读到任何内容的运行会明确告诉你"没有检查任何内容",而不是说"没有发现任何内容"。
诚实的开发状态。 项目自我定位为 Alpha:本地工具已发布并经过测试,托管收集与证据留存尚未构建。测试套件以不变量而非预期输出编写,多数测试的存在是因为文档页面上的某句话曾经出过错。
使用方式与部署教程
方式一:零安装试运行(不装任何东西,跑完即走):
uvx ranwhat check
方式二:安装到 PATH:
pipx install ranwhat
# 或
pip install ranwhat
安装后可以先看演示,在内置样例上体验权限扫描的效果:
ranwhat demo
凭据扫描完整流程:
ranwhat scan profile.json --html report.html
read -rs RANWHAT_GITHUB_TOKEN # 粘贴:不回显、不进历史
export RANWHAT_GITHUB_TOKEN
ranwhat live # 只读内省
read -rs RANWHAT_STRIPE_TOKEN
export RANWHAT_STRIPE_TOKEN
ranwhat scan profile.json --pull-usage
安全地传令牌:令牌请通过环境变量传递,不要写在命令行里——argv 中的任何内容都会被本机其他用户通过进程表读到,并写进你的 shell 历史。先 read -rs 读入再 export,因为一行内的 VAR=x ranwhat ... 仍会把值放进该 shell 自己的命令行,而手打 export VAR=x 会进入历史记录。--stripe env:MY_VAR 和 --stripe -(从 stdin 读一行)是备选方案;把令牌作为标志值传入仍然可用,但会打印一条警告告诉你为什么不该这么做。
PreToolUse hook(可选):
uv tool install ranwhat # 或 pipx install ranwhat;uvx 会被拒绝
ranwhat hook install # 写入 ~/.claude/settings.json 或 $CLAUDE_CONFIG_DIR 下的同名文件
ranwhat hook status
ranwhat hook uninstall
另外几个实用开关:--root PATH 读取任意其他目录作为 Claude Code 记录源(等效于 --path claude-code=PATH),--state-dir 之于 OpenClaw 同理;在终端上运行 scan、live、update --status 时,stderr 可能出现一行关于目录源的暗色提示,设置 RANWHAT_NO_HINTS=1 可关闭。
应用场景
- 日常自检:把
uvx ranwhat check加进每周例行,快速确认本周代理有没有碰过私钥、发布过包或强推过分支。 - 多代理环境统一审计:团队里有人用 Claude Code、有人用 Codex、有人用 Qwen Code 或 Kimi CLI 时,一条命令覆盖全部历史,每条记录标明来源代理。
- 机密泄露排查:怀疑
.env内容进过模型上下文后,用ranwhat clean定位所有副本、按提供商分组轮换,并借助机密索引确保后续输出中不再回显。 - 凭据权限治理:用
ranwhat scan给 CI 机器人或代理持有的 token 打分,结合"已验证 / 自我声明 / 未验证"三态证据,优先收窄那些既高危又无审计追踪的凭据。 - 事前拦截:对不常盯终端的场景,
ranwhat hook install --mode deny让严重级调用直接被拒,高危级调用弹出人工确认。
总结
ranwhat 把"AI 编码代理安全"这件事拆成了三个可执行的动作:**回看(watch/check)、清理(clean)、量化**(scan),外加一个可选的事前确认(hook)。它不承诺替你管住代理,只保证你能看见代理做过什么、手中凭据能做什么——并且把这些信息留在你自己的机器上。目前项目处于 Alpha 阶段,本地工具可用,托管收集能力尚未推出。如果你已经在用 Claude Code 或其他 AI 编程助手,花一分钟跑一条 uvx ranwhat check,大概率会比想象中更有收获。
- 官网与文档:https://ranwhat.com
- GitHub 仓库:https://github.com/MatijaMiki/ranwhat