@aimarket/warden 0.9.0 → 0.14.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/README-es.md +36 -11
  3. package/README-fr.md +36 -11
  4. package/README-ru.md +38 -11
  5. package/README-zh.md +36 -11
  6. package/README.md +38 -11
  7. package/dist/classifier.d.ts +44 -10
  8. package/dist/classifier.d.ts.map +1 -1
  9. package/dist/classifier.js +299 -48
  10. package/dist/classifier.js.map +1 -1
  11. package/dist/encoded-text.d.ts +7 -0
  12. package/dist/encoded-text.d.ts.map +1 -0
  13. package/dist/encoded-text.js +63 -0
  14. package/dist/encoded-text.js.map +1 -0
  15. package/dist/fold.d.ts +1 -2
  16. package/dist/fold.d.ts.map +1 -1
  17. package/dist/fold.js +3 -2
  18. package/dist/fold.js.map +1 -1
  19. package/dist/histor.d.ts +79 -4
  20. package/dist/histor.d.ts.map +1 -1
  21. package/dist/histor.js +11 -7
  22. package/dist/histor.js.map +1 -1
  23. package/dist/index.d.ts +6 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +8 -4
  26. package/dist/index.js.map +1 -1
  27. package/dist/mcp-tools.d.ts.map +1 -1
  28. package/dist/mcp-tools.js +29 -4
  29. package/dist/mcp-tools.js.map +1 -1
  30. package/dist/pin-store.d.ts.map +1 -1
  31. package/dist/pin-store.js +1 -0
  32. package/dist/pin-store.js.map +1 -1
  33. package/dist/pinning.d.ts +1 -1
  34. package/dist/pinning.d.ts.map +1 -1
  35. package/dist/pinning.js +12 -4
  36. package/dist/pinning.js.map +1 -1
  37. package/dist/result-screen.d.ts +19 -0
  38. package/dist/result-screen.d.ts.map +1 -0
  39. package/dist/result-screen.js +86 -0
  40. package/dist/result-screen.js.map +1 -0
  41. package/dist/scan-lock.d.ts.map +1 -1
  42. package/dist/scan-lock.js +2 -1
  43. package/dist/scan-lock.js.map +1 -1
  44. package/dist/scan-report.d.ts.map +1 -1
  45. package/dist/scan-report.js +15 -0
  46. package/dist/scan-report.js.map +1 -1
  47. package/dist/scan.d.ts +40 -0
  48. package/dist/scan.d.ts.map +1 -1
  49. package/dist/scan.js +272 -25
  50. package/dist/scan.js.map +1 -1
  51. package/dist/static-scan.d.ts +4 -3
  52. package/dist/static-scan.d.ts.map +1 -1
  53. package/dist/static-scan.js +40 -9
  54. package/dist/static-scan.js.map +1 -1
  55. package/dist/toxic-flow.d.ts +33 -0
  56. package/dist/toxic-flow.d.ts.map +1 -0
  57. package/dist/toxic-flow.js +117 -0
  58. package/dist/toxic-flow.js.map +1 -0
  59. package/dist/types.d.ts +12 -0
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/wrap-cli.d.ts +3 -0
  62. package/dist/wrap-cli.d.ts.map +1 -1
  63. package/dist/wrap-cli.js +17 -6
  64. package/dist/wrap-cli.js.map +1 -1
  65. package/dist/wrap-state.js +2 -2
  66. package/dist/wrap-state.js.map +1 -1
  67. package/dist/wrap.d.ts.map +1 -1
  68. package/dist/wrap.js +20 -2
  69. package/dist/wrap.js.map +1 -1
  70. package/docs/scan.es.md +29 -14
  71. package/docs/scan.fr.md +29 -14
  72. package/docs/scan.md +29 -14
  73. package/docs/scan.ru.md +29 -14
  74. package/docs/scan.zh.md +29 -14
  75. package/docs/scanner-comparison.es.md +3 -12
  76. package/docs/scanner-comparison.fr.md +3 -13
  77. package/docs/scanner-comparison.md +3 -12
  78. package/docs/scanner-comparison.ru.md +3 -12
  79. package/docs/scanner-comparison.zh.md +3 -7
  80. package/docs/security-hardening-language-boundary.md +306 -0
  81. package/package.json +6 -3
  82. package/scripts/check-quality-gate.mjs +64 -0
  83. package/scripts/quality-public-key.pem +3 -0
