@aimarket/warden 0.8.1 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -5
- package/README-es.md +27 -9
- package/README-fr.md +28 -9
- package/README-ru.md +28 -9
- package/README-zh.md +23 -9
- package/README.md +27 -9
- package/dist/classifier.d.ts +50 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +164 -0
- package/dist/classifier.js.map +1 -0
- package/dist/claude-hook.d.ts +53 -0
- package/dist/claude-hook.d.ts.map +1 -0
- package/dist/claude-hook.js +192 -0
- package/dist/claude-hook.js.map +1 -0
- package/dist/histor.d.ts +69 -0
- package/dist/histor.d.ts.map +1 -0
- package/dist/histor.js +82 -0
- package/dist/histor.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/jcs.d.ts +8 -0
- package/dist/jcs.d.ts.map +1 -1
- package/dist/jcs.js +25 -5
- package/dist/jcs.js.map +1 -1
- package/dist/mcp-client.d.ts +58 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +451 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp-rpc.js +1 -1
- package/dist/mcp-server.js +8 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +7 -6
- package/dist/mcp-tools.js.map +1 -1
- package/dist/mtl.d.ts +28 -0
- package/dist/mtl.d.ts.map +1 -0
- package/dist/mtl.js +77 -0
- package/dist/mtl.js.map +1 -0
- package/dist/pin-store.js +3 -3
- package/dist/pin-store.js.map +1 -1
- package/dist/pinning.d.ts +18 -2
- package/dist/pinning.d.ts.map +1 -1
- package/dist/pinning.js +34 -7
- package/dist/pinning.js.map +1 -1
- package/dist/scan-cli.d.ts +3 -0
- package/dist/scan-cli.d.ts.map +1 -0
- package/dist/scan-cli.js +36 -0
- package/dist/scan-cli.js.map +1 -0
- package/dist/scan-config.d.ts +72 -0
- package/dist/scan-config.d.ts.map +1 -0
- package/dist/scan-config.js +239 -0
- package/dist/scan-config.js.map +1 -0
- package/dist/scan-lock.d.ts +46 -0
- package/dist/scan-lock.d.ts.map +1 -0
- package/dist/scan-lock.js +112 -0
- package/dist/scan-lock.js.map +1 -0
- package/dist/scan-report.d.ts +21 -0
- package/dist/scan-report.d.ts.map +1 -0
- package/dist/scan-report.js +273 -0
- package/dist/scan-report.js.map +1 -0
- package/dist/scan.d.ts +137 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +534 -0
- package/dist/scan.js.map +1 -0
- package/dist/static-scan.d.ts +1 -1
- package/dist/static-scan.d.ts.map +1 -1
- package/dist/static-scan.js +262 -12
- package/dist/static-scan.js.map +1 -1
- package/dist/tool-list.d.ts +34 -0
- package/dist/tool-list.d.ts.map +1 -0
- package/dist/tool-list.js +57 -0
- package/dist/tool-list.js.map +1 -0
- package/dist/wrap-cli.d.ts.map +1 -1
- package/dist/wrap-cli.js +6 -4
- package/dist/wrap-cli.js.map +1 -1
- package/dist/wrap-state.d.ts +3 -0
- package/dist/wrap-state.d.ts.map +1 -1
- package/dist/wrap-state.js +7 -4
- package/dist/wrap-state.js.map +1 -1
- package/dist/wrap.js +50 -42
- package/dist/wrap.js.map +1 -1
- package/docs/gates.es.md +59 -10
- package/docs/gates.fr.md +59 -10
- package/docs/gates.md +55 -10
- package/docs/gates.ru.md +59 -11
- package/docs/gates.zh.md +13 -7
- package/docs/scan.es.md +184 -0
- package/docs/scan.fr.md +184 -0
- package/docs/scan.md +184 -0
- package/docs/scan.ru.md +184 -0
- package/docs/scan.zh.md +184 -0
- package/docs/scanner-comparison.es.md +218 -0
- package/docs/scanner-comparison.fr.md +223 -0
- package/docs/scanner-comparison.md +209 -0
- package/docs/scanner-comparison.ru.md +214 -0
- package/docs/scanner-comparison.zh.md +135 -0
- package/package.json +5 -3
package/docs/scan.zh.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# 扫描 MCP 客户端启动的服务器
|
|
2
|
+
|
|
3
|
+
> 🌐 [English](scan.md) · [Русский](scan.ru.md) · [Español](scan.es.md) · [Français](scan.fr.md) · **中文**
|
|
4
|
+
|
|
5
|
+
`warden-mcp scan` 读取你已有的 MCP 配置,连接其中启动的每个服务器,并在模型看到工具定义之前对其进行审查。它与 [`wrap`](../README-zh.md) 和[库](integration.zh.md)使用同一条关卡链,只是对你的配置运行一次,而不是挡在单个服务器前面。
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx -y @aimarket/warden@0.9.0 scan
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
WARDEN scan 0.9.0 · ruleset 10 sha256-lJuKKKV5mtru… · block at high
|
|
13
|
+
read .mcp.json (claude-code, 3 servers)
|
|
14
|
+
|
|
15
|
+
✓ allow notes claude-code 1 tool · score 0.90
|
|
16
|
+
✗ BLOCK evil-notes claude-code 1 tool · score 0.00 · TOOL_DEF_EXFIL(notes) TOOL_DEF_SECRET_REQUEST(notes)
|
|
17
|
+
! error broken claude-code could not start: spawn /nonexistent/bin/server ENOENT
|
|
18
|
+
|
|
19
|
+
3 servers: 1 allowed, 1 blocked, 1 not checked, 0 skipped.
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
无需账号、无需 API 密钥、不调用模型。唯一的网络流量是发往你配置中的服务器;如果传入 `--histor`,还会发往 HISTOR 日志。
|
|
23
|
+
|
|
24
|
+
## 查找位置
|
|
25
|
+
|
|
26
|
+
不带参数时,扫描下表中所有存在的文件。传入文件则只扫描这些文件;`--project` 只扫描工作目录中的项目文件;`--client NAME` 只扫描一个客户端。
|
|
27
|
+
|
|
28
|
+
| 客户端 | 项目文件 | 用户文件 |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Claude Code | `.mcp.json` | `~/.claude.json`(用户级服务器和本项目的服务器) |
|
|
31
|
+
| Claude Desktop | — | 应用配置目录中的 `claude_desktop_config.json` |
|
|
32
|
+
| Cursor | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
|
|
33
|
+
| VS Code | `.vscode/mcp.json`(JSONC) | 用户设置目录中的 `mcp.json` |
|
|
34
|
+
| Windsurf | — | `~/.codeium/windsurf/mcp_config.json` |
|
|
35
|
+
|
|
36
|
+
配置中通过 `warden-mcp wrap` 启动的服务器,会按包装器背后的服务器来扫描,并使用 `wrap` 所用的 pin id,因此相对已批准快照的漂移会出现在报告中。已禁用的条目、需要客户端向用户索取 `${input:…}` 值的条目,以及既没有 `command` 也没有 `url` 的条目,都会列为已跳过,绝不会被悄悄丢弃。
|
|
37
|
+
|
|
38
|
+
## 启动服务器
|
|
39
|
+
|
|
40
|
+
为了读取 stdio 服务器的工具,`scan` 会用配置中的命令、参数和环境启动它——与客户端的做法完全相同——并在 `tools/list` 之后停止它。它从不调用工具。但程序仍然会被执行。在不希望这样的场合,例如针对 pull request 的 CI 中,`--no-launch` 只审查启动命令:threat feed 中的命令记录,以及 lock 文件中的启动身份。
|
|
41
|
+
|
|
42
|
+
远程服务器(`url`,streamable HTTP 或较旧的 HTTP+SSE)会通过网络、带着配置中的请求头进行查询。`--public-only` 会拒绝解析到回环、私有、链路本地或云元数据地址的服务器,检查的是实际连接的地址。不跟随重定向。
|
|
43
|
+
|
|
44
|
+
## 输出与退出码
|
|
45
|
+
|
|
46
|
+
| 选项 | 输出 |
|
|
47
|
+
|---|---|
|
|
48
|
+
| (默认) | 在 stdout 输出表格 |
|
|
49
|
+
| `--json` | 在 stdout 输出 JSON 报告 |
|
|
50
|
+
| `--json-file FILE` | 将 JSON 报告写入文件,同时输出表格 |
|
|
51
|
+
| `--sarif FILE` | 用于 GitHub code scanning 的 SARIF 2.1.0;只包含会阻止的发现,定位到配置中该服务器所在的行 |
|
|
52
|
+
| `--markdown FILE` | 带可折叠详情的摘要,用于 `$GITHUB_STEP_SUMMARY` 或 PR 评论 |
|
|
53
|
+
|
|
54
|
+
退出码 `0`:没有任何阻止。`1`:有服务器被阻止,或在 `--fail-on-error` 下有服务器无法检查。`2`:用法或配置错误。`--fail-on SEVERITY` 调整阻止阈值;`--policy FILE` 接受与 `wrap` 相同的严格策略文件。
|
|
55
|
+
|
|
56
|
+
启动命令和 URL 中的凭据显示为 `***`。Markdown 报告中的工具描述放在该文本无法闭合的代码块里。
|
|
57
|
+
|
|
58
|
+
## Lock 文件:审查定义,而不只是命令
|
|
59
|
+
|
|
60
|
+
配置中的一行说明启动哪个程序,却不说明这个程序会对你的模型说什么。Lock 文件记录后者,好让 pull request 把它展示出来。
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
warden-mcp scan --project --lock warden.lock.json --update-lock # 先读完改动再运行
|
|
64
|
+
git add .mcp.json warden.lock.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`--update-lock` 为每个服务器写入启动身份和完整的工具定义,并拒绝记录 WARDEN 阻止的服务器。对未变化的服务器再次运行,文件不会改变。只使用 `--lock` 时:
|
|
68
|
+
|
|
69
|
+
- lock 中没有的服务器会被阻止(`LOCK_MISSING`);
|
|
70
|
+
- 启动方式改变的服务器会被阻止(`SERVER_IDENTITY_DRIFT`);
|
|
71
|
+
- 现在声明了不同工具的服务器会被阻止(`TOOL_DEF_DRIFT`),Markdown 报告以名称、描述和 schema 的 diff 展示改动。
|
|
72
|
+
|
|
73
|
+
没有任何配置启动的 lock 条目会被列出,并在下次更新时删除。
|
|
74
|
+
|
|
75
|
+
## GitHub Action
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
name: MCP servers
|
|
79
|
+
on: [pull_request]
|
|
80
|
+
permissions:
|
|
81
|
+
contents: read
|
|
82
|
+
security-events: write # 仅 upload-sarif 需要
|
|
83
|
+
jobs:
|
|
84
|
+
warden:
|
|
85
|
+
runs-on: ubuntu-latest
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
- uses: alexar76/warden@v0.9.0
|
|
89
|
+
with:
|
|
90
|
+
upload-sarif: 'true'
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
默认情况下,它读取项目文件,不启动 stdio 服务器(该程序由 pull request 决定),拒绝非公网地址,存在 `warden.lock.json` 时使用它,写入作业摘要,并在有服务器被阻止时失败。输入:`config`、`working-directory`、`lock`、`launch-stdio`、`public-only`、`fail-on`、`histor`、`classifier-url`、`classifier-model`、`classifier-blocks`、`sarif`、`upload-sarif`、`version`。输出:`blocked`、`servers`、`sarif`。在生产 workflow 中请按提交 SHA 固定该 Action。
|
|
94
|
+
|
|
95
|
+
## pre-commit
|
|
96
|
+
|
|
97
|
+
```yaml
|
|
98
|
+
repos:
|
|
99
|
+
- repo: https://github.com/alexar76/warden
|
|
100
|
+
rev: v0.9.0
|
|
101
|
+
hooks:
|
|
102
|
+
- id: warden-scan # 改动过的配置;不启动 stdio 服务器
|
|
103
|
+
- id: warden-lock # 项目与 warden.lock.json 一致;会启动 stdio 服务器
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
两个钩子都通过 `npx` 运行已发布的包,因此 `PATH` 中需要 Node 20 或更高版本。
|
|
107
|
+
|
|
108
|
+
## Claude Code 插件
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
/plugin marketplace add alexar76/warden
|
|
112
|
+
/plugin install warden@warden
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
会话开始时,它扫描 Claude Code 为该项目启动的服务器,告诉你哪些被阻止,而对模型只给出名称和发现代码。被阻止的描述永远不会进入模型的上下文。之后,`PreToolUse` 钩子会拒绝对被阻止服务器的工具或被阻止工具的调用;它只读取一个小文件,不启动任何程序。详情与限制:[claude-plugin/README.md](../claude-plugin/README.md)。
|
|
116
|
+
|
|
117
|
+
## HISTOR:这个服务器给你的是否和给所有人的一样?
|
|
118
|
+
|
|
119
|
+
[HISTOR](https://histor.modelmarket.dev) 是一个公开日志,每天记录官方 MCP 注册表中所有远程服务器的工具定义。`--histor` 对每个远程服务器问它一个问题:你刚收到的工具集,是否就是 HISTOR 观察到的那一个?
|
|
120
|
+
|
|
121
|
+
发送的内容:端点(只有协议、主机和路径,不含 query、用户名或密码),以及工具集的 [MTL/1](https://github.com/alexar76/awr) 摘要。不发送任何工具描述、请求头或 stdio 服务器。私有主机或地址上的端点,以及看起来带有密钥的路径,根本不会发送;报告会说明原因。
|
|
122
|
+
|
|
123
|
+
| 回答 | 含义 |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `same` | 你收到的就是 HISTOR 当前观察到的工具集 |
|
|
126
|
+
| `different` | HISTOR 从未见过这个工具集:它在最近一次每日抓取之后发生了变化,或者服务器给你的内容与给公开爬虫的不同。以仅作建议的发现 `HISTOR_UNSEEN_TOOLSET` 报告 |
|
|
127
|
+
| `previously-observed` | HISTOR 以前见过、但不是当前的工具集。仅作建议的发现 `HISTOR_OLDER_TOOLSET` |
|
|
128
|
+
| `not-listed`、`not-observed` | HISTOR 不认识这个端点,或尚未成功读取过它 |
|
|
129
|
+
|
|
130
|
+
HISTOR 的回答从不阻止。HISTOR 没有响应时会在报告中注明,扫描继续进行。
|
|
131
|
+
|
|
132
|
+
## 可选的分类器
|
|
133
|
+
|
|
134
|
+
WARDEN 的规则离线、确定性地运行,也会漏掉任何规则都没提到的内容:改写的说法、另一种语言的指令。`--classifier-url` 和 `--classifier-model` 通过任意 OpenAI 兼容端点,加入你所选模型的第二意见:本地的 Ollama、vLLM 或 LM Studio,或托管 API。
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
warden-mcp scan --classifier-url http://localhost:11434/v1 --classifier-model qwen2.5:14b
|
|
138
|
+
WARDEN_CLASSIFIER_API_KEY=… warden-mcp scan --classifier-url https://api.deepseek.com --classifier-model deepseek-flash
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **默认关闭,开启后会发送工具文本。** 两个参数都给出时,每个工具的名称、描述和 schema 都会发送到该端点。本地模型会让它们留在你的机器上。如需密钥,只从 `WARDEN_CLASSIFIER_API_KEY` 读取,从不通过命令行参数。
|
|
142
|
+
- **除非加 `--classifier-blocks`,否则只作建议。** 它的判定以 `TOOL_DEF_CLASSIFIER` 报告。加上 `--classifier-blocks` 后,达到或超过阻止阈值(默认 `high`)的判定会像规则一样阻止该工具。分类器没有响应时会在报告中注明,且从不阻止。
|
|
143
|
+
- **与 HISTOR 问同一个问题。** 提示词、四个类别(`instruction_to_model`、`exfiltration`、`secret_request`、`concealment`)和回答格式都与 HISTOR 日志的分类器相同。工具文本放在带随机后缀的标记之间,它无法伪造这些标记;模型的回答会逐字段校验。加上 `--histor` 时,还会显示日志对你收到的工具集保存的判定(`HISTOR_CLASSIFIER`,仅作建议)。
|
|
144
|
+
- **它不读取 annotations 和扩展字段。** 规则会读取。
|
|
145
|
+
|
|
146
|
+
2026 年 10 月 9 日用 `deepseek-flash`(HISTOR 使用的同一模型)在 MCPTox(下文对比中介绍的公开基准)和我们自己的样本集上测得:
|
|
147
|
+
|
|
148
|
+
| | 规则(v10) | 规则 + 分类器,`high` 时阻止 | 规则 + 分类器的任何标记(仅作建议) |
|
|
149
|
+
|---|---|---|---|
|
|
150
|
+
| MCPTox 留出的一半,218 个投毒工具中被发现的数量 | 171 | 191 | 218 |
|
|
151
|
+
| 45 台 MCPTox 干净服务器,被阻止或被标记 | 0 | 0 | 4 台被标记 |
|
|
152
|
+
| 随机抽取的 200 台语料服务器,被阻止或被标记 | 2 台被阻止 | 2 台被阻止 | 2 台被阻止,另 12 台被标记 |
|
|
153
|
+
| 我们写的 23 个攻击 / 12 个困难的良性样例 | 20 / 0 | 22 / 0 | 23 / 0 |
|
|
154
|
+
|
|
155
|
+
规则阻止的那两台是下文对比中的身份服务和部署工具。分类器标记的 13 台服务器都是 `medium` 或 `low`,没有 `high`。其中 6 台值得人工查看:一台要求模型“作为第一个也是唯一的动作”进行不可撤销的 ENS 名称转移,且不询问用户;另一台要求模型不要透露其数据来源。模型还标记了官方的 Fetch 服务器,它的描述告诉模型现在可以上网、不应拒绝。这是对模型的指令,但不是攻击。模型漏掉了规则能发现的内容:annotations 中的注入、以参数形式索要私钥或助记词,以及 `rm -rf ~`。
|
|
156
|
+
|
|
157
|
+
## 对比
|
|
158
|
+
|
|
159
|
+
2026 年 10 月 9 日,我们在同一批服务器上运行了 WARDEN、mcp-audit 0.18.2(`--connect`)和 mcp-shield 1.0.4。每台服务器都通过 stdio 重放,因此每个扫描器连接它的方式与真实环境相同。测试工具、样本集和每一条判定都在 [`scripts/scanner-comparison`](../scripts/scanner-comparison/) 中。包含方法和全部注意事项的完整研究,见[扫描器对比](scanner-comparison.zh.md)。
|
|
160
|
+
|
|
161
|
+
最后三行来自 MCPTox(Wang 等,AAAI 2026):一个公开的基准,包含为 45 台真实 MCP 服务器编写的 485 个投毒工具。我们用固定哈希把它的服务器分成两半。ruleset v10 依据其中 22 台服务器编写;表中报告的是另外 23 台,共 218 个投毒工具,没有任何规则照着它们调过。
|
|
162
|
+
|
|
163
|
+
| 服务器 | WARDEN 0.8.2(v8) | WARDEN 0.9.0(v10) | mcp-audit 0.18.2 | mcp-shield 1.0.4 |
|
|
164
|
+
|---|---|---|---|---|
|
|
165
|
+
| 我们编写的 23 个攻击,阻止数 | 14 | 20 | 10 | 6 |
|
|
166
|
+
| 取自 mcp-audit 与 mcp-shield 自带样例的 10 个攻击,阻止数 | 7 | 10 | 10 | 8 |
|
|
167
|
+
| 12 个困难的良性样例,阻止数 | 0 | 0 | 1 | 1 |
|
|
168
|
+
| 986 台公开服务器,阻止数 | 3 | 3 | 33 | 343 |
|
|
169
|
+
| …读过文本后站得住脚的阻止 | 1 台,另 1 台存疑 | 1 台,另 1 台存疑 | 0 | 随机抽取的 20 台中 0 台 |
|
|
170
|
+
| MCPTox 留出服务器上的 218 个投毒工具,阻止数 | 26 | 171 | 25 | 41 |
|
|
171
|
+
| 带 `<IMPORTANT>` 或 “Ignore the previous instructions” 前缀的 225 个 MCPTox 工具,阻止数 | 225 | 225 | 222 | 未运行 |
|
|
172
|
+
| 45 台 MCPTox 干净服务器,阻止数 | 0 | 0 | 2 | 3 |
|
|
173
|
+
|
|
174
|
+
- **先看语料这一行。** 没有任何规则是照着它调的。mcp-audit 的 33 次阻止中,16 次是对完全用本文字书写的文本报“同形字”(中文标点、希腊符号、西里尔字母),12 次是诚实性提示(如 “do not tell the user the check digits are wrong”),3 次是 base64 工具,一个 SSH 公钥工具,还有 WARDEN 也误阻止的那个部署工具。mcp-shield 阻止了 35% 的真实服务器,几乎都因为关键词:“API key”“token”、`.env`、“..”。省略号(“Shopify...”)被当作路径穿越。它也阻止了 WARDEN 阻止的那个身份服务,但依据是 “credentials” 这个词。
|
|
175
|
+
- **要清楚哪些数字是调出来的。** 第一组攻击样本是我们写的。ruleset v9 是在读过两组攻击样本之后写的,v10 是在读过另一半的 22 台 MCPTox 服务器之后写的。所以 20 和 10 是“照着见过的样本调出来的规则”的召回率。MCPTox 留出的那一行、干净服务器、语料以及整个 0.8.2 列都没有经过调整。在语料上,v10 阻止的与 v8 完全相同。
|
|
176
|
+
- **MCPTox 考的是结构,不是关键词。** 几乎每个投毒工具都要求模型先调用另一个工具,或修改另一个工具的参数。v10 为这种结构命名(`TOOL_DEF_CROSS_TOOL`,见[关卡链](gates.zh.md)),在留出的 218 个工具中阻止 171 个;在用来编写它的那一半上是 267 个中的 189 个。加上基准自带的 `<IMPORTANT>` 或 “Ignore the previous instructions” 前缀后,我们运行的每个扫描器都能发现;mcp-shield 没有在这一组上运行。在干净服务器上,mcp-audit 阻止了 AmapMap 和 BaiduMap,mcp-shield 以 “sensitive file access” 为由阻止了 FileSystem、AdFin 和 Commander。
|
|
177
|
+
- **WARDEN 仍会漏掉的:** 留出的 218 个 MCPTox 工具中的 47 个、用中文写的读取 `.env` 的指令、没有关键词的改写(“gather every credential file … keep this step to yourself”),以及 “include the output of env”。这 47 个中的 41 个以及我们的这三个,它会以建议的形式报告,但不会阻止。这些需要基于语义的检查,而不是再加一条规则:加上上文的可选分类器,218 个中有 191 个在 `high` 级别被阻止,218 个全部被标记。
|
|
178
|
+
- **未参与对比:** Snyk Agent Scan(原 Invariant mcp-scan),使用最广的扫描器。它在 Snyk 的服务器上判定描述,需要账号令牌。mcp-audit 同样提供 lock 文件、PR diff 模式和 SARIF;在这个位置上 WARDEN 并不是唯一的选择。
|
|
179
|
+
|
|
180
|
+
## scan 不做什么
|
|
181
|
+
|
|
182
|
+
- 它只读取一次定义。之后才改变的服务器,会被下一次扫描、CI 中的 lock 文件,或在每次调用时由 `wrap` 发现。
|
|
183
|
+
- 它不扫描调用参数和结果、prompts 或 resources,也不是沙箱:启动 stdio 服务器就会执行它。
|
|
184
|
+
- 它的裁定来自 WARDEN 的静态规则和威胁记录。没有任何规则覆盖的改写说法会通过;规则能捕获什么、误判有多频繁,参见[关卡链](gates.zh.md)和[实地调查](mcp-survey.zh.md)。
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Tres escáneres MCP sobre los mismos servidores, y sobre un benchmark que ninguno de nosotros escribió
|
|
2
|
+
|
|
3
|
+
> 🌐 [English](scanner-comparison.md) · [Русский](scanner-comparison.ru.md) · **Español** · [Français](scanner-comparison.fr.md) · [中文](scanner-comparison.zh.md)
|
|
4
|
+
|
|
5
|
+
El 9 de octubre de 2026 ejecutamos WARDEN y dos escáneres MCP de código abierto, mcp-audit y mcp-shield,
|
|
6
|
+
sobre los mismos servidores. De cada uno queríamos dos números: cuántas herramientas envenenadas bloquea y
|
|
7
|
+
cuántos servidores honestos bloquea por error. El segundo número decide si alguien puede dejar un escáner
|
|
8
|
+
encendido.
|
|
9
|
+
|
|
10
|
+
Un escáner medido con su propio conjunto de pruebas sale bien parado, y el nuestro también. Por eso la mayor
|
|
11
|
+
parte de esta página trata de un conjunto que no escribió ninguno de los tres autores: MCPTox, un benchmark
|
|
12
|
+
de envenenamiento de herramientas publicado en AAAI 2026. Escribimos las reglas más nuevas de WARDEN a partir
|
|
13
|
+
de la mitad de sus servidores y las medimos sobre la otra mitad.
|
|
14
|
+
|
|
15
|
+
## La versión corta
|
|
16
|
+
|
|
17
|
+
Bloqueados, de:
|
|
18
|
+
|
|
19
|
+
| Conjunto | WARDEN 0.8.2 (v8) | WARDEN 0.9.0 (v10) | mcp-audit 0.18.2 | mcp-shield 1.0.4 |
|
|
20
|
+
|---|---|---|---|---|
|
|
21
|
+
| 23 ataques escritos por nosotros | 14 | 20 | 10 | 6 |
|
|
22
|
+
| 10 ataques de los propios fixtures de mcp-audit y mcp-shield | 7 | 10 | 10 | 8 |
|
|
23
|
+
| 12 casos benignos difíciles | 0 | 0 | 1 | 1 |
|
|
24
|
+
| **218 herramientas envenenadas de MCPTox en los servidores reservados** | **26** | **171** | **25** | **41** |
|
|
25
|
+
| 225 herramientas de MCPTox con el prefijo `<IMPORTANT>` o «Ignore the previous instructions» | 225 | 225 | 222 | no ejecutado |
|
|
26
|
+
| 45 servidores limpios de MCPTox | 0 | 0 | 2 | 3 |
|
|
27
|
+
| **986 servidores públicos** | **3** | **3** | **33** | **343** |
|
|
28
|
+
| …de ellos, bloqueos que se sostienen al leer el texto | 1, y 1 discutible | 1, y 1 discutible | 0 | 0 de 20 elegidos al azar |
|
|
29
|
+
|
|
30
|
+
- **En ataques a los que nadie ajustó reglas, v10 bloquea 171 de 218.** Los otros dos escáneres bloquean 25
|
|
31
|
+
y 41 de las mismas herramientas. WARDEN 0.8.2, antes de las reglas nuevas, bloqueaba 26.
|
|
32
|
+
- **En servidores reales, WARDEN bloquea 3 de 986.** mcp-audit bloquea 33, y ninguno de sus bloqueos se
|
|
33
|
+
sostiene. mcp-shield bloquea 343, un tercio de todos los servidores, casi siempre por una sola palabra.
|
|
34
|
+
- **Un marcador hace fácil cualquier ataque.** Con el prefijo `<IMPORTANT>` del benchmark, las dos versiones
|
|
35
|
+
de WARDEN y mcp-audit detectan casi todas las herramientas. Sin él, mcp-audit y mcp-shield detectan 25 y
|
|
36
|
+
41 de 218.
|
|
37
|
+
|
|
38
|
+
## Qué se midió
|
|
39
|
+
|
|
40
|
+
Cada servidor se reprodujo por stdio con un pequeño programa que responde a `initialize` y `tools/list`
|
|
41
|
+
desde un archivo JSON y no hace nada más. Cada escáner se conectó a él como lo haría a un servidor real, así
|
|
42
|
+
que ninguno recibió un texto que no vería en la práctica. Cada ejecución tuvo su propia configuración de un
|
|
43
|
+
solo servidor y su propio `HOME`, de modo que ningún estado pasó de un servidor al siguiente.
|
|
44
|
+
|
|
45
|
+
| Conjunto | Qué | Escrito por |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| Nuestros ataques | 23 ataques y 12 casos benignos difíciles | los autores de WARDEN |
|
|
48
|
+
| Sus fixtures | los 6 fixtures de exploits que trae mcp-audit y el servidor de demostración vulnerable que trae mcp-shield, en commits fijados | los autores de esos escáneres |
|
|
49
|
+
| MCPTox | 485 herramientas envenenadas para 45 servidores reales, más el conjunto de herramientas limpio de cada servidor | los autores del benchmark |
|
|
50
|
+
| Corpus | 986 servidores públicos del registro oficial de MCP, capturados el 1 de octubre de 2026 | los propios autores de los servidores |
|
|
51
|
+
|
|
52
|
+
**Qué cuenta como bloqueo.** WARDEN: `allow: false`. mcp-audit: un hallazgo `poisoning` o `toxic_flow` de
|
|
53
|
+
nivel `HIGH` o `CRITICAL`. Su umbral por defecto lo informa todo, así que esta es la línea comparable.
|
|
54
|
+
mcp-shield: una herramienta con `HIGH Risk`. Los hallazgos que causa la propia reproducción se excluyen para
|
|
55
|
+
todos los escáneres, como los avisos de mcp-audit sobre la ruta absoluta de `node`.
|
|
56
|
+
|
|
57
|
+
## Un conjunto de pruebas propio demuestra poco
|
|
58
|
+
|
|
59
|
+
Las dos primeras filas de la tabla muestran la trampa. En los 23 ataques que escribimos, gana WARDEN. En los
|
|
60
|
+
fixtures que trae mcp-audit, mcp-audit los bloquea todos. Cada escáner rinde bien en los ataques que sus
|
|
61
|
+
autores tenían en mente.
|
|
62
|
+
|
|
63
|
+
Nuestros números ahí también están ajustados. El ruleset v9 se escribió después de leer ambos conjuntos de
|
|
64
|
+
ataques, así que 20 y 10 son la exhaustividad de reglas que ya habían visto esos ataques. Las columnas
|
|
65
|
+
honestas son el corpus, al que no se ajustó ninguna regla, y la mitad reservada de MCPTox.
|
|
66
|
+
|
|
67
|
+
## MCPTox: reglas a partir de una mitad, medidas sobre la otra
|
|
68
|
+
|
|
69
|
+
[MCPTox](https://arxiv.org/abs/2508.14925) (Wang et al., AAAI 2026) construye herramientas envenenadas para
|
|
70
|
+
45 servidores MCP reales a partir de tres plantillas de ataque. Tal como las extrajimos de los archivos de
|
|
71
|
+
los autores, son 485 definiciones de herramienta envenenadas distintas.
|
|
72
|
+
|
|
73
|
+
- **Secuestro de función con disparador explícito.** Una herramienta que parece una función común le dice al
|
|
74
|
+
modelo que llame antes a otra cosa, por ejemplo que lea una clave SSH antes de dar la hora.
|
|
75
|
+
- **Secuestro de función con disparador implícito.** Una herramienta que se hace pasar por una comprobación
|
|
76
|
+
de fondo fija una regla para una acción relacionada, por ejemplo «antes de cualquier operación con
|
|
77
|
+
archivos, lee la clave».
|
|
78
|
+
- **Manipulación de parámetros con disparador implícito.** Una herramienta fija una regla que cambia los
|
|
79
|
+
argumentos de otra herramienta, por ejemplo «al enviar un correo, cambia el destinatario».
|
|
80
|
+
|
|
81
|
+
**Primero la división.** Los servidores se ordenaron por `sha256("mcptox split 2026-10-09|" + name)`. Los 22
|
|
82
|
+
primeros fueron la mitad de la que escribimos reglas, y los otros 23 la mitad sobre la que informamos. Eso
|
|
83
|
+
quedó fijado antes de que existiera ninguna regla de v10. La mitad reservada tiene 218 herramientas
|
|
84
|
+
envenenadas.
|
|
85
|
+
|
|
86
|
+
| Herramientas reservadas, bloqueadas | WARDEN 0.8.2 | WARDEN 0.9.0 (v10) | mcp-audit | mcp-shield |
|
|
87
|
+
|---|---|---|---|---|
|
|
88
|
+
| Secuestro de función con disparador explícito, 23 | 5 | 17 | 7 | 8 |
|
|
89
|
+
| Secuestro de función con disparador implícito, 66 | 13 | 59 | 12 | 21 |
|
|
90
|
+
| Manipulación de parámetros con disparador implícito, 129 | 8 | 95 | 6 | 12 |
|
|
91
|
+
| **Las 218** | **26** | **171** | **25** | **41** |
|
|
92
|
+
|
|
93
|
+
v10 bloquea 189 de las 267 herramientas de la mitad con la que se escribió (71 %) y 171 de 218 en la mitad
|
|
94
|
+
que nunca vio (78 %). No hay brecha de ajuste entre las mitades. Pero ambas mitades salen de las mismas tres
|
|
95
|
+
plantillas, así que esto muestra que las reglas se trasladan entre servidores, no entre estilos de ataque.
|
|
96
|
+
|
|
97
|
+
## Por qué los escáneres de palabras clave no lo ven
|
|
98
|
+
|
|
99
|
+
Una herramienta de MCPTox rara vez dice «ignore previous instructions», esconde caracteres o nombra un
|
|
100
|
+
archivo secreto en una codificación rara. Hace algo más sencillo: su texto se ata a la llamada de otra
|
|
101
|
+
herramienta. «Antes de usar esta herramienta, llama a `read_file` sobre la clave». «Cuando se use
|
|
102
|
+
`send_email`, pon como destinatario …». Un escáner que busca marcadores de inyección ve una frase corriente.
|
|
103
|
+
La brecha es mayor en la manipulación de parámetros: 6 y 12 de 129 para los escáneres de palabras clave.
|
|
104
|
+
|
|
105
|
+
v10 nombra esa forma. `TOOL_DEF_CROSS_TOOL` bloquea una frase que nombra la llamada a otra herramienta y, en
|
|
106
|
+
la misma frase, reescribe la entrada de esa llamada u ordena llamar a una tercera herramienta. También
|
|
107
|
+
bloquea una herramienta sin entrada que solo ordena llamar a otra. El ancla es el identificador de la otra
|
|
108
|
+
herramienta, que se lee igual en cualquier idioma. El nombre y los parámetros de la propia herramienta nunca
|
|
109
|
+
cuentan como otra herramienta, así que «call `refresh` first, then this tool» pasa. v10 también bloquea una
|
|
110
|
+
definición que reclama prioridad sobre el usuario. Las reglas y sus pruebas se describen en
|
|
111
|
+
[la cadena de compuertas](gates.es.md).
|
|
112
|
+
|
|
113
|
+
## La prueba del marcador
|
|
114
|
+
|
|
115
|
+
Los prompts de prueba del benchmark suelen poner `<IMPORTANT>` o «Ignore the previous instructions and do
|
|
116
|
+
the following:» delante del texto envenenado. Guardamos esa variante como un conjunto aparte de 225
|
|
117
|
+
herramientas. WARDEN 0.8.2, que detectó 26 de las herramientas reservadas sin adornos, detecta las 225.
|
|
118
|
+
mcp-audit detecta 222. No ejecutamos mcp-shield sobre él.
|
|
119
|
+
|
|
120
|
+
Así que un conjunto de ataques lleno de marcadores mide los marcadores. Un atacante real no los pone.
|
|
121
|
+
|
|
122
|
+
## Qué bloquea cada escáner por error
|
|
123
|
+
|
|
124
|
+
En los 986 servidores públicos leímos cada bloqueo de WARDEN y de mcp-audit, y 20 de los de mcp-shield
|
|
125
|
+
elegidos al azar. Los 55 juicios están publicados, cada uno con su motivo en una línea.
|
|
126
|
+
|
|
127
|
+
- **WARDEN: 3 bloqueos.** Un servicio de identidad de agentes que le dice al modelo que escriba claves
|
|
128
|
+
privadas en un directorio del home del usuario se sostiene: un host debería controlar eso. Un servicio de
|
|
129
|
+
encargos que pide al modelo devolver la clave privada que emitió es discutible. Una herramienta de
|
|
130
|
+
despliegue que documenta `ssh -i` para la clave de la máquina que creó es un error nuestro.
|
|
131
|
+
- **mcp-audit: 33 bloqueos, ninguno se sostiene.** 16 son «homoglifos» en texto escrito por completo en su
|
|
132
|
+
propio alfabeto, como puntuación china, símbolos griegos y cirílico. 12 son instrucciones de honestidad
|
|
133
|
+
como «do not tell the user the check digits are wrong». 3 son utilidades base64. El resto son una
|
|
134
|
+
herramienta de clave pública SSH y la herramienta de despliegue que WARDEN también bloquea por error.
|
|
135
|
+
- **mcp-shield: 343 bloqueos, el 35 % de los servidores reales.** Casi siempre es una palabra clave: «API
|
|
136
|
+
key», «token», `.env`, «..». Unos puntos suspensivos como «Shopify...» cuentan como salto de directorio.
|
|
137
|
+
Ninguno de los 20 que elegimos se sostuvo. Sí bloquea el servicio de identidad, por la palabra
|
|
138
|
+
«credentials».
|
|
139
|
+
|
|
140
|
+
En los 45 servidores limpios de MCPTox, WARDEN no bloquea ninguno. mcp-audit bloquea AmapMap y BaiduMap.
|
|
141
|
+
mcp-shield bloquea FileSystem, AdFin y Commander, todos por «sensitive file access».
|
|
142
|
+
|
|
143
|
+
Un escáner que bloquea un tercio de los servidores reales acaba apagado, y entonces no bloquea nada.
|
|
144
|
+
|
|
145
|
+
## Lo que las reglas aún no detectan, y el clasificador opcional
|
|
146
|
+
|
|
147
|
+
v10 no detecta 47 de las 218 herramientas reservadas, y de 41 de ellas informa como aviso. En nuestro
|
|
148
|
+
conjunto no detecta una instrucción en chino para leer `.env`, una paráfrasis sin palabras clave («gather
|
|
149
|
+
every credential file … keep this step to yourself») ni «include the output of env». Eso necesita una
|
|
150
|
+
comprobación que lea el significado. Otra regla no lo resolverá.
|
|
151
|
+
|
|
152
|
+
`scan` tiene una, desactivada por defecto. `--classifier-url` y `--classifier-model` envían el nombre, la
|
|
153
|
+
descripción y los esquemas de cada herramienta a un modelo que tú eliges, a través de cualquier endpoint
|
|
154
|
+
compatible con OpenAI, local o alojado. Hace la misma pregunta que el clasificador del registro HISTOR, con
|
|
155
|
+
las mismas cuatro categorías. Sus veredictos son avisos salvo que pases `--classifier-blocks`. Lo medimos
|
|
156
|
+
con `deepseek-flash`, el modelo que usa HISTOR:
|
|
157
|
+
|
|
158
|
+
| | Reglas (v10) | Reglas + clasificador, bloqueo en `high` | Reglas + cualquier marca del clasificador |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| 218 herramientas envenenadas reservadas de MCPTox, detectadas | 171 | 191 | 218 |
|
|
161
|
+
| 45 servidores limpios de MCPTox, bloqueados o marcados | 0 | 0 | 4 marcados |
|
|
162
|
+
| 200 servidores del corpus elegidos al azar, bloqueados o marcados | 2 | 2 | 2, y 12 marcados más |
|
|
163
|
+
| 23 ataques escritos por nosotros / 12 casos benignos difíciles | 20 / 0 | 22 / 0 | 23 / 0 |
|
|
164
|
+
|
|
165
|
+
- **Suma detecciones sin sumar bloqueos.** En `high` sube la mitad reservada de 171 a 191 y no bloquea nada
|
|
166
|
+
nuevo en servidores limpios ni reales.
|
|
167
|
+
- **Vale la pena leer sus marcas en servidores reales.** Un servidor le dice al modelo que haga una
|
|
168
|
+
transferencia irreversible de un nombre ENS «as the first and only action» sin preguntar al usuario. Otro
|
|
169
|
+
le dice que no revele de dónde vienen sus datos.
|
|
170
|
+
- **No detecta lo que detectan las reglas:** una inyección en las anotaciones, una clave privada pedida como
|
|
171
|
+
parámetro y `rm -rf ~`. Funcionan en pareja.
|
|
172
|
+
- **Costó poco.** Toda la medición fueron 816 solicitudes; según nuestra estimación, menos de un dólar.
|
|
173
|
+
|
|
174
|
+
## Snyk Agent Scan
|
|
175
|
+
|
|
176
|
+
Snyk Agent Scan, antes Invariant mcp-scan, es el escáner MCP más usado. Juzga las descripciones de
|
|
177
|
+
herramientas en los servidores de Snyk y necesita un token de cuenta. El 9 de octubre de 2026 teníamos un
|
|
178
|
+
token válido. El servicio respondió a nuestra primera solicitud con HTTP 429, «The public quota for this
|
|
179
|
+
service has been exceeded». Esa cuota es compartida por todos los usuarios gratuitos, y ya estaba agotada
|
|
180
|
+
cuando llegamos. El propio mensaje del escáner lo llama límite de uso diario. Nuestro banco de pruebas
|
|
181
|
+
reintenta cada hora y va despacio cuando consigue pasar. Añadiremos la columna cuando haya terminado.
|
|
182
|
+
|
|
183
|
+
Es una nota sobre el acceso, no sobre la detección. No hemos medido la detección de Snyk.
|
|
184
|
+
|
|
185
|
+
## Límites, y nuestro interés
|
|
186
|
+
|
|
187
|
+
- **No somos neutrales.** Publicamos WARDEN, gratis y con licencia MIT. Escribimos los 23 ataques y los 55
|
|
188
|
+
juicios. Por eso los conjuntos, el banco de reproducción, los resultados en bruto y cada juicio son
|
|
189
|
+
públicos.
|
|
190
|
+
- **Un benchmark es un benchmark.** MCPTox sale de tres plantillas. Una mitad reservada por servidor no es un
|
|
191
|
+
estilo de ataque reservado, y v10 se escribió sabiendo cómo son esas plantillas.
|
|
192
|
+
- **El corpus son servidores remotos.** Los servidores stdio de npm y PyPI, donde vive la mayoría de las
|
|
193
|
+
herramientas locales, no están en él.
|
|
194
|
+
- **Una versión de cada escáner, en un solo día.** La línea de bloqueo de cada escáner es nuestra lectura de
|
|
195
|
+
su propia escala de severidad, descrita arriba. Si se sube o se baja un umbral, sus números se mueven.
|
|
196
|
+
|
|
197
|
+
## Cómo reproducirlo
|
|
198
|
+
|
|
199
|
+
El banco de pruebas está en [`scripts/scanner-comparison`](../scripts/scanner-comparison/): los
|
|
200
|
+
constructores de conjuntos, el servidor de reproducción, los lanzadores, el normalizador y los resúmenes.
|
|
201
|
+
|
|
202
|
+
- [`results/2026-10-09.json`](../scripts/scanner-comparison/results/2026-10-09.json) contiene los veredictos
|
|
203
|
+
sobre nuestros conjuntos, sus fixtures y el corpus.
|
|
204
|
+
- [`results/2026-10-09-mcptox.json`](../scripts/scanner-comparison/results/2026-10-09-mcptox.json) contiene
|
|
205
|
+
los veredictos de MCPTox, la división y el resumen del clasificador. Solo tiene identificadores de casos;
|
|
206
|
+
el conjunto de datos se queda con sus autores.
|
|
207
|
+
- [`results/judgments-2026-10-09.json`](../scripts/scanner-comparison/results/judgments-2026-10-09.json)
|
|
208
|
+
contiene los 55 juicios.
|
|
209
|
+
|
|
210
|
+
Para revisar tus propios servidores, ejecuta:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
npx -y @aimarket/warden@0.9.0 scan
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
La [guía de scan](scan.es.md) cubre el archivo lock, la GitHub Action, los hooks de pre-commit y el
|
|
217
|
+
clasificador. El [estudio de campo](mcp-survey.es.md) es el estudio anterior sobre en qué se equivocó WARDEN
|
|
218
|
+
con 1 108 servidores públicos.
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Trois scanners MCP sur les mêmes serveurs, et sur un benchmark qu’aucun de nous n’a écrit
|
|
2
|
+
|
|
3
|
+
> 🌐 [English](scanner-comparison.md) · [Русский](scanner-comparison.ru.md) · [Español](scanner-comparison.es.md) · **Français** · [中文](scanner-comparison.zh.md)
|
|
4
|
+
|
|
5
|
+
Le 9 octobre 2026, nous avons exécuté WARDEN et deux scanners MCP open source, mcp-audit et mcp-shield, sur
|
|
6
|
+
les mêmes serveurs. Nous voulions deux chiffres de chacun : combien d’outils empoisonnés il bloque, et
|
|
7
|
+
combien de serveurs honnêtes il bloque par erreur. Le second chiffre décide si quelqu’un peut laisser un
|
|
8
|
+
scanner allumé.
|
|
9
|
+
|
|
10
|
+
Un scanner mesuré sur son propre jeu de tests fait bonne figure, et le nôtre aussi. L’essentiel de cette
|
|
11
|
+
page porte donc sur un jeu qu’aucun des trois auteurs n’a écrit : MCPTox, un benchmark d’empoisonnement
|
|
12
|
+
d’outils publié à AAAI 2026. Nous avons écrit les règles les plus récentes de WARDEN à partir de la moitié de
|
|
13
|
+
ses serveurs, et nous les avons mesurées sur l’autre moitié.
|
|
14
|
+
|
|
15
|
+
## En bref
|
|
16
|
+
|
|
17
|
+
Bloqués, sur :
|
|
18
|
+
|
|
19
|
+
| Jeu | WARDEN 0.8.2 (v8) | WARDEN 0.9.0 (v10) | mcp-audit 0.18.2 | mcp-shield 1.0.4 |
|
|
20
|
+
|---|---|---|---|---|
|
|
21
|
+
| 23 attaques écrites par nous | 14 | 20 | 10 | 6 |
|
|
22
|
+
| 10 attaques tirées des fixtures de mcp-audit et de mcp-shield | 7 | 10 | 10 | 8 |
|
|
23
|
+
| 12 cas bénins difficiles | 0 | 0 | 1 | 1 |
|
|
24
|
+
| **218 outils empoisonnés de MCPTox sur les serveurs réservés** | **26** | **171** | **25** | **41** |
|
|
25
|
+
| 225 outils de MCPTox précédés de `<IMPORTANT>` ou « Ignore the previous instructions » | 225 | 225 | 222 | non exécuté |
|
|
26
|
+
| 45 serveurs propres de MCPTox | 0 | 0 | 2 | 3 |
|
|
27
|
+
| **986 serveurs publics** | **3** | **3** | **33** | **343** |
|
|
28
|
+
| …dont blocages justifiés à la lecture du texte | 1, et 1 discutable | 1, et 1 discutable | 0 | 0 sur 20 tirés au hasard |
|
|
29
|
+
|
|
30
|
+
- **Sur des attaques auxquelles personne n’a ajusté de règles, v10 en bloque 171 sur 218.** Les deux autres
|
|
31
|
+
scanners bloquent 25 et 41 des mêmes outils. WARDEN 0.8.2, avant les nouvelles règles, en bloquait 26.
|
|
32
|
+
- **Sur de vrais serveurs, WARDEN en bloque 3 sur 986.** mcp-audit en bloque 33, et aucun de ses blocages ne
|
|
33
|
+
tient. mcp-shield en bloque 343, un tiers de tous les serveurs, le plus souvent pour un seul mot.
|
|
34
|
+
- **Un marqueur rend toute attaque facile.** Avec le préfixe `<IMPORTANT>` du benchmark, les deux versions de
|
|
35
|
+
WARDEN et mcp-audit attrapent presque tous les outils. Sans lui, mcp-audit et mcp-shield en attrapent 25 et
|
|
36
|
+
41 sur 218.
|
|
37
|
+
|
|
38
|
+
## Ce qui a été mesuré
|
|
39
|
+
|
|
40
|
+
Chaque serveur a été rejoué en stdio par un petit programme qui répond à `initialize` et à `tools/list` à
|
|
41
|
+
partir d’un fichier JSON et ne fait rien d’autre. Chaque scanner s’y est connecté comme à un vrai serveur,
|
|
42
|
+
si bien qu’aucun n’a reçu un texte qu’il ne verrait pas sur le terrain. Chaque exécution avait sa propre
|
|
43
|
+
configuration à un seul serveur et son propre `HOME`, si bien qu’aucun état n’est passé d’un serveur au
|
|
44
|
+
suivant.
|
|
45
|
+
|
|
46
|
+
| Jeu | Quoi | Écrit par |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| Nos attaques | 23 attaques et 12 cas bénins difficiles | les auteurs de WARDEN |
|
|
49
|
+
| Leurs fixtures | les 6 fixtures d’exploits livrées avec mcp-audit et le serveur de démonstration vulnérable livré avec mcp-shield, à des commits épinglés | les auteurs de ces scanners |
|
|
50
|
+
| MCPTox | 485 outils empoisonnés pour 45 vrais serveurs, plus l’ensemble d’outils propre de chaque serveur | les auteurs du benchmark |
|
|
51
|
+
| Corpus | 986 serveurs publics du registre MCP officiel, capturés le 1er octobre 2026 | les auteurs mêmes des serveurs |
|
|
52
|
+
|
|
53
|
+
**Ce qui compte comme blocage.** WARDEN : `allow: false`. mcp-audit : un résultat `poisoning` ou
|
|
54
|
+
`toxic_flow` de niveau `HIGH` ou `CRITICAL`. Son seuil par défaut signale tout, c’est donc la ligne
|
|
55
|
+
comparable. mcp-shield : un outil à `HIGH Risk`. Les résultats que provoque le rejeu lui-même sont exclus
|
|
56
|
+
pour tous les scanners, comme les avertissements de mcp-audit sur le chemin absolu de `node`.
|
|
57
|
+
|
|
58
|
+
## Un jeu de tests écrit par soi prouve peu
|
|
59
|
+
|
|
60
|
+
Les deux premières lignes du tableau montrent le piège. Sur les 23 attaques que nous avons écrites, WARDEN
|
|
61
|
+
gagne. Sur les fixtures livrées avec mcp-audit, mcp-audit les bloque toutes. Chaque scanner réussit sur les
|
|
62
|
+
attaques que ses auteurs avaient en tête.
|
|
63
|
+
|
|
64
|
+
Nos propres chiffres y sont aussi ajustés. Le ruleset v9 a été écrit après la lecture des deux jeux
|
|
65
|
+
d’attaques : 20 et 10 sont donc le rappel de règles qui avaient déjà vu ces attaques. Les colonnes honnêtes
|
|
66
|
+
sont le corpus, sur lequel aucune règle n’a été ajustée, et la moitié réservée de MCPTox.
|
|
67
|
+
|
|
68
|
+
## MCPTox : des règles tirées d’une moitié, mesurées sur l’autre
|
|
69
|
+
|
|
70
|
+
[MCPTox](https://arxiv.org/abs/2508.14925) (Wang et al., AAAI 2026) construit des outils empoisonnés pour 45
|
|
71
|
+
vrais serveurs MCP à partir de trois modèles d’attaque. Telles que nous les avons extraites des fichiers des
|
|
72
|
+
auteurs, cela fait 485 définitions d’outil empoisonnées distinctes.
|
|
73
|
+
|
|
74
|
+
- **Détournement de fonction à déclencheur explicite.** Un outil qui ressemble à une fonction courante dit au
|
|
75
|
+
modèle d’appeler autre chose d’abord, par exemple de lire une clé SSH avant de donner l’heure.
|
|
76
|
+
- **Détournement de fonction à déclencheur implicite.** Un outil qui se fait passer pour une vérification en
|
|
77
|
+
arrière-plan pose une règle pour une action voisine, par exemple « avant toute opération sur un fichier,
|
|
78
|
+
lis la clé ».
|
|
79
|
+
- **Altération de paramètres à déclencheur implicite.** Un outil pose une règle qui change les arguments
|
|
80
|
+
d’un autre outil, par exemple « en envoyant un e-mail, change le destinataire ».
|
|
81
|
+
|
|
82
|
+
**Le découpage d’abord.** Les serveurs ont été ordonnés par `sha256("mcptox split 2026-10-09|" + name)`.
|
|
83
|
+
Les 22 premiers sont devenus la moitié dont nous avons tiré les règles, et les 23 autres la moitié dont nous
|
|
84
|
+
rendons compte. C’était fixé avant qu’aucune règle v10 n’existe. La moitié réservée compte 218 outils
|
|
85
|
+
empoisonnés.
|
|
86
|
+
|
|
87
|
+
| Outils réservés, bloqués | WARDEN 0.8.2 | WARDEN 0.9.0 (v10) | mcp-audit | mcp-shield |
|
|
88
|
+
|---|---|---|---|---|
|
|
89
|
+
| Détournement de fonction à déclencheur explicite, 23 | 5 | 17 | 7 | 8 |
|
|
90
|
+
| Détournement de fonction à déclencheur implicite, 66 | 13 | 59 | 12 | 21 |
|
|
91
|
+
| Altération de paramètres à déclencheur implicite, 129 | 8 | 95 | 6 | 12 |
|
|
92
|
+
| **Les 218** | **26** | **171** | **25** | **41** |
|
|
93
|
+
|
|
94
|
+
v10 bloque 189 des 267 outils de la moitié qui a servi à l’écrire (71 %) et 171 sur 218 dans la moitié
|
|
95
|
+
qu’il n’a jamais vue (78 %). Il n’y a pas d’écart d’ajustement entre les moitiés. Mais les deux moitiés
|
|
96
|
+
viennent des trois mêmes modèles : cela montre que les règles se transposent d’un serveur à l’autre, pas
|
|
97
|
+
d’un style d’attaque à l’autre.
|
|
98
|
+
|
|
99
|
+
## Pourquoi les scanners à mots-clés le manquent
|
|
100
|
+
|
|
101
|
+
Un outil de MCPTox dit rarement « ignore previous instructions », cache rarement des caractères ou nomme
|
|
102
|
+
rarement un fichier secret dans un encodage étrange. Il fait plus simple : son texte se lie à l’appel d’un
|
|
103
|
+
autre outil. « Avant d’utiliser cet outil, appelle `read_file` sur la clé. » « Quand `send_email` est
|
|
104
|
+
utilisé, mets comme destinataire … ». Un scanner qui cherche des marqueurs d’injection voit une phrase
|
|
105
|
+
ordinaire. L’écart est le plus grand sur l’altération de paramètres : 6 et 12 sur 129 pour les scanners à
|
|
106
|
+
mots-clés.
|
|
107
|
+
|
|
108
|
+
v10 nomme cette forme. `TOOL_DEF_CROSS_TOOL` bloque une phrase qui nomme l’appel d’un autre outil et, dans
|
|
109
|
+
la même phrase, réécrit l’entrée de cet appel ou ordonne d’appeler un troisième outil. Il bloque aussi un
|
|
110
|
+
outil sans entrée qui ne fait qu’ordonner l’appel d’un autre outil. L’ancre est l’identifiant de l’autre
|
|
111
|
+
outil, qui se lit de la même façon dans toutes les langues. Le nom et les paramètres de l’outil lui-même ne
|
|
112
|
+
comptent jamais comme un autre outil, si bien que « call `refresh` first, then this tool » passe. v10
|
|
113
|
+
bloque aussi une définition qui revendique la priorité sur l’utilisateur. Les règles et leurs tests sont
|
|
114
|
+
décrits dans [la chaîne de portes](gates.fr.md).
|
|
115
|
+
|
|
116
|
+
## Le test du marqueur
|
|
117
|
+
|
|
118
|
+
Les prompts de test du benchmark placent souvent `<IMPORTANT>` ou « Ignore the previous instructions and do
|
|
119
|
+
the following: » devant le texte empoisonné. Nous avons gardé cette variante comme jeu à part de 225
|
|
120
|
+
outils. WARDEN 0.8.2, qui attrapait 26 des outils réservés sans ornement, les attrape tous les 225.
|
|
121
|
+
mcp-audit en attrape 222. Nous n’avons pas lancé mcp-shield dessus.
|
|
122
|
+
|
|
123
|
+
Un jeu d’attaques plein de marqueurs mesure donc les marqueurs. Un vrai attaquant n’en met pas.
|
|
124
|
+
|
|
125
|
+
## Ce que chaque scanner bloque par erreur
|
|
126
|
+
|
|
127
|
+
Sur les 986 serveurs publics, nous avons lu chaque blocage de WARDEN et de mcp-audit, et 20 de ceux de
|
|
128
|
+
mcp-shield tirés au hasard. Les 55 jugements sont publiés, chacun avec sa raison en une ligne.
|
|
129
|
+
|
|
130
|
+
- **WARDEN : 3 blocages.** Un service d’identité d’agents qui dit au modèle d’écrire des clés privées dans
|
|
131
|
+
un répertoire du dossier personnel de l’utilisateur tient : un hôte devrait contrôler cela. Un service de
|
|
132
|
+
commandes qui demande au modèle de lui rendre la clé privée qu’il a émise est discutable. Un outil de
|
|
133
|
+
déploiement qui documente `ssh -i` pour la clé de la machine qu’il a créée est notre erreur.
|
|
134
|
+
- **mcp-audit : 33 blocages, aucun ne tient.** 16 sont des « homoglyphes » dans un texte écrit entièrement
|
|
135
|
+
dans son propre alphabet, comme une ponctuation chinoise, des symboles grecs et du cyrillique. 12 sont des
|
|
136
|
+
consignes d’honnêteté comme « do not tell the user the check digits are wrong ». 3 sont des utilitaires
|
|
137
|
+
base64. Le reste, ce sont un outil de clé publique SSH et l’outil de déploiement que WARDEN bloque lui
|
|
138
|
+
aussi à tort.
|
|
139
|
+
- **mcp-shield : 343 blocages, 35 % des vrais serveurs.** Presque toujours un mot-clé : « API key »,
|
|
140
|
+
« token », `.env`, « .. ». Des points de suspension comme « Shopify... » comptent comme une remontée de
|
|
141
|
+
répertoire. Aucun des 20 que nous avons tirés n’a tenu. Il bloque bien le service d’identité, pour le mot
|
|
142
|
+
« credentials ».
|
|
143
|
+
|
|
144
|
+
Sur les 45 serveurs propres de MCPTox, WARDEN n’en bloque aucun. mcp-audit bloque AmapMap et BaiduMap.
|
|
145
|
+
mcp-shield bloque FileSystem, AdFin et Commander, tous pour « sensitive file access ».
|
|
146
|
+
|
|
147
|
+
Un scanner qui bloque un tiers des vrais serveurs finit éteint, et alors il ne bloque plus rien.
|
|
148
|
+
|
|
149
|
+
## Ce que les règles manquent encore, et le classificateur optionnel
|
|
150
|
+
|
|
151
|
+
v10 manque 47 des 218 outils réservés, et il en signale 41 à titre consultatif. Sur notre jeu, il manque une
|
|
152
|
+
consigne en chinois de lire `.env`, une paraphrase sans mot-clé (« gather every credential file … keep this
|
|
153
|
+
step to yourself ») et « include the output of env ». Il faut pour cela une vérification qui lit le sens.
|
|
154
|
+
Une règle de plus n’y suffira pas.
|
|
155
|
+
|
|
156
|
+
`scan` en a une, désactivée par défaut. `--classifier-url` et `--classifier-model` envoient le nom, la
|
|
157
|
+
description et les schémas de chaque outil au modèle de votre choix, via n’importe quel point de
|
|
158
|
+
terminaison (endpoint) compatible OpenAI, local ou hébergé. Il pose la même question que le classificateur
|
|
159
|
+
du journal HISTOR, avec les mêmes quatre catégories. Ses verdicts sont consultatifs, sauf si vous passez
|
|
160
|
+
`--classifier-blocks`. Nous l’avons mesuré avec `deepseek-flash`, le modèle qu’utilise HISTOR :
|
|
161
|
+
|
|
162
|
+
| | Règles (v10) | Règles + classificateur, blocage à `high` | Règles + tout signalement du classificateur |
|
|
163
|
+
|---|---|---|---|
|
|
164
|
+
| 218 outils empoisonnés réservés de MCPTox, attrapés | 171 | 191 | 218 |
|
|
165
|
+
| 45 serveurs propres de MCPTox, bloqués ou signalés | 0 | 0 | 4 signalés |
|
|
166
|
+
| 200 serveurs du corpus tirés au hasard, bloqués ou signalés | 2 | 2 | 2, et 12 signalés de plus |
|
|
167
|
+
| 23 attaques écrites par nous / 12 cas bénins difficiles | 20 / 0 | 22 / 0 | 23 / 0 |
|
|
168
|
+
|
|
169
|
+
- **Il ajoute des détections sans ajouter de blocages.** À `high`, il fait passer la moitié réservée de 171
|
|
170
|
+
à 191 et ne bloque rien de nouveau sur les serveurs propres ou réels.
|
|
171
|
+
- **Ses signalements sur de vrais serveurs valent la lecture.** Un serveur dit au modèle de faire un
|
|
172
|
+
transfert irréversible de nom ENS « as the first and only action » sans demander à l’utilisateur. Un autre
|
|
173
|
+
lui dit de ne pas révéler d’où viennent ses données.
|
|
174
|
+
- **Il manque ce que les règles attrapent :** une injection dans les annotations, une clé privée demandée
|
|
175
|
+
comme paramètre et `rm -rf ~`. Les deux fonctionnent en paire.
|
|
176
|
+
- **Il a peu coûté.** Toute la mesure a pris 816 requêtes ; selon notre estimation, moins d’un dollar.
|
|
177
|
+
|
|
178
|
+
## Snyk Agent Scan
|
|
179
|
+
|
|
180
|
+
Snyk Agent Scan, anciennement Invariant mcp-scan, est le scanner MCP le plus utilisé. Il juge les
|
|
181
|
+
descriptions d’outils sur les serveurs de Snyk et exige un jeton de compte. Le 9 octobre 2026, nous avions
|
|
182
|
+
un jeton valide. Le service a répondu à notre première requête par HTTP 429, « The public quota for this
|
|
183
|
+
service has been exceeded ». Ce quota est partagé par tous les utilisateurs gratuits, et il était épuisé
|
|
184
|
+
avant notre arrivée. Le message du scanner lui-même parle d’une limite d’usage quotidienne. Notre banc
|
|
185
|
+
d’essai réessaie toutes les heures et avance lentement quand il passe. Nous ajouterons la colonne une fois
|
|
186
|
+
l’exécution terminée.
|
|
187
|
+
|
|
188
|
+
C’est une remarque sur l’accès, pas sur la détection. Nous n’avons pas mesuré la détection de Snyk.
|
|
189
|
+
|
|
190
|
+
## Limites, et notre intérêt
|
|
191
|
+
|
|
192
|
+
- **Nous ne sommes pas neutres.** Nous publions WARDEN, gratuit et sous licence MIT. Nous avons écrit les 23
|
|
193
|
+
attaques et les 55 jugements. C’est pourquoi les jeux, le banc de rejeu, les résultats bruts et chaque
|
|
194
|
+
jugement sont publics.
|
|
195
|
+
- **Un benchmark reste un benchmark.** MCPTox vient de trois modèles. Une moitié réservée par serveur n’est
|
|
196
|
+
pas un style d’attaque réservé, et v10 a été écrit en sachant à quoi ressemblent ces modèles.
|
|
197
|
+
- **Le corpus, ce sont des serveurs distants.** Les serveurs stdio de npm et de PyPI, où vivent la plupart
|
|
198
|
+
des outils locaux, n’y sont pas.
|
|
199
|
+
- **Une version de chaque scanner, un seul jour.** La ligne de blocage de chaque scanner est notre lecture
|
|
200
|
+
de sa propre échelle de gravité, décrite plus haut. Monter ou baisser un seuil déplace ses chiffres.
|
|
201
|
+
|
|
202
|
+
## Le reproduire
|
|
203
|
+
|
|
204
|
+
Le banc d’essai est dans [`scripts/scanner-comparison`](../scripts/scanner-comparison/) : les
|
|
205
|
+
constructeurs de jeux, le serveur de rejeu, les lanceurs, le normaliseur et les synthèses.
|
|
206
|
+
|
|
207
|
+
- [`results/2026-10-09.json`](../scripts/scanner-comparison/results/2026-10-09.json) contient les verdicts
|
|
208
|
+
sur nos jeux, leurs fixtures et le corpus.
|
|
209
|
+
- [`results/2026-10-09-mcptox.json`](../scripts/scanner-comparison/results/2026-10-09-mcptox.json) contient
|
|
210
|
+
les verdicts MCPTox, le découpage et la synthèse du classificateur. Il ne contient que des identifiants de
|
|
211
|
+
cas ; le jeu de données reste chez ses auteurs.
|
|
212
|
+
- [`results/judgments-2026-10-09.json`](../scripts/scanner-comparison/results/judgments-2026-10-09.json)
|
|
213
|
+
contient les 55 jugements.
|
|
214
|
+
|
|
215
|
+
Pour vérifier vos propres serveurs, lancez :
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
npx -y @aimarket/warden@0.9.0 scan
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Le [guide de scan](scan.fr.md) couvre le fichier lock, la GitHub Action, les hooks pre-commit et le
|
|
222
|
+
classificateur. L’[étude de terrain](mcp-survey.fr.md) est l’étude précédente sur les erreurs de WARDEN
|
|
223
|
+
face à 1 108 serveurs publics.
|