agentwatchdog

观测记录

这台机器上没有任何东西,有权决定自己要不要被审计。

agentwatchdog 从 agent 外部观察——在 /proc 里,“配不配合”不是一个变量。它绝不读取你的 prompt,而且有一条测试专门在它读了的时候失败。

扫描 · 4 个进程
/proc/2759430/cmdline claude -p "周五之前把预发数据库的凭据轮换掉" --model opus secret_flags-p 后面跟的是 prompt
/proc/821321/cmdline codex exec "给复盘会整理一份事故时间线" --sandbox read-only 位置参数 · 默认拒绝codex 的 prompt 就是裸参数
/proc/3104882/cmdline aider -m "修一下鉴权那个 bug" --api-key sk-EXAMPLE-NOT-A-REAL-KEY-000 secret_flags · 凭证形态两条规则各盖一次
/proc/3104960/cmdline spindle chat "起草迁移方案" --task "internal" 无指纹一个从没见过的 agent——所有位置参数照样全部拒绝

命令行是合成的,规则是真的。工具本身从不写下原始字符串——脱敏发生在落盘之前,所以你机器上并不存在一个可供还原的“之前”。

什么都不写,什么都不发。可以直接在你不放心的那台机器上跑。

缺口

你现有的工具,需要被监控方配合才有效。

agent 内部的可观测方案——OTel exporter、hook、wrapper 命令、代理——只在 agent 愿意配合时工作。不设那个环境变量、不装那个 hook、直接调用二进制,它就瞎了。而跑飞的 cron、配错的服务单元、不该出现在这里的账号,恰恰是永远不会配合的那些。

观察位置
agent 进程内部
宿主机的 /proc
能否被绕过
能——不配合即失效
不能。 进程存在,就会被看见
依赖
collector、Docker、Node 包
无。Python 3 标准库与 systemd
prompt 内容
部分工具逐字读取会话记录
绝不读取;落盘之前先脱敏
回答什么
token、成本、会话细节
谁在跑什么、被谁拉起、表现如何
服务于
键盘前的开发者
这台机器的运维者

两者都值得有。想要按 token 计费的仪表盘,请用 OTel 系的工具——本项目不是那个。这里做的是这台机器的审计日志。

检测器

六件值得把你叫醒的事。

每条告警都带着下一步该敲的命令。重复告警按“情形”而不是按“扫描次数”抑制——一个账号跑了五十个 agent 是一条结论,因为每六十秒响一次的监控,最终会被人关掉。

unexpected_user

严重

白名单 ALLOWED_USERS 之外的账号跑了 agent。告警携带 loginuid,于是“alice 通过 sudo 以 root 身份跑了 agent”是一个事实,而不是一个谜。

parent_spawn_storm

严重

同一个父进程在持续拉起 agent。人类不会这么干——这是重启循环,而且每次尝试都在计费。告警携带完整祖先链,因为 agent 是症状,父进程才是病灶。

user_high_frequency

警告

某个账号启动 agent 的速度,超过了人手输入的可能。

long_running_process

警告

非交互 agent 超过了时限。交互式会话与 SDK 会话被豁免:一个有人正坐在前面的会话不是挂死进程,把它当挂死处理,这个检测在它本该服务的机器上就废了。

high_cpu · high_mem

警告

持续性的资源异常。用生命周期均值并设了最小采样年龄——否则每个 agent 启动时都会飙的那一下,会变成每个 agent 都触发一次的告警。

agents_during_high_load

警告

多个 agent 挤在一台已经吃力的机器上。没有任何单个进程有错,而这正是别的工具都不会报它的原因。

隐私契约

五条承诺,每条都挂着一个违约即失败的测试。

这个工具靠读命令行吃饭。这件事只有在约束被强制执行、而不是被口头描述时才可接受——所以下面每一条都接着一个测试,CI 把它们作为独立 job 运行,失败不可能被忽略。

test_environ_is_never_opened

绝不读取环境变量。

任何代码路径都不会打开 /proc/PID/environ。测试记录一次扫描期间的每一次 open(),该文件一旦出现即失败。住在环境变量里的 API key,不可能经由这个工具泄露。

test_prompt_never_survives_redaction

prompt 永不落盘。

命令行在写入之前就按 agent 各自的规则脱敏——因为每家 CLI 放 prompt 的位置都不一样。

test_unknown_flag_is_assumed_boolean

位置参数默认拒绝。

一个参数只有在指纹明确认可它是已知子命令时才能留下。认不出的 flag 一律按布尔量处理,于是它后面那个 token 也会被盖住。比这个版本更晚发布的 agent 泄不出任何东西,只是记录得更粗略。

test_credentials_are_redacted_in_any_position

凭证形态在任何位置都会被盖住。

各家 key 前缀、JWT、长不透明 token,即使出现在没有任何规则预料到的位置也会被打码——而那恰恰是有人把命令粘错时凭证会去的地方。

test_export_reports_only_permitted_keys

允许离开主机的数据,从构造上就是匿名的。

摘要是按固定形状从零拼装的,不是从事件里过滤字段得来的,之后还要在运行时对照 key 白名单校验。证明不了匿名的导出会被拒绝,而不是被发布。

跑一下 agentwatchdog selftest,看这些承诺对你机器上真实运行的 agent 是否成立——不是对我们的机器。

agents.d

检测是数据,不是代码。

每个 agent 是一份 JSON 指纹,描述如何识别它、以及它把 prompt 放在哪。运维者可以在 /etc/agentwatchdog/agents.d 里添加或修正指纹,不必等新版本;而“一份指纹 + 一条脱敏测试”是这个项目最欢迎的 pull request。

Agentflag 表prompt 在哪
Claude Code已对真机核验裸位置参数,以及 -p
OpenAI Codex CLI已对真机核验位置参数,顶层与 exec 之后都有
aider已对 0.86.2 核验-m、--message、--msg
Gemini CLI按文档编写-p、--prompt
OpenCode按文档编写run 之后的位置参数

真机核验抓到的两个坑,可以作为“别凭记忆写这种表”的佐证。其一,Codex 在顶层就接受位置 prompt,不只在 exec 之后。其二,Codex 的 -p 是 --profile,而 Claude Code 的 -p 是 prompt——把一家的规则搬给另一家,工具会认真地把 profile 名打码,然后把真正的 prompt 写进磁盘。两个坑现在都是回归测试。

安装

先看,再装。

一个文件,无需构建,除 python3 外零依赖。仅支持 Linux——因为这套设计本身就是 /proc。

1 · 看

$ curl -fsSLO https://github.com/Ymakercc/agentwatchdog/releases/latest/download/agentwatchdog.pyz
$ chmod +x agentwatchdog.pyz
$ ./agentwatchdog.pyz dry-run
# 不写文件、不发消息、不更新任何状态

在你自己的机器上,看到一次扫描的完整输出——而此时工具还什么都没做过。

2 · 验

$ ./agentwatchdog.pyz selftest

对全部内置指纹、以及你正站着的这台机器上真实运行的命令行,验证脱敏规则。

3 · 装

$ sudo mv agentwatchdog.pyz /usr/local/bin/agentwatchdog
$ sudo agentwatchdog install
# systemd timer、配置、日志轮转。在 NOTIFY 另行配置之前,
# 告警只留在本地。

把它指向你最没底的那台机器。