package/docs/scan.md CHANGED
@@ -5,18 +5,19 @@
5
5
  `warden-mcp scan` reads the MCP configuration you already have, connects to every server it starts, and vets the tool definitions before a model sees them. It is the same gate chain as [`wrap`](../README.md#protect-claude-desktop-or-cursor-with-one-wrapper) and the [library](integration.md), run once over your configs instead of in front of one server.
6
6
 
7
7
  ```bash
8
- npx -y @aimarket/warden@0.9.0 scan
8
+ npx -y @aimarket/warden@0.11.0 scan
9
9
  ```
10
10
 
11
11
  ```text
12
- WARDEN scan 0.9.0 · ruleset 10 sha256-lJuKKKV5mtru… · block at high
12
+ WARDEN scan 0.10.0 · ruleset 10 sha256-lJuKKKV5mtru… · block at high
13
13
  read .mcp.json (claude-code, 3 servers)
14
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
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) THREAT_SSH_KEY_READ(notes)
17
+ ! error broken claude-code could not start: spawn /nonexistent/bin/server ENOENT
18
18
 
19
19
  3 servers: 1 allowed, 1 blocked, 1 not checked, 0 skipped.
20
+ Details: warden-mcp scan --json, or --markdown FILE. To review a changed server: warden-mcp scan --lock warden.lock.json --update-lock.
20
21
  ```
21
22
 
22
23
  No account, no API key, no model. The only network traffic is to the servers in your config, and to the HISTOR log if you pass `--histor`.
@@ -85,19 +86,21 @@ jobs:
85
86
  runs-on: ubuntu-latest
86
87
  steps:
87
88
  - uses: actions/checkout@v4
88
- - uses: alexar76/warden@v0.9.0
89
+ - uses: alexar76/warden@v0.11.0
89
90
  with:
90
91
  upload-sarif: 'true'
91
92
  ```
92
93
 
93
94
  By default it reads the project files, does not start stdio servers (a pull request chooses that program), refuses non-public addresses, uses `warden.lock.json` when it exists, writes the job summary, and fails on a blocked server. Inputs: `config`, `working-directory`, `lock`, `launch-stdio`, `public-only`, `fail-on`, `histor`, `classifier-url`, `classifier-model`, `classifier-blocks`, `sarif`, `upload-sarif`, `version`. Outputs: `blocked`, `servers`, `sarif`. Pin the action by commit SHA in production workflows.
94
95
 
96
+ To be told when a server you use changes, run the same job on a schedule as well (`on: { schedule: [{ cron: '17 6 * * *' }] }`): with a committed `warden.lock.json`, a server whose tool definitions changed fails the run with `TOOL_DEF_DRIFT` and the summary shows the diff.
97
+
95
98
  ## pre-commit
96
99
 
97
100
  ```yaml
98
101
  repos:
99
102
  - repo: https://github.com/alexar76/warden
100
- rev: v0.9.0
103
+ rev: v0.10.0
101
104
  hooks:
102
105
  - id: warden-scan # changed configs; does not start stdio servers
103
106
  - id: warden-lock # the project matches warden.lock.json; starts stdio servers
@@ -114,11 +117,23 @@ Both run the published package through `npx`, so Node 20 or later must be on `PA
114
117
 
115
118
  At session start it scans the servers Claude Code starts for the project, tells you which are blocked, and tells the model by name and finding code only. A blocked description never enters the model's context. Calls to tools of a blocked server, or to a blocked tool, are then denied by a `PreToolUse` hook that reads one small file and starts nothing. Details and limits: [claude-plugin/README.md](../claude-plugin/README.md).
116
119
 
120
+ ## Toxic flows: safe one by one, dangerous together
121
+
122
+ A client hands every server's tools to one model. When the servers it starts include a tool that reads your private data, one that lets text from outside in (a web page, an issue, an email) and one that can send data out, text arriving through the second can tell the model to read with the first and send with the third. No server is at fault, so no per-server verdict shows it. `scan` reports it per client:
123
+
124
+ ```text
125
+ ⚠ toxic flow claude-code can read private data (filesystem.read_file, …), take in text from outside (fetch.fetch) and send data out (fetch.fetch). …
126
+ ```
127
+
128
+ The capabilities are read from tool names and parameter names (`read_file`, `send_email`, a `url` or `to` parameter), not from descriptions: the reading does not depend on the description's language, and a description cannot talk its way out of it. A flow is advisory: it never blocks and never changes the exit code. It is in `--json` (`flows`) and in the Markdown summary. To break one, drop a leg — a `fetch` server you do not need, or a filesystem server scoped to one folder in a separate client.
129
+
117
130
  ## HISTOR: is this server serving you what it serves everyone?
118
131
 
119
132
  [HISTOR](https://histor.modelmarket.dev) is a public log that records, every day, the tool definitions of every remote server in the official MCP registry. `--histor` asks it one question per remote server: is the tool set you were just served the one HISTOR observes?
120
133
 
121
- What is sent: the endpoint with scheme, host and path only (no query, no user name or password), and the [MTL/1](https://github.com/alexar76/awr) digest of the tool set. No tool description, no header, no stdio server. Endpoints on private hosts or addresses, and paths that look like they carry a key, are not sent at all; the report says why.
134
+ What is sent: the endpoint with scheme, host and path only (no query, no user name or password), and the [MTL/1](https://github.com/alexar76/awr) digest of the tool set. No tool description, no header. Endpoints on private hosts or addresses, and paths that look like they carry a key, are not sent at all; the report says why.
135
+
136
+ A stdio server started from the public npm or PyPI registry (`npx`, `bunx`, `npm exec`, `pnpm dlx`, `uvx`, `uv tool run`, `pipx run`) is named by its package — `npm:<name>` or `pypi:<name>`, without the version — and the same digest. HISTOR installs each published version of such a package in a gVisor sandbox (decoy credentials, no route out; each tool called once with canary arguments, against nothing real) and records its tools and what it did and records its tools, so `previously-observed` here means you run an older version, and `different` a version HISTOR has not reached yet or a package that is not the published one. A local path, a git or tarball URL, a private registry (`--registry`, `--index-url`) or a plain `node`/`python` launch is never sent. For a package HISTOR also says, as advisory findings, whether its name imitates a popular one (`HISTOR_PACKAGE_LOOKALIKE`: one character away, the same name under another scope, or other separators, from a package downloaded at least 100 times more) and whether the version it observed carries the marks of a stolen publishing token: `HISTOR_PACKAGE_PROVENANCE_LOST` (no attested CI build where the previous version had one), `HISTOR_PACKAGE_INSTALL_SCRIPTS` (new install scripts), `HISTOR_PACKAGE_PUBLISHER_CHANGED`, `HISTOR_PACKAGE_NEW_DEPENDENCIES`. And what the package did in the sandbox: `HISTOR_PACKAGE_READS_SECRETS` (it opened decoy credentials — SSH keys, cloud or registry tokens, a wallet — at install, at startup or when a tool was called), `HISTOR_PACKAGE_PERSISTENCE` (it wrote where it would outlive the session, such as `.bashrc` or `authorized_keys`), `HISTOR_PACKAGE_STARTUP_NETWORK` (it reached out before any tool was called), `HISTOR_PACKAGE_STARTS_PROGRAMS`, `HISTOR_PACKAGE_INSTALL_BEHAVIOUR` (what its install scripts do). Loading `.env` from the working directory and calling its own API during a tool call are not findings. Why a package did not start, from what it printed: `HISTOR_PACKAGE_SDK_INCOMPATIBLE` (medium — written for MCP SDK 1.x, so a fresh install with `mcp` 2.x crashes on import; when HISTOR also ran it with `mcp<2`, how many tools it listed then), `HISTOR_PACKAGE_NEEDS_CONFIG` (info — the key or setting it asked for, or that its tools were read with placeholders for them) and `HISTOR_PACKAGE_KEY_SENT_TO` (low — where it sent the placeholder key HISTOR set; check it is the service's own API). Every sandbox finding comes from the same conditions: Linux under gVisor, no internet, decoy secrets ([test conditions](https://histor.modelmarket.dev/#conditions)). With `--histor`, the report also gives a link to a HISTOR feed of changes to the scanned servers HISTOR knows (`historWatch` in `--json`): subscribe in any feed reader.
122
137
 
123
138
  | Answer | Meaning |
124
139
  |---|---|
@@ -135,13 +150,13 @@ WARDEN's rules are offline and deterministic, and they miss what no rule names:
135
150
 
136
151
  ```bash
137
152
  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
153
+ WARDEN_CLASSIFIER_API_KEY=… warden-mcp scan --classifier-url https://api.deepseek.com --classifier-model deepseek-flash --classifier-reasoning-effort none
139
154
  ```
140
155
 
141
- - **It is off by default and sends tool text.** With both flags, every tool's name, description and schemas go to that endpoint. A local model keeps them on your machine. A key, if needed, comes only from `WARDEN_CLASSIFIER_API_KEY`, never from a flag.
142
- - **Advisory unless `--classifier-blocks`.** Its verdicts are reported as `TOOL_DEF_CLASSIFIER`. With `--classifier-blocks` a verdict at or above the block threshold (`high` by default) blocks the tool like a rule. A classifier that does not answer is reported and never blocks.
143
- - **Same question as HISTOR's.** The prompt, the four categories (`instruction_to_model`, `exfiltration`, `secret_request`, `concealment`) and the answer format are the HISTOR log's. The tool text sits between markers with a random suffix it cannot forge, and the model's answer is checked field by field. With `--histor`, the log's own stored verdict on the tool set you were served is shown too (`HISTOR_CLASSIFIER`, advisory).
144
- - **It does not read annotations or extension fields.** The rules do.
156
+ - **Opt-in; sends every advertised field.** Name, title, description, both schemas, annotations and extension metadata go to the endpoint, together with a normalized reading when needed. The key comes only from `WARDEN_CLASSIFIER_API_KEY`.
157
+ - **Advisory by default; fail closed with `--classifier-blocks`.** Findings use `TOOL_DEF_CLASSIFIER`. Enforcing mode also refuses incomplete coverage, truncation, uncertainty, malformed evidence, timeouts and provider errors (`CLASSIFIER_INCOMPLETE`). These refusals are inspection failures, not detected attacks. They cannot enter a lock through `--update-lock`.
158
+ - **Authority, in any language.** Categories are `instruction_to_model`, `exfiltration`, `secret_request`, `concealment` and `cross_tool`. The prompt separates legitimate tool requirements from attempts to override the user, invent consent or control another tool. Random fences help delimit untrusted data; they are not a proof against injection. The field-reference protocol requires one decision per input and host-validated evidence field IDs. WARDEN resolves source previews itself and retries each incomplete input once in isolation. Provider outages do not fan out into per-tool requests.
159
+ - **Review is not approval.** Model language coverage varies. For a language-independent admission boundary use [`wrap --require-approval`](security-hardening-language-boundary.md). An LLM cannot create that approval. Historical HISTOR classifier results remain advisory and use their historical protocol.
145
160
 
146
161
  Measured on 2026-10-09 with `deepseek-flash`, the model HISTOR uses, on MCPTox (the published benchmark described in the comparison below) and on our own sets:
147
162
 
@@ -175,7 +190,7 @@ The last three rows come from MCPTox (Wang et al., AAAI 2026), a published bench
175
190
  - **Know which numbers are fitted.** We wrote the first attack set. Ruleset v9 was written after we had read both attack sets, and v10 after the 22 MCPTox servers of the other half. So 20 and 10 are the recall of rules fitted to what they were shown. The held-out MCPTox row, the clean servers, the corpus and the whole 0.8.2 column are not fitted. On the corpus v10 blocks exactly what v8 blocked.
176
191
  - **MCPTox tests a shape, not a keyword.** Almost every poisoned tool tells the model to call another tool first, or to change another tool's arguments. v10 names that shape (`TOOL_DEF_CROSS_TOOL`, see [the gate chain](gates.md)) and blocks 171 of 218 held-out tools, against 189 of 267 on the half it was written from. Add the benchmark's `<IMPORTANT>` or "Ignore the previous instructions" prefix and every scanner we ran catches it; we did not run mcp-shield on that set. On the clean servers mcp-audit blocks AmapMap and BaiduMap, and mcp-shield blocks FileSystem, AdFin and Commander on "sensitive file access".
177
192
  - **What WARDEN still misses:** 47 of the 218 held-out MCPTox tools, an instruction in Chinese to read `.env`, a paraphrase with no keyword ("gather every credential file … keep this step to yourself") and "include the output of env". It reports 41 of those 47, and all three of ours, as advisory, not as blocks. They need a meaning-based check, not another rule: with the optional classifier above, 191 of the 218 are blocked at `high` and all 218 are flagged.
178
- - **Not compared:** Snyk Agent Scan, formerly Invariant mcp-scan and the most used scanner. It judges descriptions on Snyk's servers and needs an account token. mcp-audit also ships a lock file, a PR diff mode and SARIF; WARDEN is not alone in that slot.
193
+ - **WARDEN is not alone in that slot.** mcp-audit also ships a lock file, a PR diff mode and SARIF.
179
194
 
180
195
  ## What scan does not do
181
196
 
package/docs/scan.ru.md CHANGED
@@ -5,18 +5,19 @@
5
5
  `warden-mcp scan` читает уже существующую у вас конфигурацию MCP, подключается к каждому серверу из неё и проверяет определения инструментов до того, как их увидит модель. Это та же цепочка гейтов, что у [`wrap`](../README-ru.md) и у [библиотеки](integration.ru.md), только пройденная один раз по вашим конфигам, а не перед одним сервером.
6
6
 
7
7
  ```bash
8
- npx -y @aimarket/warden@0.9.0 scan
8
+ npx -y @aimarket/warden@0.11.0 scan
9
9
  ```
10
10
 
11
11
  ```text
12
- WARDEN scan 0.9.0 · ruleset 10 sha256-lJuKKKV5mtru… · block at high
12
+ WARDEN scan 0.10.0 · ruleset 10 sha256-lJuKKKV5mtru… · block at high
13
13
  read .mcp.json (claude-code, 3 servers)
14
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
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) THREAT_SSH_KEY_READ(notes)
17
+ ! error broken claude-code could not start: spawn /nonexistent/bin/server ENOENT
18
18
 
