runningoffcode/agent-wormhole
GitHub: runningoffcode/agent-wormhole
一个用于检测和阻止自复制 prompt payload 写入 AI agent 配置文件的完整性监控与防护工具。
Stars: 0 | Forks: 0
# Agent Wormhole
[](https://github.com/runningoffcode/agent-wormhole/actions/workflows/ci.yml)
[](https://pypi.org/project/wormhole-guard/)
[](https://pypi.org/project/wormhole-guard/)
[](LICENSE)
[](pyproject.toml)
[](#no-telemetry)
[](corpus/)
**你的 agent 们正在互相通信。确保它们没有夹带私货。**
Agent 会生成 agent、交接工作、评论 issue 并读取彼此的输出。一个被攻破的
agent 不再仅仅是受害者,而会变成携带者——而另一端的助手对你的指令同样
言听计从。
这背后隐藏着一个无人认领的不对称性:你的供应商会保护它自己的
`settings.json`,但没有任何东西保护你的 `CLAUDE.md`、`AGENTS.md` 或
`.cursor/rules`,而且默认情况下你的 agent 可以随意写入所有这些文件。
```
$ wormhole scan .
CRITICAL SessionStart hook executes a script from an unusual path [AUTOSTART-002]
.claude/settings.json
`node .github/setup.js` runs unprompted on SessionStart. This survives
uninstalling the package that planted it.
HIGH Agent config not in baseline [BASELINE-003]
.cursor/rules/setup.mdc
This file was not present when the baseline was taken.
```
除 Python 3.8+ 外无任何依赖。无需账户、无需 API token、无网络调用。
你的 `CLAUDE.md` 永远不会离开本地机器。
## 为什么
**2026 年 6 月:Miasma 蠕虫瘫痪了 73 个 Microsoft GitHub 仓库。** 它
并未利用内存漏洞,而是写入了 agent 配置:
| 文件 | 机制 |
|---|---|
| `.claude/settings.json` | `SessionStart` hook → `node .github/setup.js` |
| `.gemini/settings.json` | 同上 |
| `.cursor/rules/setup.mdc` | `alwaysApply: true`,“运行安装脚本” |
| `.vscode/tasks.json` | `runOn: folderOpen` |
| `package.json` | 被劫持的 `test` 脚本 |
它瞄准了 15 个 AI 编码 agent,并且这种持久化**能在 `npm uninstall` 和
重装 agent 后存活**——配置文件的寿命比这两者都要长。它还在每次写入时
重新加密自身,因此对已知 payload 进行哈希匹配永远找不到它。
这五个锚点中有四个根本不需要模型参与。hook 触发仅仅是因为一个 session
启动了。这就是为什么此工具检查配置,而不仅仅是文本说明。
还有两件事使得这个缺口成为结构性的,而非偶然的:
- **Cursor 收到了警告却拒绝承担责任。** Pillar Security 的 Rules File
Backdoor(2025 年 2 月至 3 月披露)利用不可见的 Unicode 字符将指令隐藏在
`.cursor/rules` 中。Cursor 的回应是该风险应由用户自己承担。这就是你承担
该责任的方式。
- **沙盒机制未覆盖关键文件。** Claude Code 自己的文档声明
*"Read、Edit 和 Write 直接使用权限系统,而不是
通过沙盒运行"*,且默认对工作目录具有写访问权限。下面的研究
结果——沙盒隔离将攻击成功率降至零——并不适用于默认安装。
机制论文为
[arXiv:2603.15727](https://arxiv.org/abs/2603.15727)(预印本,2026 年 3 月,
2026 年 7 月修订):进行了 2,250 次试验,通过技能供应链
投毒攻击成功率达到 82%(在所有三个向量中总体成功率为 63%),启用沙盒隔离后
攻击成功率为 0%——而且在 **82 个公开索引的 agent 配置中,有 0 个**
启用了沙盒隔离。62% 的配置使用了 gateway 身份验证,但这
并不能阻止传播。
有效的防御方案已经存在,却没人在用。这个缺口是一个工具
问题,而这就是那个工具。参见 [MISSION.md](MISSION.md)。
## 安装
```
pipx install wormhole-guard
wormhole scan ~/your-project --blast-radius
```
发布包名为 `wormhole-guard`。PyPI 上的 `agent-wormhole` 是一个无关的
项目。命令和 import 包名都是 `wormhole`。
仅使用标准库,因此它也可以直接从检出的代码运行,完全不需要
安装步骤:
```
git clone https://github.com/runningoffcode/agent-wormhole
cd agent-wormhole
python3 -m wormhole scan ~/your-project --blast-radius
```
## 使用
预防——这些在 payload 落地之前运行:
```
wormhole init ~/project # harden + baseline + print the hooks
wormhole outbound --install # sends: refuse to pass a payload on
wormhole readguard --install # reads: PostToolUse + InstructionsLoaded
wormhole guard --install # writes: the PreToolUse hook
wormhole harden ~/project --apply # drop the write bit, block creation
wormhole harden ~/project --undo --apply # restore write permission
```
三道大门。`readguard` 覆盖接收的内容——抓取的页面、shell 输出、MCP
响应——这正是 2026 年所有公开披露的 agent 被攻破事件的真实
入侵方式。`outbound` 覆盖你的 agent 传递给子 agent、
对等节点或另一个团队的 bot 会读取的 issue 的内容。`guard` 和
`harden` 覆盖是否有任何东西可以持久化到下一个 session。
`outbound` 是唯一一个默认拦截的。入站内容根据定义是不受信任的,并且数量庞大,
因此这些规则保持保守。出站内容是由你自己的 agent 编写的,
因此 payload 出现在那里已经是异常情况——并且被拒绝的发送会显式报错,
而一旦发送出去,就会到达一个从未同意信任你的操作员那里。
检测与控制——这些在事后运行:
```
wormhole scan ~/project --blast-radius # payloads, posture, blast radius
wormhole baseline ~/project # fingerprint configs
wormhole verify ~/project # detect modification
wormhole watch --limit 20 # injection attempts in tool output
wormhole capture ~/project # preview capture (dry run)
wormhole capture ~/project --apply # capture, preserving originals
wormhole captured # list what has been contained
wormhole restore # pull one back out, byte-for-byte
wormhole insights # what the capture history reveals
wormhole handoffs # payloads in agent-to-agent tasks
wormhole corpus ./docs # documents before they are embedded
```
`handoffs` 和 `corpus` 覆盖了该工具最不擅长检测的两个向量,
有必要说明一下其局限性。传递给子 agent 的任务描述没有
拦截点——父 agent 在内存中将其组合——因此 `handoffs` 在
事后读取记录。向量存储没有标准格式,因此 `corpus`
在摄入*之前*扫描文档,这是文本
仍然是文本的最后一个时刻。
`guard` 默认发出警告。Block 模式仅拒绝 WORM-001 和 WORM-003——这
两条规则具有明确的结构特征且无语料库误报——因为拦截工具中的
规则缺陷会阻碍正常工作,而不是仅仅打印噪音。
`scan` 和 `watch` 在达到或超过 `--fail-on`(默认为 `high`)时
以非零状态退出,因此它们可以直接原样接入 CI。
## Wormhole
捕获的 payload 会进入 Wormhole,而不是被丢弃到回收站。
```
wormhole capture ~/project --apply # excise payloads, keep originals
wormhole captured # what has been captured
wormhole restore # pull one back out (false positive)
wormhole export ./samples # inert fixtures for rule development
```
删除 payload 会破坏回答感染后唯一重要问题所需的
证据:是什么写入了它,什么时候,以及它是否传播。它
还会将误报转化为数据丢失,这正是安全工具失去
用户的原因。因此,原始文件被逐字节保留,并附带完整的
来源记录,并且每次捕获都是可逆的。
清理工作迭代运行——清理、重新扫描、重复——因为 payload 可能
占据多个独立的块。AgentWorm 的双锚设计恰好使用
两个锚点,如果只移除第一个锚点,第二个锚点将保持活动状态,同时使
文件看起来已被处理。如果文件无法完全清理,运行将报告
`INCOMPLETE`,而不是宣称成功。
Wormhole 权限为 `0700`;payload 以 `0400` 权限存储并带有
`.quarantined` 后缀,因此其中的任何内容都不会被加载为
agent 配置或被执行。
## 它确切做了什么
这是一个**针对你的 agent 作为指令读取的文件的完整性监控器**。
关键部分并不关心 payload 说了什么。
| | | 在重写措辞后依然有效? |
|---|---|---|
| **预防** | `harden` 移除写入权限,并预先创建缺失的配置路径,使得 payload 也无法创建它们 | 是——不涉及规则 |
| **通知** | `baseline`/`verify` 对每个配置进行哈希处理;更改或未记录的文件即为一个发现 | 是——哈希与措辞无关 |
| **拒绝** | `guard` 通过 PreToolUse hook 检查挂起的写入,并可以拒绝该写入 | 部分——基于规则 |
| **检测** | 用于识别 payload 形状的内容规则、用于无人值守执行的 autostart 规则、用于评估能力的 posture 规则 | 否——可规避,用作分诊 |
| **控制** | `capture` 清理 payload,逐字节保留原始文件以供恢复 | 不适用 |
这种排序是刻意的。预防和完整性是持久的一半;
规则只是在其之上的便利。一个以规则数量为卖点的工具是在
最容易衰退的轴线上竞争。
将感染率降至零的控制手段是沙盒隔离,它存在于
你的 agent 框架中,而不是这里——并且根据 Anthropic 自己的文档,它默认不覆盖
Read/Edit/Write。该工具让这一缺口无法被忽视。
## 检测规则
**内容**——看起来像自我复制 payload 的文本:
| | |
|---|---|
| `WORM-001` | 自我复制指令(自我引用 + 复制动词 + 目标) |
| `WORM-002` | 指令覆盖措辞 |
| `WORM-003` | 凭据泄漏到外部目标 |
| `WORM-004` | 隐藏在 HTML 注释中的指令 |
| `WORM-005` | 零宽字符 |
| `WORM-006` | Unicode tag 块走私(不可见,模型可读) |
| `WORM-007` | 隐瞒指令(“不要告诉用户”) |
**Autostart**——在无提示且无模型参与的情况下执行的配置。这
是在实际传播中真正起作用的形状:
| | |
|---|---|
| `AUTOSTART-001` | 无人值守的 hook 下载并执行(`curl … \| sh`) |
| `AUTOSTART-002` | 无人值守的 hook 运行来自配置目录的脚本 |
| `AUTOSTART-003` | 无人值守的 hook 运行解释器 |
| `AUTOSTART-004` | 指示 agent 运行命令的总是应用(Always-applied)的 Cursor 规则 |
`PreToolUse` 被刻意不视为无人值守——它是因为
agent 已经在行动时才触发的,并且它是 `guard` 本身的发布方式。
**MCP 工具完整性**——这里唯一不接触磁盘的 artifact。服务器
在连接时响应 `tools/list`,它返回的名称、描述和
schema 被注入到模型的上下文中,在那里它们被读取为
指令。协议中没有任何内容对该答案进行签名,也没有任何内容要求
客户端重新检查它,因此服务器在你审查时可能是良性的,而一周后
即使没有任何文件更改,它也可能变得不同。
| | |
|---|---|
| `MCP-001` | 工具定义不再匹配记录的内容 |
| `MCP-002` | 获取基线时不存在的工具 |
| `MCP-003` | 记录的工具不再被声明 |
| `MCP-WORM-*` | 内容规则,应用于工具描述 |
`wormhole baseline` 与你的配置文件一起对它们进行指纹记录;`verify`
报告偏差。重新格式化的 JSON 不算作更改——只有名称、描述
和 schema 会被哈希。
**Posture**——如果 payload 到达,它能完成什么操作:不受限制的 shell
(`POSTURE-001`)、网络出口(`-002`)、缺少拒绝规则(`-003`)、可写的
配置(`-004`)、远程 MCP 服务器(`-005`)、已安装的技能(`-006`)。
**完整性**——`BASELINE-001/002/003`:已更改、缺失或未跟踪的配置。
这是能够捕获任何规则都未预料到的 payload 的一半。
**运行时**——`RUNTIME-*`:应用于 session 记录中工具输出的
相同内容规则,覆盖了从不接触磁盘的
供应链通道。
## 回归测试套件
```
$ ./loop/replay.sh
detected 15/15 clean 14/14 FN=0 FP=0
```
**这是一个回归测试套件,而不是检出率。** 它是在
为本仓库编写的 fixture 上测量的,因此它证明规则在更改后仍然按照
预期运行——仅此而已。任何人(包括我们自己)将其作为针对
真实攻击者的准确率进行引用,都是在夸大其词。基于规则的检测在构建上
就是可规避的:Trail of Bits 在不到
一小时内绕过了所有主要的技能扫描器,而仅仅改写措辞就足以击败已发布的分类器。
该套件确实强制执行了使规则可用的准则:
每个恶意 fixture 都附带一个**良性孪生体**,它保留了 payload 的
定罪表面特征,并且仅改变规则所依赖的属性。
关键词匹配器会在两个方向上对该对测试失败。如果规则在
孪生体上触发,它就不会被发布。这在发布前捕获了两个真正的
漏报和一个严重的误报。
良性部分是刻意设计的对抗性测试:一份威胁模型文档、一份写着
“在你创建的每个源文件中包含许可证头”的配置、一份 `.env`
指南、一个写入 `CHANGELOG.md` 的合法技能、一个运行
`git fetch` 的 `SessionStart` hook,以及本项目自己的
guard hook。
此外还针对 7 个真实项目进行了验证:0 发现。
## 免责声明
Apache 2.0,其中的责任条款值得阅读而不是想当然:
— [LICENSE](LICENSE),第 7 和 8 节
通俗地说:这是一个检测和加固工具,而不是保证。它可能会
漏掉它从未见过的 payload,它也可能对它见过 payload 判断失误,并且它
无法使 agent 变得安全。在你依赖它进行任何操作之前,请阅读[局限性](#limits),并且
无论何时都要将真正有效的控制手段——沙盒隔离——保留在
你的清单上。
Apache 2.0 还包含明确的专利授权(第 3 节),这就是为什么本项目
坚持使用它,而不是转向更宽松的简化许可协议。
## 无遥测
此工具读取你环境中最敏感的部分:prompt、权限、
与凭据相关的配置,以及你的 agent 视为
指令的文件内容。因此,它不会将其中任何内容发送到任何地方。
- 无需账户、无需 API token、任何时候都没有网络调用。
- 除 Python 标准库外无任何依赖,因此以后不会引入
可能改变这一情况的任何东西。
- 基线和捕获存储位于你机器上的 `~/.wormhole` 中。
- `wormhole insights` 在本地分析你自己的捕获历史。没有
全局数据流,这是刻意的——构建一个数据流恰好需要
此承诺所禁止的数据。
向 upstream 贡献 fixture 是一项独立的、刻意的操作(`wormhole
export`),并且根据策略,导出的内容是无害的:没有实时的 endpoint,没有
有效的 payload。
请验证它,而不是盲目相信它。整个工具大约有 4,000 行
无依赖的 Python 代码:
```
# 没有在任何地方导入网络客户端。这不会打印任何内容。
grep -rnE "^\s*(import|from)\s+(socket|urllib|http|requests|aiohttp)" wormhole/
```
## 支持的配置格式
`AGENTS.md`、`CLAUDE.md`、`GEMINI.md`、`.cursorrules`、`.cursor/rules/*.mdc`、
`.windsurfrules`、`.windsurf/rules/*.md`、
`.github/copilot-instructions.md`、`.github/instructions/*.instructions.md`。
权限分析目前最能理解 Claude Code 的 `settings.json`。
## 持续审计
```
loop/install-cron.sh # every 6h; --remove to uninstall
```
没有任何变化时保持静默;记录到 `~/.wormhole/logs/` 并在
被跟踪的配置被修改或出现 payload 时发出通知。
[loop/RESEARCH.md](loop/RESEARCH.md) 记录了如何在不降低
误报率的情况下添加新规则。
## CI
```
- uses: runningoffcode/agent-wormhole@v1
with:
fail-on: high
```
## MCP 服务器
允许 agent 审计自己的 posture。根据设计为只读——它只报告且从不
写入,因为 agent 可以要求修改其配置的安全工具
本身就是注入的目标。
```
{
"mcpServers": {
"wormhole": {
"command": "python3",
"args": ["-m", "wormhole.mcp_server"],
"cwd": "/path/to/agent-wormhole"
}
}
}
```
工具:`scan_agent_configs`、`check_integrity`、`blast_radius`、
`scan_session_history`。
## 局限性
坦诚地声明,因为夸大其词的安全工具比没有更糟糕:
- 正则表达式规则捕捉的是*形状*,而不是含义。新颖的措辞可以规避它们——这
就是为什么 `baseline`/`verify` 存在并且比规则覆盖率更重要的原因。
- `watch` 在事后读取记录。它告诉你注入尝试
已经到达了你的 agent;它不会拦截它。
- 这里没有任何东西可以从正在运行的 agent 中清除感染。`wormhole` 只清理文件。
- 目前尚未公开确认有蠕虫在传播中。其先决条件
如今已经存在,并且无论情况如何,posture 发现都是真实的。
## 贡献
每个新的检测规则都附带一个良性孪生体——一个讨论相同
攻击但不构成攻击的文件。如果规则在孪生体上触发,它就不会被发布。参见
[CONTRIBUTING.md](CONTRIBUTING.md) 和 [SECURITY.md](SECURITY.md)。
## 许可证
Apache 2.0。
标签:AI安全, API接口, Blue Team, Chat Copilot, DLL 劫持, DNS 反向解析, Python, 人工智能, 大语言模型, 无后门, 用户模式Hook绕过, 逆向工具, 静态扫描