首页 渗透工具 正文

ranwhat 上手实测:给 Claude Code 等 12 款 AI 编码代理装上"飞行记录仪",本地扫描越权操作与泄露密钥

摘要

当你把 Claude Code、Codex、Gemini CLI 这类 AI 编码代理接上自己的终端,它们拿到的其实是三样东西:你的 shell、你的密钥、你的仓库。代理执行过什么命令、读过哪些敏感文件、是否动过不可逆的操作,大多数开发者并没有留档的习惯。ranwhat 正是一款面向 **AI 编码代理安全 的开源工具——它把自己定位为AI 代理的飞行记录仪...

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

ranwhat watch 终端审计输出,展示凭据访问、递归删除、包发布、破坏性 git 操作四类高危行为

一次典型的 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 的核心是一套规则引擎,针对代理的工具调用做逐条判定。九条规则分别是:

  1. 凭据访问:读取了以保存机密为唯一用途的文件(如 ~/.ssh/id_rsa)
  2. 工具调用中的机密形态字符串:命令或参数中出现符合凭据特征的密钥内容
  3. 包发布:向 npm 等公开注册表推送内容,具备供应链触达能力且通常不可逆
  4. 云资源变更:创建、修改或删除云基础设施
  5. 金融 API 调用:触达支付、交易类接口
  6. 日志篡改:删除或修改审计痕迹本身
  7. 破坏性 git 操作:改写历史、强推、删除分支
  8. 递归删除:rm -rf 类批量删除,且严重性跟随目标而非动词——删 /tmp/x 静默、删 ~/Documents 记为高危、删 / 记为严重
  9. 用 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,大概率会比想象中更有收获。

收藏 0
评论
博主关闭了评论
友情链接