19
19
  3 servers: 1 allowed, 1 blocked, 1 not checked, 0 skipped.
20
+ Details: warden-mcp scan --json, or --markdown FILE. To review a changed server: warden-mcp scan --lock warden.lock.json --update-lock.
20
21
  ```
21
22
 
22
23
  Без аккаунта, без API-ключа, без модели. Сетевой трафик идёт только к серверам из вашего конфига и, если передан `--histor`, к журналу HISTOR.
@@ -85,19 +86,21 @@ jobs:
85
86
  runs-on: ubuntu-latest
86
87
  steps:
87
88
  - uses: actions/checkout@v4
88
- - uses: alexar76/warden@v0.9.0
89
+ - uses: alexar76/warden@v0.11.0
89
90
  with:
90
91
  upload-sarif: 'true'
91
92
  ```
92
93
 
93
94
  По умолчанию Action читает файлы проекта, не запускает 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 закрепляйте Action по SHA коммита.
94
95
 
96
+ Чтобы узнавать, когда меняется сервер, которым вы пользуетесь, запускайте ту же задачу ещё и по расписанию (`on: { schedule: [{ cron: '17 6 * * *' }] }`): при закоммиченном `warden.lock.json` сервер, у которого изменились определения инструментов, роняет запуск с `TOOL_DEF_DRIFT`, а в сводке виден diff.
97
+
95
98
  ## pre-commit
96
99
 
97
100
  ```yaml
98
101
  repos:
99
102
  - repo: https://github.com/alexar76/warden
100
- rev: v0.9.0
103
+ rev: v0.10.0
101
104
  hooks:
102
105
  - id: warden-scan # изменённые конфиги; stdio-серверы не запускаются
103
106
  - id: warden-lock # проект соответствует warden.lock.json; stdio-серверы запускаются
@@ -114,11 +117,23 @@ repos:
114
117
 
115
118
  В начале сессии плагин проверяет серверы, которые Claude Code запускает для проекта, сообщает вам, какие из них заблокированы, а модели — только имя и код находки. Описание заблокированного инструмента никогда не попадает в контекст модели. Вызовы инструментов заблокированного сервера или заблокированного инструмента затем запрещает хук `PreToolUse`: он читает один небольшой файл и ничего не запускает. Подробности и ограничения: [claude-plugin/README.md](../claude-plugin/README.md).
116
119
 
120
+ ## Опасные связки: по отдельности безопасно, вместе — нет
121
+
122
+ Клиент отдаёт инструменты всех серверов одной модели. Если среди серверов есть инструмент, который читает ваши приватные данные, инструмент, через который приходит чужой текст (веб-страница, задача, письмо), и инструмент, который может отправить данные наружу, то текст, пришедший через второй, может велеть модели прочитать первым и отправить третьим. Ни один сервер не виноват, поэтому вердикт по отдельному серверу этого не покажет. `scan` сообщает об этом по каждому клиенту:
123
+
124
+ ```text
125
+ ⚠ toxic flow claude-code can read private data (filesystem.read_file, …), take in text from outside (fetch.fetch) and send data out (fetch.fetch). …
126
+ ```
127
+
128
+ Возможности определяются по именам инструментов и параметров (`read_file`, `send_email`, параметр `url` или `to`), а не по описаниям: язык описания не важен, и описание не может себя оправдать. Связка — рекомендательная находка: она никогда не блокирует и не меняет код выхода. Она есть в `--json` (`flows`) и в сводке Markdown. Чтобы разорвать связку, уберите одну из ног — например, ненужный сервер `fetch` или вынесите файловый сервер с доступом к одной папке в отдельный клиент.
129
+
117
130
  ## HISTOR: отдаёт ли сервер вам то же, что всем?
118
131
 
119
132
  [HISTOR](https://histor.modelmarket.dev) — публичный журнал, который каждый день записывает определения инструментов всех удалённых серверов из официального реестра MCP. `--histor` задаёт ему один вопрос о каждом удалённом сервере: тот ли это набор инструментов, который видит HISTOR?
120
133
 
121
- Что отправляется: адрес эндпоинта — только схема, хост и путь (без query, без имени пользователя и пароля) — и дайджест набора инструментов по [MTL/1](https://github.com/alexar76/awr). Ни описаний инструментов, ни заголовков, ни stdio-серверов. Эндпоинты на частных хостах и адресах, а также пути, похожие на содержащие ключ, не отправляются вовсе; отчёт объясняет почему.
134
+ Что отправляется: адрес эндпоинта — только схема, хост и путь (без query, без имени пользователя и пароля) — и дайджест набора инструментов по [MTL/1](https://github.com/alexar76/awr). Ни описаний инструментов, ни заголовков. Эндпоинты на частных хостах и адресах, а также пути, похожие на содержащие ключ, не отправляются вовсе; отчёт объясняет почему.
135
+
136
+ Stdio-сервер, запущенный из публичного реестра npm или PyPI (`npx`, `bunx`, `npm exec`, `pnpm dlx`, `uvx`, `uv tool run`, `pipx run`), называется по пакету — `npm:<имя>` или `pypi:<имя>`, без версии — с тем же дайджестом. HISTOR ставит каждую опубликованную версию такого пакета в песочнице gVisor (приманки вместо секретов, без маршрута наружу; каждый инструмент вызывается один раз с подставными аргументами, ни на что настоящее не действуя) и записывает его инструменты и то, что он делал и записывает его инструменты, поэтому `previously-observed` здесь значит, что у вас более старая версия, а `different` — версия, до которой HISTOR ещё не дошёл, или пакет, который не совпадает с опубликованным. Локальный путь, git- или tarball-адрес, частный реестр (`--registry`, `--index-url`) или простой запуск `node`/`python` не отправляются никогда. Для пакета HISTOR также сообщает, рекомендательными находками, не подражает ли его имя популярному (`HISTOR_PACKAGE_LOOKALIKE`: на один символ, то же имя под другим scope или с другими разделителями, при том что популярный скачивают хотя бы в 100 раз чаще) и нет ли у прочитанной версии признаков украденного токена публикации: `HISTOR_PACKAGE_PROVENANCE_LOST` (нет подтверждённой сборки в CI, хотя у предыдущей версии была), `HISTOR_PACKAGE_INSTALL_SCRIPTS` (появились установочные скрипты), `HISTOR_PACKAGE_PUBLISHER_CHANGED`, `HISTOR_PACKAGE_NEW_DEPENDENCIES`. И что пакет делал в песочнице: `HISTOR_PACKAGE_READS_SECRETS` (открыл приманки — SSH-ключи, токены облаков или реестров, кошелёк — при установке, при запуске или при вызове инструмента), `HISTOR_PACKAGE_PERSISTENCE` (писал туда, где пережил бы сессию, например в `.bashrc` или `authorized_keys`), `HISTOR_PACKAGE_STARTUP_NETWORK` (выходил в сеть до вызова любого инструмента), `HISTOR_PACKAGE_STARTS_PROGRAMS`, `HISTOR_PACKAGE_INSTALL_BEHAVIOUR` (что делают его установочные скрипты). Загрузка `.env` из рабочей папки и обращения к собственному API во время вызова инструмента находками не считаются. Почему пакет не запустился — по тому, что он напечатал: `HISTOR_PACKAGE_SDK_INCOMPATIBLE` (medium — написан под MCP SDK 1.x, поэтому свежая установка с `mcp` 2.x падает при импорте; если HISTOR запускал его и с `mcp<2`, — сколько инструментов он тогда отдал), `HISTOR_PACKAGE_NEEDS_CONFIG` (info — ключ или настройка, которые он потребовал, либо то, что его инструменты прочитаны с заглушками для них) и `HISTOR_PACKAGE_KEY_SENT_TO` (low — куда он отправил ключ-заглушку HISTOR; проверьте, что это собственный API сервиса). Все находки песочницы получены в одних условиях: Linux под gVisor, без интернета, приманки вместо секретов ([условия проверки](https://histor.modelmarket.dev/#conditions)). С `--histor` отчёт также даёт ссылку на ленту HISTOR с изменениями тех просканированных серверов, которые HISTOR знает (`historWatch` в `--json`): на неё можно подписаться в любом читателе лент.
122
137
 
123
138
  | Ответ | Значение |
124
139
  |---|---|
@@ -135,13 +150,13 @@ repos:
135
150
 
136
151
  ```bash
137
152
  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
153
+ WARDEN_CLASSIFIER_API_KEY=… warden-mcp scan --classifier-url https://api.deepseek.com --classifier-model deepseek-flash --classifier-reasoning-effort none
139
154
  ```
140
155
 
141
- - **По умолчанию выключен и отправляет текст инструментов.** С обоими флагами имя, описание и схемы каждого инструмента уходят на этот адрес. Локальная модель оставляет их на вашей машине. Ключ, если нужен, берётся только из `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
- - **Аннотации и поля расширений он не читает.** Их читают правила.
156
+ - **Включается явно и отправляет все объявленные поля.** Имя, заголовок, описание, обе схемы, annotations и метаданные расширений, а при необходимости и нормализованное прочтение. Ключ берётся только из `WARDEN_CLASSIFIER_API_KEY`.
157
+ - **По умолчанию — рекомендация; с `--classifier-blocks` неполная проверка запрещает допуск.** Находки имеют код `TOOL_DEF_CLASSIFIER`. Ошибки провайдера, таймаут, усечение текста, неопределённость, неполный или некорректный ответ дают `CLASSIFIER_INCOMPLETE`. Это отказ проверки, а не обнаруженная атака. Такой результат нельзя утвердить через `--update-lock`.
158
+ - **Проверка полномочий на любом языке.** Пять категорий: `instruction_to_model`, `exfiltration`, `secret_request`, `concealment`, `cross_tool`. Промпт отличает требования самого инструмента от подмены указаний пользователя, выдуманного согласия и управления чужими инструментами. Случайные разделители не гарантируют защиту от инъекции. Протокол требует решение по каждому инструменту и ссылки на номера полей. WARDEN сам извлекает доказательства из исходника. Неполное решение повторно проверяется один раз отдельно; сбои API не размножают запросы.
159
+ - **Проверка не даёт доверия.** Владение языками зависит от модели. Независимую от языка границу допуска даёт [`wrap --require-approval`](security-hardening-language-boundary.md); LLM не может выдать это утверждение. Старые ответы HISTOR остаются рекомендательными и используют прежний протокол.
145
160
 
146
161
  Замер 9 октября 2026 года на `deepseek-flash`, той же модели, что у HISTOR, на MCPTox (опубликованный бенчмарк, он описан в сравнении ниже) и на наших наборах:
147
162
 
@@ -175,7 +190,7 @@ WARDEN_CLASSIFIER_API_KEY=… warden-mcp scan --classifier-url https://api.deeps
175
190
  - **Помните, какие числа подогнаны.** Первый набор атак написали мы. Ruleset v9 написан после того, как мы прочли оба набора атак, а v10 — после 22 серверов MCPTox из другой половины. Так что 20 и 10 — это полнота правил, подогнанных под увиденное. Отложенная строка MCPTox, чистые серверы, корпус и весь столбец 0.8.2 не подогнаны. На корпусе v10 блокирует ровно то же, что v8.
176
191
  - **MCPTox проверяет форму, а не ключевое слово.** Почти каждый отравленный инструмент велит модели сначала вызвать другой инструмент или изменить аргументы другого инструмента. v10 называет эту форму (`TOOL_DEF_CROSS_TOOL`, см. [цепочку гейтов](gates.ru.md)) и блокирует 171 из 218 отложенных инструментов против 189 из 267 на половине, по которой он написан. С префиксом бенчмарка `<IMPORTANT>` или «Ignore the previous instructions» их ловит каждый запущенный нами сканер; mcp-shield на этом наборе мы не запускали. На чистых серверах mcp-audit блокирует AmapMap и BaiduMap, а mcp-shield — FileSystem, AdFin и Commander за «sensitive file access».
177
192
  - **Что WARDEN всё ещё пропускает:** 47 из 218 отложенных инструментов MCPTox, инструкцию по-китайски прочитать `.env`, перефразировку без ключевых слов («gather every credential file … keep this step to yourself») и «include the output of env». О 41 из этих 47 и обо всех трёх наших он сообщает рекомендательно, но не блокирует. Здесь нужна проверка по смыслу, а не ещё одно правило: с необязательным классификатором выше 191 из 218 блокируются на уровне `high`, а помечаются все 218.
178
- - **Не сравнивали:** Snyk Agent Scan, бывший Invariant mcp-scan, самый используемый сканер. Он судит описания на серверах Snyk и требует токен аккаунта. У mcp-audit тоже есть лок-файл, режим диффа для PR и SARIF; WARDEN в этой нише не один.
193
+ - **WARDEN в этой нише не один.** У mcp-audit тоже есть лок-файл, режим диффа для PR и SARIF.
179
194
 
180
195
  ## Чего scan не делает
181
196
 
package/docs/scan.zh.md CHANGED
@@ -5,18 +5,19 @@
5
5
  `warden-mcp scan` 读取你已有的 MCP 配置,连接其中启动的每个服务器,并在模型看到工具定义之前对其进行审查。它与 [`wrap`](../README-zh.md) 和[库](integration.zh.md)使用同一条关卡链,只是对你的配置运行一次,而不是挡在单个服务器前面。
6
6
 
7
7
  ```bash
8
- npx -y @aimarket/warden@0.9.0 scan
8
+ npx -y @aimarket/warden@0.11.0 scan
9
9
  ```
10
10
 
11
11
  ```text
12
- WARDEN scan 0.9.0 · ruleset 10 sha256-lJuKKKV5mtru… · block at high
12
+ WARDEN scan 0.10.0 · ruleset 10 sha256-lJuKKKV5mtru… · block at high
13
13
  read .mcp.json (claude-code, 3 servers)
14
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
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) THREAT_SSH_KEY_READ(notes)
17
+ ! error broken claude-code could not start: spawn /nonexistent/bin/server ENOENT
18
18
 
19
19
  3 servers: 1 allowed, 1 blocked, 1 not checked, 0 skipped.
20
+ Details: warden-mcp scan --json, or --markdown FILE. To review a changed server: warden-mcp scan --lock warden.lock.json --update-lock.
20
21
  ```
21
22
 
22
23
  无需账号、无需 API 密钥、不调用模型。唯一的网络流量是发往你配置中的服务器;如果传入 `--histor`,还会发往 HISTOR 日志。
@@ -85,19 +86,21 @@ jobs:
85
86
  runs-on: ubuntu-latest
86
87
  steps:
87
88
  - uses: actions/checkout@v4
88
- - uses: alexar76/warden@v0.9.0
89
+ - uses: alexar76/warden@v0.11.0
89
90
  with:
90
91
  upload-sarif: 'true'
91
92
  ```
92
93
 
93
94
  默认情况下,它读取项目文件,不启动 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
 
96
+ 若想在你使用的服务器发生变化时得到通知,可以再按计划运行同一个 job(`on: { schedule: [{ cron: '17 6 * * *' }] }`):只要仓库里提交了 `warden.lock.json`,工具定义发生变化的服务器就会以 `TOOL_DEF_DRIFT` 让这次运行失败,摘要中会显示 diff。
97
+
95
98
  ## pre-commit
96
99
 
97
100
  ```yaml
98
101
  repos:
99
102
  - repo: https://github.com/alexar76/warden
100
- rev: v0.9.0
103
+ rev: v0.10.0
101
104
  hooks:
102
105
  - id: warden-scan # 改动过的配置;不启动 stdio 服务器
103
106
  - id: warden-lock # 项目与 warden.lock.json 一致;会启动 stdio 服务器
@@ -114,11 +117,23 @@ repos:
114
117
 
115
118
  会话开始时,它扫描 Claude Code 为该项目启动的服务器,告诉你哪些被阻止,而对模型只给出名称和发现代码。被阻止的描述永远不会进入模型的上下文。之后,`PreToolUse` 钩子会拒绝对被阻止服务器的工具或被阻止工具的调用;它只读取一个小文件,不启动任何程序。详情与限制:[claude-plugin/README.md](../claude-plugin/README.md)。
116
119
 
120
+ ## 有毒数据流:单个安全,组合危险
121
+
122
+ 客户端把所有服务器的工具交给同一个模型。如果其中有一个工具读取你的私有数据,一个工具引入外部文本(网页、issue、邮件),还有一个工具能把数据发出去,那么经第二个工具进来的文本就可以让模型用第一个读取、用第三个发送。没有哪个服务器有错,所以按服务器给出的结论看不到它。`scan` 按客户端报告:
123
+
124
+ ```text
125
+ ⚠ toxic flow claude-code can read private data (filesystem.read_file, …), take in text from outside (fetch.fetch) and send data out (fetch.fetch). …
126
+ ```
127
+
128
+ 能力从工具名和参数名(`read_file`、`send_email`、`url` 或 `to` 参数)中读取,而不是从描述中读取:结果与描述的语言无关,描述也无法为自己开脱。数据流只是提示:从不阻止,也不改变退出码。它出现在 `--json`(`flows`)和 Markdown 摘要中。要打断它,去掉其中一环——例如不需要的 `fetch` 服务器,或把只开放一个文件夹的文件服务器放到另一个客户端。
129
+
117
130
  ## HISTOR:这个服务器给你的是否和给所有人的一样?
118
131
 
119
132
  [HISTOR](https://histor.modelmarket.dev) 是一个公开日志,每天记录官方 MCP 注册表中所有远程服务器的工具定义。`--histor` 对每个远程服务器问它一个问题:你刚收到的工具集,是否就是 HISTOR 观察到的那一个?
120
133
 
121
- 发送的内容:端点(只有协议、主机和路径,不含 query、用户名或密码),以及工具集的 [MTL/1](https://github.com/alexar76/awr) 摘要。不发送任何工具描述、请求头或 stdio 服务器。私有主机或地址上的端点,以及看起来带有密钥的路径,根本不会发送;报告会说明原因。
134
+ 发送的内容:端点(只有协议、主机和路径,不含 query、用户名或密码),以及工具集的 [MTL/1](https://github.com/alexar76/awr) 摘要。不发送任何工具描述或请求头。私有主机或地址上的端点,以及看起来带有密钥的路径,根本不会发送;报告会说明原因。
135
+
136
+ 从公共 npm 或 PyPI 注册表启动的 stdio 服务器(`npx`、`bunx`、`npm exec`、`pnpm dlx`、`uvx`、`uv tool run`、`pipx run`)以其软件包命名——`npm:<名称>` 或 `pypi:<名称>`,不带版本——并附上同样的摘要。HISTOR 会在 gVisor 沙箱中(诱饵凭据、无对外路由;每个工具用金丝雀参数调用一次,不作用于任何真实对象)并记录其工具及其行为安装这类软件包的每个已发布版本并记录其工具,因此这里的 `previously-observed` 表示你运行的是较旧的版本,`different` 表示 HISTOR 尚未覆盖到的版本,或者与已发布版本不一致的软件包。本地路径、git 或 tarball 地址、私有注册表(`--registry`、`--index-url`)以及直接用 `node`/`python` 启动的服务器,永远不会被发送。 对于软件包,HISTOR 还会以提示性发现告诉你:它的名称是否在模仿热门软件包(`HISTOR_PACKAGE_LOOKALIKE`:相差一个字符、在另一个 scope 下同名或仅分隔符不同,且热门软件包的下载量至少高 100 倍),以及所观察的版本是否带有发布令牌被盗的迹象:`HISTOR_PACKAGE_PROVENANCE_LOST`(上一个版本有经证明的 CI 构建,这个版本没有)、`HISTOR_PACKAGE_INSTALL_SCRIPTS`(新增安装脚本)、`HISTOR_PACKAGE_PUBLISHER_CHANGED`、`HISTOR_PACKAGE_NEW_DEPENDENCIES`。 以及软件包在沙箱中的行为:`HISTOR_PACKAGE_READS_SECRETS`(在安装、启动或工具被调用时打开了诱饵凭据——SSH 密钥、云或注册表令牌、钱包),`HISTOR_PACKAGE_PERSISTENCE`(写入了会在会话之后留存的位置,例如 `.bashrc` 或 `authorized_keys`),`HISTOR_PACKAGE_STARTUP_NETWORK`(在调用任何工具之前就访问了网络),`HISTOR_PACKAGE_STARTS_PROGRAMS`,`HISTOR_PACKAGE_INSTALL_BEHAVIOUR`(其安装脚本做了什么)。从工作目录加载 `.env`、在工具调用期间访问其自身 API,都不算发现。软件包为何没有启动——依据它打印的内容:`HISTOR_PACKAGE_SDK_INCOMPATIBLE`(medium——为 MCP SDK 1.x 编写,因此用 `mcp` 2.x 全新安装会在导入时失败;如果 HISTOR 也用 `mcp<2` 运行过它,则给出当时列出的工具数量)、`HISTOR_PACKAGE_NEEDS_CONFIG`(info——它要求的密钥或设置,或其工具是在占位值下读取的)以及 `HISTOR_PACKAGE_KEY_SENT_TO`(low——它把 HISTOR 设置的占位密钥发送到了哪里;请确认那是该服务自己的 API)。所有沙箱发现都来自相同的条件:gVisor 下的 Linux、无互联网、诱饵机密([测试条件](https://histor.modelmarket.dev/#conditions))。 使用 `--histor` 时,报告还会给出一个 HISTOR 订阅源链接,包含 HISTOR 已知的被扫描服务器的变更(`--json` 中的 `historWatch`):可在任意订阅阅读器中订阅。
122
137
 
123
138
  | 回答 | 含义 |
124
139
  |---|---|
@@ -135,13 +150,13 @@ WARDEN 的规则离线、确定性地运行,也会漏掉任何规则都没提
135
150
 
136
151
  ```bash
137
152
  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
153
+ WARDEN_CLASSIFIER_API_KEY=… warden-mcp scan --classifier-url https://api.deepseek.com --classifier-model deepseek-flash --classifier-reasoning-effort none
139
154
  ```
140
155
 
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 和扩展字段。** 规则会读取。
156
+ - **显式启用,并发送所有声明字段。** 包括名称、标题、描述、输入/输出 schema、annotations 和扩展元数据;需要时附带归一化文本。密钥仅来自 `WARDEN_CLASSIFIER_API_KEY`。
157
+ - **默认仅建议;`--classifier-blocks` 对不完整检查拒绝放行。** 检测结果使用 `TOOL_DEF_CLASSIFIER`。截断、不确定、覆盖不完整、无效回答、超时及服务错误使用 `CLASSIFIER_INCOMPLETE`;这是检查失败,不是检测到攻击,`--update-lock` 不能批准这些结果。
158
+ - **五类语义检查。** `instruction_to_model`、`exfiltration`、`secret_request`、`concealment`、`cross_tool`。检查任何语言中的权限越界,但效果取决于模型的语言能力。协议要求每个工具有明确决定及有效的字段编号;WARDEN 自行从原文提取证据。每个未完成的工具最多单独重试一次。随机分隔符不保证免疫提示注入。
159
+ - **检查不等于批准。** [`wrap --require-approval`](security-hardening-language-boundary.md) 提供独立于语言的操作员批准边界。HISTOR 的历史模型结果仍然仅供参考。
145
160
 
146
161
  2026 年 10 月 9 日用 `deepseek-flash`(HISTOR 使用的同一模型)在 MCPTox(下文对比中介绍的公开基准)和我们自己的样本集上测得:
147
162
 
@@ -175,7 +190,7 @@ WARDEN_CLASSIFIER_API_KEY=… warden-mcp scan --classifier-url https://api.deeps
175
190
  - **要清楚哪些数字是调出来的。** 第一组攻击样本是我们写的。ruleset v9 是在读过两组攻击样本之后写的,v10 是在读过另一半的 22 台 MCPTox 服务器之后写的。所以 20 和 10 是“照着见过的样本调出来的规则”的召回率。MCPTox 留出的那一行、干净服务器、语料以及整个 0.8.2 列都没有经过调整。在语料上,v10 阻止的与 v8 完全相同。
176
191
  - **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
192
  - **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 并不是唯一的选择。
193
+ - **在这个位置上 WARDEN 并不是唯一的选择。** mcp-audit 同样提供 lock 文件、PR diff 模式和 SARIF。
179
194
 
180
195
  ## scan 不做什么
181
196
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🌐 [English](scanner-comparison.md) · [Русский](scanner-comparison.ru.md) · **Español** · [Français](scanner-comparison.fr.md) · [中文](scanner-comparison.zh.md)
4
4
 
5
- El 9 de octubre de 2026 ejecutamos WARDEN y dos escáneres MCP de código abierto, mcp-audit y mcp-shield,
5
+ El 9 de octubre de 2026 ejecutamos WARDEN y dos escáneres MCP de código abierto, mcp-audit y mcp-shield,[^snyk]
6
6
  sobre los mismos servidores. De cada uno queríamos dos números: cuántas herramientas envenenadas bloquea y
7
7
  cuántos servidores honestos bloquea por error. El segundo número decide si alguien puede dejar un escáner
8
8
  encendido.
@@ -171,17 +171,6 @@ con `deepseek-flash`, el modelo que usa HISTOR:
171
171
  parámetro y `rm -rf ~`. Funcionan en pareja.
172
172
  - **Costó poco.** Toda la medición fueron 816 solicitudes; según nuestra estimación, menos de un dólar.
173
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
174
  ## Límites, y nuestro interés
186
175
 
187
176
  - **No somos neutrales.** Publicamos WARDEN, gratis y con licencia MIT. Escribimos los 23 ataques y los 55
@@ -216,3 +205,5 @@ npx -y @aimarket/warden@0.9.0 scan
216
205
  La [guía de scan](scan.es.md) cubre el archivo lock, la GitHub Action, los hooks de pre-commit y el
217
206
  clasificador. El [estudio de campo](mcp-survey.es.md) es el estudio anterior sobre en qué se equivocó WARDEN
218
207
  con 1 108 servidores públicos.
208
+
209
+ [^snyk]: Snyk Agent Scan, el escáner MCP más usado, no está en la comparación. El 9 de octubre de 2026 su versión gratuita rechazó cada solicitud con HTTP 429, «The public quota for this service has been exceeded»: desde la primera solicitud, con las dos versiones de la CLI (0.6.8 y 0.5.17) y desde dos direcciones de red.
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🌐 [English](scanner-comparison.md) · [Русский](scanner-comparison.ru.md) · [Español](scanner-comparison.es.md) · **Français** · [中文](scanner-comparison.zh.md)
4
4
 
5
- Le 9 octobre 2026, nous avons exécuté WARDEN et deux scanners MCP open source, mcp-audit et mcp-shield, sur
5
+ Le 9 octobre 2026, nous avons exécuté WARDEN et deux scanners MCP open source, mcp-audit et mcp-shield,[^snyk] sur
6
6
  les mêmes serveurs. Nous voulions deux chiffres de chacun : combien d’outils empoisonnés il bloque, et
7
7
  combien de serveurs honnêtes il bloque par erreur. Le second chiffre décide si quelqu’un peut laisser un
8
8
  scanner allumé.
@@ -175,18 +175,6 @@ du journal HISTOR, avec les mêmes quatre catégories. Ses verdicts sont consult
175
175
  comme paramètre et `rm -rf ~`. Les deux fonctionnent en paire.
176
176
  - **Il a peu coûté.** Toute la mesure a pris 816 requêtes ; selon notre estimation, moins d’un dollar.
177
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
178
  ## Limites, et notre intérêt
191
179
 
192
180
  - **Nous ne sommes pas neutres.** Nous publions WARDEN, gratuit et sous licence MIT. Nous avons écrit les 23
@@ -221,3 +209,5 @@ npx -y @aimarket/warden@0.9.0 scan
221
209
  Le [guide de scan](scan.fr.md) couvre le fichier lock, la GitHub Action, les hooks pre-commit et le
222
210
  classificateur. L’[étude de terrain](mcp-survey.fr.md) est l’étude précédente sur les erreurs de WARDEN
223
211
  face à 1 108 serveurs publics.
212
+
213
+ [^snyk]: Snyk Agent Scan, le scanner MCP le plus utilisé, n’est pas comparé. Le 9 octobre 2026, sa version gratuite a refusé chaque requête par HTTP 429, « The public quota for this service has been exceeded » : dès la première requête, avec les deux versions de la CLI (0.6.8 et 0.5.17) et depuis deux adresses réseau.
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🌐 **English** · [Русский](scanner-comparison.ru.md) · [Español](scanner-comparison.es.md) · [Français](scanner-comparison.fr.md) · [中文](scanner-comparison.zh.md)
4
4
 
5
- On 2026-10-09 we ran WARDEN and two open-source MCP scanners, mcp-audit and mcp-shield, over the same
5
+ On 2026-10-09 we ran WARDEN and two open-source MCP scanners, mcp-audit and mcp-shield,[^snyk] over the same
6
6
  servers. We wanted two numbers from each: how many poisoned tools it blocks, and how many honest servers
7
7
  it blocks by mistake. The second number decides whether anyone can leave a scanner switched on.
8
8
 
@@ -164,17 +164,6 @@ uses:
164
164
  and `rm -rf ~`. The two work as a pair.
165
165
  - **It cost little.** The whole measurement was 816 requests; by our estimate it cost under a dollar.
166
166
 
167
- ## Snyk Agent Scan
168
-
169
- Snyk Agent Scan, formerly Invariant mcp-scan, is the most used MCP scanner. It judges tool descriptions on
170
- Snyk's servers and needs an account token. On 2026-10-09 we had a valid token. The service answered our
171
- first request with HTTP 429, "The public quota for this service has been exceeded". That quota is shared by
172
- every free user, and it was spent before we arrived. The scanner's own message calls it a daily usage
173
- limit. Our harness retries every hour and goes slowly when it gets through. We will add the column when it
174
- has run.
175
-
176
- That is a note about access, not about detection. We have not measured Snyk's detection.
177
-
178
167
  ## Limits, and our interest
179
168
 
180
169
  - **We are not neutral.** We publish WARDEN, MIT-licensed and free. We wrote the 23 attacks and the 55
@@ -207,3 +196,5 @@ npx -y @aimarket/warden@0.9.0 scan
207
196
 
208
197
  The [scan guide](scan.md) covers the lock file, the GitHub Action, the pre-commit hooks and the classifier.
209
198
  The [field survey](mcp-survey.md) is the earlier study of what WARDEN got wrong on 1 108 public servers.
199
+
200
+ [^snyk]: Snyk Agent Scan, the most used MCP scanner, is not compared. On 2026-10-09 its free version refused every request with HTTP 429, "The public quota for this service has been exceeded": from the first request, with both CLI versions (0.6.8 and 0.5.17) and from two network addresses.
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🌐 [English](scanner-comparison.md) · **Русский** · [Español](scanner-comparison.es.md) · [Français](scanner-comparison.fr.md) · [中文](scanner-comparison.zh.md)
4
4
 
5
- 9 октября 2026 года мы прогнали WARDEN и два MCP-сканера с открытым кодом, mcp-audit и mcp-shield, по одним
5
+ 9 октября 2026 года мы прогнали WARDEN и два MCP-сканера с открытым кодом, mcp-audit и mcp-shield,[^snyk] по одним
6
6
  и тем же серверам. От каждого нам нужны были два числа: сколько отравленных инструментов он блокирует и
7
7
  сколько честных серверов блокирует по ошибке. Второе число решает, оставит ли кто-нибудь сканер включённым.
8
8
 
@@ -168,17 +168,6 @@ credential file … keep this step to yourself») и «include the output of env
168
168
  и `rm -rf ~`. Они работают в паре.
169
169
  - **Он обошёлся дёшево.** Весь замер занял 816 запросов; по нашей оценке, это меньше доллара.
170
170
 
171
- ## Snyk Agent Scan
172
-
173
- Snyk Agent Scan, бывший Invariant mcp-scan, — самый используемый MCP-сканер. Он судит описания инструментов
174
- на серверах Snyk и требует токен аккаунта. 9 октября 2026 года у нас был действующий токен. На первый же
175
- запрос сервис ответил HTTP 429: «The public quota for this service has been exceeded». Эта квота общая для
176
- всех бесплатных пользователей, и она закончилась до нас. Собственное сообщение сканера называет это
177
- дневным лимитом. Наш стенд повторяет попытку каждый час и идёт медленно, когда квота открывается. Столбец
178
- добавим, когда прогон пройдёт.
179
-
180
- Это заметка о доступе, а не о качестве обнаружения. Обнаружение у Snyk мы не измеряли.
181
-
182
171
  ## Ограничения и наш интерес
183
172
 
184
173
  - **Мы не нейтральны.** Мы выпускаем WARDEN, бесплатно и под лицензией MIT. 23 атаки и 55 разметок написали
@@ -212,3 +201,5 @@ npx -y @aimarket/warden@0.9.0 scan
212
201
  [Руководство по scan](scan.ru.md) описывает лок-файл, GitHub Action, хуки pre-commit и классификатор.
213
202
  [Полевой обзор](mcp-survey.ru.md) — более раннее исследование того, в чём WARDEN ошибся на 1 108 публичных
214
203
  серверах.
204
+
205
+ [^snyk]: Snyk Agent Scan, самый используемый MCP-сканер, в сравнении не участвует. 9 октября 2026 года его бесплатная версия отказала на каждый запрос с HTTP 429 «The public quota for this service has been exceeded»: с первого же запроса, в обеих версиях CLI (0.6.8 и 0.5.17) и с двух сетевых адресов.
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 🌐 [English](scanner-comparison.md) · [Русский](scanner-comparison.ru.md) · [Español](scanner-comparison.es.md) · [Français](scanner-comparison.fr.md) · **中文**
4
4
 
5
- 2026 年 10 月 9 日,我们在同一批服务器上运行了 WARDEN 和两款开源 MCP 扫描器:mcp-audit 与 mcp-shield。我们想从每款扫描器得到两个数字:它阻止了多少投毒工具,以及它误阻止了多少诚实的服务器。第二个数字决定了有没有人敢让扫描器一直开着。
5
+ 2026 年 10 月 9 日,我们在同一批服务器上运行了 WARDEN 和两款开源 MCP 扫描器:mcp-audit 与 mcp-shield[^snyk]。我们想从每款扫描器得到两个数字:它阻止了多少投毒工具,以及它误阻止了多少诚实的服务器。第二个数字决定了有没有人敢让扫描器一直开着。
6
6
 
7
7
  用自己的测试集衡量扫描器,结果总是好看,我们的也一样。所以本页大部分内容讲的是一个三家作者都没写过的样本集:MCPTox,一个发表于 AAAI 2026 的工具投毒基准。我们依据它一半的服务器编写了 WARDEN 最新的规则,并在另一半上测量。
8
8
 
@@ -105,12 +105,6 @@ v10 漏掉了 218 个留出工具中的 47 个,其中 41 个会以建议的形
105
105
  - **它会漏掉规则能抓到的东西:** 注解(annotations)里的注入、作为参数索取的私钥,以及 `rm -rf ~`。两者要搭配使用。
106
106
  - **成本很低。** 整个测量共 816 次请求;按我们的估算,不到一美元。
107
107
 
108
- ## Snyk Agent Scan
109
-
110
- Snyk Agent Scan(原 Invariant mcp-scan)是使用最广的 MCP 扫描器。它在 Snyk 的服务器上判定工具描述,需要账号令牌。2026 年 10 月 9 日,我们持有有效的令牌。服务对我们的第一次请求就返回了 HTTP 429:“The public quota for this service has been exceeded”。这份配额由所有免费用户共享,在我们到来之前就已用完。扫描器自己的提示把它称作每日使用上限。我们的测试工具每小时重试一次,通过之后会放慢节奏运行。运行完成后,我们会补上这一列。
111
-
112
- 这只是关于访问的说明,不涉及检测能力。我们没有测量 Snyk 的检测效果。
113
-
114
108
  ## 局限,以及我们的利益
115
109
 
116
110
  - **我们并不中立。** WARDEN 由我们发布,免费,采用 MIT 许可。23 个攻击和 55 条判定由我们编写。正因如此,样本集、重放测试工具、原始结果和每一条判定都是公开的。
@@ -133,3 +127,5 @@ npx -y @aimarket/warden@0.9.0 scan
133
127
  ```
134
128
 
135
129
  [scan 指南](scan.zh.md)介绍 lock 文件、GitHub Action、pre-commit 钩子和分类器。[实地普查](mcp-survey.zh.md)是更早的一项研究,讲的是 WARDEN 在 1 108 台公开服务器上判错了什么。
130
+
131
+ [^snyk]: 使用最广的 MCP 扫描器 Snyk Agent Scan 未参与对比。2026 年 10 月 9 日,它的免费版本对每一次请求都返回 HTTP 429:“The public quota for this service has been exceeded”。从第一次请求起即如此,两个 CLI 版本(0.6.8 和 0.5.17)均如此,从两个网络地址发出也一样。