@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.
Files changed (99) hide show
  1. package/CHANGELOG.md +41 -5
  2. package/README-es.md +27 -9
  3. package/README-fr.md +28 -9
  4. package/README-ru.md +28 -9
  5. package/README-zh.md +23 -9
  6. package/README.md +27 -9
  7. package/dist/classifier.d.ts +50 -0
  8. package/dist/classifier.d.ts.map +1 -0
  9. package/dist/classifier.js +164 -0
  10. package/dist/classifier.js.map +1 -0
  11. package/dist/claude-hook.d.ts +53 -0
  12. package/dist/claude-hook.d.ts.map +1 -0
  13. package/dist/claude-hook.js +192 -0
  14. package/dist/claude-hook.js.map +1 -0
  15. package/dist/histor.d.ts +69 -0
  16. package/dist/histor.d.ts.map +1 -0
  17. package/dist/histor.js +82 -0
  18. package/dist/histor.js.map +1 -0
  19. package/dist/index.d.ts +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/index.js.map +1 -1
  23. package/dist/jcs.d.ts +8 -0
  24. package/dist/jcs.d.ts.map +1 -1
  25. package/dist/jcs.js +25 -5
  26. package/dist/jcs.js.map +1 -1
  27. package/dist/mcp-client.d.ts +58 -0
  28. package/dist/mcp-client.d.ts.map +1 -0
  29. package/dist/mcp-client.js +451 -0
  30. package/dist/mcp-client.js.map +1 -0
  31. package/dist/mcp-rpc.js +1 -1
  32. package/dist/mcp-server.js +8 -0
  33. package/dist/mcp-server.js.map +1 -1
  34. package/dist/mcp-tools.d.ts.map +1 -1
  35. package/dist/mcp-tools.js +7 -6
  36. package/dist/mcp-tools.js.map +1 -1
  37. package/dist/mtl.d.ts +28 -0
  38. package/dist/mtl.d.ts.map +1 -0
  39. package/dist/mtl.js +77 -0
  40. package/dist/mtl.js.map +1 -0
  41. package/dist/pin-store.js +3 -3
  42. package/dist/pin-store.js.map +1 -1
  43. package/dist/pinning.d.ts +18 -2
  44. package/dist/pinning.d.ts.map +1 -1
  45. package/dist/pinning.js +34 -7
  46. package/dist/pinning.js.map +1 -1
  47. package/dist/scan-cli.d.ts +3 -0
  48. package/dist/scan-cli.d.ts.map +1 -0
  49. package/dist/scan-cli.js +36 -0
  50. package/dist/scan-cli.js.map +1 -0
  51. package/dist/scan-config.d.ts +72 -0
  52. package/dist/scan-config.d.ts.map +1 -0
  53. package/dist/scan-config.js +239 -0
  54. package/dist/scan-config.js.map +1 -0
  55. package/dist/scan-lock.d.ts +46 -0
  56. package/dist/scan-lock.d.ts.map +1 -0
  57. package/dist/scan-lock.js +112 -0
  58. package/dist/scan-lock.js.map +1 -0
  59. package/dist/scan-report.d.ts +21 -0
  60. package/dist/scan-report.d.ts.map +1 -0
  61. package/dist/scan-report.js +273 -0
  62. package/dist/scan-report.js.map +1 -0
  63. package/dist/scan.d.ts +137 -0
  64. package/dist/scan.d.ts.map +1 -0
  65. package/dist/scan.js +534 -0
  66. package/dist/scan.js.map +1 -0
  67. package/dist/static-scan.d.ts +1 -1
  68. package/dist/static-scan.d.ts.map +1 -1
  69. package/dist/static-scan.js +262 -12
  70. package/dist/static-scan.js.map +1 -1
  71. package/dist/tool-list.d.ts +34 -0
  72. package/dist/tool-list.d.ts.map +1 -0
  73. package/dist/tool-list.js +57 -0
  74. package/dist/tool-list.js.map +1 -0
  75. package/dist/wrap-cli.d.ts.map +1 -1
  76. package/dist/wrap-cli.js +6 -4
  77. package/dist/wrap-cli.js.map +1 -1
  78. package/dist/wrap-state.d.ts +3 -0
  79. package/dist/wrap-state.d.ts.map +1 -1
  80. package/dist/wrap-state.js +7 -4
  81. package/dist/wrap-state.js.map +1 -1
  82. package/dist/wrap.js +50 -42
  83. package/dist/wrap.js.map +1 -1
  84. package/docs/gates.es.md +59 -10
  85. package/docs/gates.fr.md +59 -10
  86. package/docs/gates.md +55 -10
  87. package/docs/gates.ru.md +59 -11
  88. package/docs/gates.zh.md +13 -7
  89. package/docs/scan.es.md +184 -0
  90. package/docs/scan.fr.md +184 -0
  91. package/docs/scan.md +184 -0
  92. package/docs/scan.ru.md +184 -0
  93. package/docs/scan.zh.md +184 -0
  94. package/docs/scanner-comparison.es.md +218 -0
  95. package/docs/scanner-comparison.fr.md +223 -0
  96. package/docs/scanner-comparison.md +209 -0
  97. package/docs/scanner-comparison.ru.md +214 -0
  98. package/docs/scanner-comparison.zh.md +135 -0
  99. package/package.json +5 -3
@@ -0,0 +1,184 @@
1
+ # Analyser les serveurs que démarrent vos clients MCP
2
+
3
+ > 🌐 [English](scan.md) · [Русский](scan.ru.md) · [Español](scan.es.md) · **Français** · [中文](scan.zh.md)
4
+
5
+ `warden-mcp scan` lit la configuration MCP que vous avez déjà, se connecte à chaque serveur qu’elle démarre et vérifie les définitions d’outils avant qu’un modèle ne les voie. C’est la même chaîne de portes que [`wrap`](../README-fr.md) et la [bibliothèque](integration.fr.md), exécutée une fois sur vos configurations au lieu de se placer devant un seul serveur.
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
+ Pas de compte, pas de clé d’API, pas de modèle. Le seul trafic réseau va vers les serveurs de votre configuration et, si vous passez `--histor`, vers le journal HISTOR.
23
+
24
+ ## Où il cherche
25
+
26
+ Sans argument, chaque fichier du tableau qui existe. Passez des fichiers pour n’analyser que ceux-là, `--project` pour les fichiers du projet dans le répertoire courant, ou `--client NAME` pour un seul client.
27
+
28
+ | Client | Fichier du projet | Fichier de l’utilisateur |
29
+ |---|---|---|
30
+ | Claude Code | `.mcp.json` | `~/.claude.json` (serveurs de l’utilisateur et de ce projet) |
31
+ | Claude Desktop | — | `claude_desktop_config.json` dans le répertoire de configuration de l’application |
32
+ | Cursor | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
33
+ | VS Code | `.vscode/mcp.json` (JSONC) | `mcp.json` dans le répertoire des paramètres utilisateur |
34
+ | Windsurf | — | `~/.codeium/windsurf/mcp_config.json` |
35
+
36
+ Un serveur que la configuration démarre via `warden-mcp wrap` est analysé comme le serveur derrière l’enveloppe, sous l’identifiant de pin qu’utilise `wrap`, si bien que la dérive par rapport à l’instantané approuvé apparaît dans le rapport. Les entrées désactivées, celles qui demandent une valeur `${input:…}` que le client réclame à l’utilisateur, et celles sans `command` ni `url` sont listées comme ignorées, jamais supprimées en silence.
37
+
38
+ ## Démarrer les serveurs
39
+
40
+ Pour lire les outils d’un serveur stdio, `scan` le démarre avec la commande, les arguments et l’environnement de la configuration, exactement comme votre client, puis l’arrête après `tools/list`. Il n’appelle jamais d’outil. Le programme s’exécute tout de même. Là où vous ne le voulez pas, par exemple en CI sur une pull request, `--no-launch` ne vérifie que la ligne de lancement : les enregistrements de commandes du threat feed et l’identité de lancement du lock.
41
+
42
+ Les serveurs distants (`url`, streamable HTTP ou l’ancien HTTP+SSE) sont interrogés sur le réseau avec les en-têtes de la configuration. `--public-only` refuse ceux qui se résolvent en adresses loopback, privées, link-local ou de métadonnées cloud, en vérifiant l’adresse réellement contactée. Les redirections ne sont pas suivies.
43
+
44
+ ## Sortie et codes de sortie
45
+
46
+ | Option | Écrit |
47
+ |---|---|
48
+ | (par défaut) | un tableau sur stdout |
49
+ | `--json` | le rapport JSON sur stdout |
50
+ | `--json-file FILE` | le rapport JSON dans un fichier, en plus du tableau |
51
+ | `--sarif FILE` | du SARIF 2.1.0 pour GitHub code scanning ; uniquement les constats bloquants, situés à la ligne du serveur dans la configuration |
52
+ | `--markdown FILE` | un résumé avec des détails repliables, pour `$GITHUB_STEP_SUMMARY` ou un commentaire de PR |
53
+
54
+ Code `0` : rien de bloqué. `1` : un serveur a été bloqué ou, avec `--fail-on-error`, n’a pas pu être vérifié. `2` : erreur d’utilisation ou de configuration. `--fail-on SEVERITY` déplace le seuil de blocage ; `--policy FILE` accepte le même fichier de politique strict que `wrap`.
55
+
56
+ Les identifiants présents dans les lignes de lancement et les URL sont affichés comme `***`. Dans le rapport Markdown, les descriptions d’outils sont placées dans des blocs de code que ce texte ne peut pas refermer.
57
+
58
+ ## Le fichier lock : relire les définitions, pas seulement la commande
59
+
60
+ Une ligne de configuration dit quel programme démarre. Elle ne dit pas ce que ce programme dira à votre modèle. Le lock enregistre ce second point, pour qu’une pull request le montre.
61
+
62
+ ```bash
63
+ warden-mcp scan --project --lock warden.lock.json --update-lock # après avoir lu ce qui a changé
64
+ git add .mcp.json warden.lock.json
65
+ ```
66
+
67
+ `--update-lock` écrit l’identité de lancement de chaque serveur et ses définitions d’outils complètes, et refuse d’enregistrer un serveur que WARDEN bloque. Le relancer sur des serveurs inchangés ne modifie pas le fichier. Avec `--lock` seul :
68
+
69
+ - un serveur que le lock ne connaît pas est bloqué (`LOCK_MISSING`) ;
70
+ - un serveur démarré autrement est bloqué (`SERVER_IDENTITY_DRIFT`) ;
71
+ - un serveur qui annonce désormais d’autres outils est bloqué (`TOOL_DEF_DRIFT`), et le rapport Markdown montre le changement sous forme de diff des noms, descriptions et schémas.
72
+
73
+ Les entrées du lock qu’aucune configuration ne démarre sont listées, puis supprimées à la mise à jour suivante.
74
+
75
+ ## GitHub Action
76
+
77
+ ```yaml
78
+ name: MCP servers
79
+ on: [pull_request]
80
+ permissions:
81
+ contents: read
82
+ security-events: write # uniquement pour 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
+ Par défaut, elle lit les fichiers du projet, ne démarre pas les serveurs stdio (c’est la pull request qui choisit ce programme), refuse les adresses non publiques, utilise `warden.lock.json` s’il existe, écrit le résumé du job et échoue sur un serveur bloqué. Entrées : `config`, `working-directory`, `lock`, `launch-stdio`, `public-only`, `fail-on`, `histor`, `classifier-url`, `classifier-model`, `classifier-blocks`, `sarif`, `upload-sarif`, `version`. Sorties : `blocked`, `servers`, `sarif`. Dans les workflows de production, épinglez l’action par SHA de commit.
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 # configurations modifiées ; ne démarre pas les serveurs stdio
103
+ - id: warden-lock # le projet correspond à warden.lock.json ; démarre les serveurs stdio
104
+ ```
105
+
106
+ Les deux exécutent le paquet publié via `npx` : Node 20 ou plus récent doit être dans le `PATH`.
107
+
108
+ ## Plugin Claude Code
109
+
110
+ ```text
111
+ /plugin marketplace add alexar76/warden
112
+ /plugin install warden@warden
113
+ ```
114
+
115
+ Au début de la session, il analyse les serveurs que Claude Code démarre pour le projet, vous indique lesquels sont bloqués, et ne donne au modèle que le nom et le code du constat. Une description bloquée n’entre jamais dans le contexte du modèle. Ensuite, un hook `PreToolUse` refuse les appels aux outils d’un serveur bloqué ou à un outil bloqué ; il lit un petit fichier et ne démarre rien. Détails et limites : [claude-plugin/README.md](../claude-plugin/README.md).
116
+
117
+ ## HISTOR : ce serveur vous sert-il ce qu’il sert à tout le monde ?
118
+
119
+ [HISTOR](https://histor.modelmarket.dev) est un journal public qui enregistre chaque jour les définitions d’outils de tous les serveurs distants du registre MCP officiel. `--histor` lui pose une question par serveur distant : l’ensemble d’outils qu’on vient de vous servir est-il celui qu’observe HISTOR ?
120
+
121
+ Ce qui est envoyé : l’endpoint, réduit au schéma, à l’hôte et au chemin (sans query, sans nom d’utilisateur ni mot de passe), et le condensé [MTL/1](https://github.com/alexar76/awr) de l’ensemble d’outils. Aucune description d’outil, aucun en-tête, aucun serveur stdio. Les endpoints sur des hôtes ou adresses privés, et les chemins qui semblent contenir une clé, ne sont pas envoyés du tout ; le rapport dit pourquoi.
122
+
123
+ | Réponse | Signification |
124
+ |---|---|
125
+ | `same` | On vous a servi l’ensemble qu’HISTOR observe actuellement |
126
+ | `different` | HISTOR n’a jamais vu cet ensemble : il a changé après le dernier passage quotidien, ou le serveur vous sert autre chose qu’au robot public. Signalé comme constat à titre consultatif uniquement `HISTOR_UNSEEN_TOOLSET` |
127
+ | `previously-observed` | Un ensemble qu’HISTOR a vu auparavant, pas l’actuel. Constat consultatif `HISTOR_OLDER_TOOLSET` |
128
+ | `not-listed`, `not-observed` | HISTOR ne connaît pas cet endpoint ou ne l’a encore jamais lu avec succès |
129
+
130
+ Les réponses d’HISTOR ne bloquent jamais. Si HISTOR ne répond pas, c’est signalé et l’analyse continue.
131
+
132
+ ## Le classificateur optionnel
133
+
134
+ Les règles de WARDEN fonctionnent hors ligne et de façon déterministe, et elles manquent ce qu'aucune règle ne nomme : une paraphrase, une consigne dans une autre langue. `--classifier-url` et `--classifier-model` ajoutent l'avis d'un modèle que vous choisissez, via n'importe quel endpoint compatible OpenAI : Ollama, vLLM ou LM Studio en local, ou une API hébergée.
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
+ - **Désactivé par défaut, il envoie le texte des outils.** Avec les deux options, le nom, la description et les schémas de chaque outil partent vers cet endpoint. Un modèle local les garde sur votre machine. La clé, si nécessaire, vient uniquement de `WARDEN_CLASSIFIER_API_KEY`, jamais d'une option.
142
+ - **Consultatif sauf avec `--classifier-blocks`.** Ses verdicts sont signalés comme `TOOL_DEF_CLASSIFIER`. Avec `--classifier-blocks`, un verdict au seuil de blocage ou au-dessus (`high` par défaut) bloque l'outil comme une règle. Un classificateur qui ne répond pas est signalé et ne bloque jamais.
143
+ - **La même question que HISTOR.** Le prompt, les quatre catégories (`instruction_to_model`, `exfiltration`, `secret_request`, `concealment`) et le format de réponse sont ceux du classificateur du journal HISTOR. Le texte des outils est placé entre des marqueurs à suffixe aléatoire qu'il ne peut pas forger, et la réponse du modèle est vérifiée champ par champ. Avec `--histor`, le verdict que le journal a lui-même enregistré sur l'ensemble qu'on vous a servi s'affiche aussi (`HISTOR_CLASSIFIER`, consultatif).
144
+ - **Il ne lit ni les annotations ni les champs d'extension.** Les règles, si.
145
+
146
+ Mesuré le 9 octobre 2026 avec `deepseek-flash`, le modèle qu'utilise HISTOR, sur MCPTox (le benchmark publié décrit dans la comparaison ci-dessous) et sur nos propres jeux :
147
+
148
+ | | Règles (v10) | Règles + classificateur, blocage à `high` | Règles + toute marque du classificateur (consultative) |
149
+ |---|---|---|---|
150
+ | Moitié réservée de MCPTox, détectés sur 218 outils empoisonnés | 171 | 191 | 218 |
151
+ | 45 serveurs propres de MCPTox, bloqués ou marqués | 0 | 0 | 4 marqués |
152
+ | 200 serveurs du corpus tirés au hasard, bloqués ou marqués | 2 bloqués | 2 bloqués | 2 bloqués, 12 autres marqués |
153
+ | 23 attaques écrites par nous / 12 cas bénins difficiles | 20 / 0 | 22 / 0 | 23 / 0 |
154
+
155
+ Les deux que bloquent les règles sont le service d’identité et l’outil de déploiement de la comparaison ci-dessous. Les 13 serveurs marqués par le classificateur l'ont été en `medium` ou `low`, jamais en `high`. Six méritent un regard humain : l'un demande au modèle d'effectuer un transfert irréversible de nom ENS « comme première et unique action » sans demander à l'utilisateur, un autre de ne pas révéler d'où viennent ses données. Le modèle marque aussi le serveur officiel Fetch, dont la description dit au modèle qu'il a désormais accès à internet et ne doit pas refuser. C'est une consigne au modèle, mais pas une attaque. Le modèle a manqué ce que les règles détectent : une injection dans les annotations, une clé privée ou une phrase de récupération demandée comme paramètre, et `rm -rf ~`.
156
+
157
+ ## Comparaison
158
+
159
+ Le 9 octobre 2026, nous avons exécuté WARDEN, mcp-audit 0.18.2 (`--connect`) et mcp-shield 1.0.4 sur les mêmes serveurs. Chaque serveur était rejoué en stdio, si bien que chaque scanner s’y connectait comme sur le terrain. Le banc, les jeux et chaque jugement sont dans [`scripts/scanner-comparison`](../scripts/scanner-comparison/). L’étude complète, avec la méthode et toutes les réserves, est la [comparaison de scanners](scanner-comparison.fr.md).
160
+
161
+ Les trois dernières lignes viennent de MCPTox (Wang et al., AAAI 2026), un benchmark publié de 485 outils empoisonnés écrits pour 45 vrais serveurs MCP. Nous avons coupé ses serveurs en deux par un hachage fixe. Le ruleset v10 a été écrit à partir de 22 serveurs ; le tableau rend compte des 23 autres, avec 218 outils empoisonnés sur lesquels aucune règle n’a été ajustée.
162
+
163
+ | Serveurs | WARDEN 0.8.2 (v8) | WARDEN 0.9.0 (v10) | mcp-audit 0.18.2 | mcp-shield 1.0.4 |
164
+ |---|---|---|---|---|
165
+ | 23 attaques écrites par nous, bloquées | 14 | 20 | 10 | 6 |
166
+ | 10 attaques tirées des fixtures de mcp-audit et de mcp-shield, bloquées | 7 | 10 | 10 | 8 |
167
+ | 12 cas bénins difficiles, bloqués | 0 | 0 | 1 | 1 |
168
+ | 986 serveurs publics, bloqués | 3 | 3 | 33 | 343 |
169
+ | …blocages justifiés à la lecture du texte | 1, et 1 discutable | 1, et 1 discutable | 0 | 0 sur 20 tirés au hasard |
170
+ | 218 outils empoisonnés de MCPTox sur les serveurs réservés, bloqués | 26 | 171 | 25 | 41 |
171
+ | 225 outils de MCPTox précédés de `<IMPORTANT>` ou « Ignore the previous instructions », bloqués | 225 | 225 | 222 | non exécuté |
172
+ | 45 serveurs propres de MCPTox, bloqués | 0 | 0 | 2 | 3 |
173
+
174
+ - **Lisez d’abord la ligne du corpus.** Aucune règle n’a été ajustée sur elle. Les 33 blocages de mcp-audit sont 16 « homoglyphes » dans un texte écrit entièrement dans son propre alphabet (ponctuation chinoise, symboles grecs, cyrillique), 12 consignes d’honnêteté comme « do not tell the user the check digits are wrong », 3 utilitaires base64, un outil de clé publique SSH et l’outil de déploiement que WARDEN bloque lui aussi à tort. mcp-shield bloque 35 % des serveurs réels, presque toujours sur un mot-clé : « API key », « token », `.env`, « .. ». Des points de suspension (« Shopify... ») comptent comme une remontée de répertoire. Il bloque bien le service d’identité que bloque WARDEN, mais pour le mot « credentials ».
175
+ - **Sachez quels chiffres sont ajustés.** Nous avons écrit le premier jeu d’attaques. Le ruleset v9 a été écrit après la lecture des deux jeux d’attaques, et v10 après les 22 serveurs MCPTox de l’autre moitié. 20 et 10 sont donc le rappel de règles ajustées à ce qu’elles ont vu. La ligne MCPTox réservée, les serveurs propres, le corpus et toute la colonne 0.8.2 ne sont pas ajustés. Sur le corpus, v10 bloque exactement ce que bloquait v8.
176
+ - **MCPTox teste une forme, pas un mot-clé.** Presque chaque outil empoisonné dit au modèle d’appeler d’abord un autre outil, ou de changer les arguments d’un autre outil. v10 nomme cette forme (`TOOL_DEF_CROSS_TOOL`, voir [la chaîne de portes](gates.fr.md)) et bloque 171 des 218 outils réservés, contre 189 sur 267 dans la moitié qui a servi à l’écrire. Avec le préfixe `<IMPORTANT>` ou « Ignore the previous instructions » du benchmark, tous les scanners que nous avons lancés les attrapent ; nous n’avons pas lancé mcp-shield sur ce jeu. Sur les serveurs propres, mcp-audit bloque AmapMap et BaiduMap, et mcp-shield bloque FileSystem, AdFin et Commander pour « sensitive file access ».
177
+ - **Ce que WARDEN manque encore :** 47 des 218 outils MCPTox réservés, une consigne en chinois de lire `.env`, une paraphrase sans mot-clé (« gather every credential file … keep this step to yourself ») et « include the output of env ». Il signale 41 de ces 47, et nos trois, à titre consultatif, sans bloquer. Il faut une vérification par le sens, pas une règle de plus : avec le classificateur optionnel ci-dessus, 191 des 218 sont bloqués au niveau `high` et les 218 sont signalés.
178
+ - **Non comparé :** Snyk Agent Scan, anciennement Invariant mcp-scan et le scanner le plus utilisé. Il juge les descriptions sur les serveurs de Snyk et exige un jeton de compte. mcp-audit propose lui aussi un fichier lock, un mode diff pour les PR et SARIF ; WARDEN n’est pas seul sur ce créneau.
179
+
180
+ ## Ce que scan ne fait pas
181
+
182
+ - Il lit les définitions une fois. Un serveur qui change ensuite est repéré par l’analyse suivante, par le lock en CI, ou à chaque appel par `wrap`.
183
+ - Il n’analyse ni les arguments ni les résultats des appels, ni les prompts, ni les resources, et ce n’est pas un bac à sable : démarrer un serveur stdio l’exécute.
184
+ - Ses verdicts sont les règles statiques et les enregistrements de menaces de WARDEN. Une paraphrase qu’aucune règle ne couvre passe ; voir [la chaîne de portes](gates.fr.md) et l’[étude de terrain](mcp-survey.fr.md) pour ce que les règles détectent et la fréquence de leurs erreurs.
package/docs/scan.md ADDED
@@ -0,0 +1,184 @@
1
+ # Scan the servers your MCP clients start
2
+
3
+ > 🌐 **English** · [Русский](scan.ru.md) · [Español](scan.es.md) · [Français](scan.fr.md) · [中文](scan.zh.md)
4
+
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
+
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
+ 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`.
23
+
24
+ ## Where it looks
25
+
26
+ Without arguments, every file below that exists. Pass files to scan only those, `--project` for the project files in the working directory, or `--client NAME` for one client.
27
+
28
+ | Client | Project file | User file |
29
+ |---|---|---|
30
+ | Claude Code | `.mcp.json` | `~/.claude.json` (user servers, and this project's) |
31
+ | Claude Desktop | — | `claude_desktop_config.json` in the app's config directory |
32
+ | Cursor | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
33
+ | VS Code | `.vscode/mcp.json` (JSONC) | `mcp.json` in the user settings directory |
34
+ | Windsurf | — | `~/.codeium/windsurf/mcp_config.json` |
35
+
36
+ A server the config starts through `warden-mcp wrap` is scanned as the server behind the wrapper, under the pin id `wrap` uses, so drift against the approved snapshot is reported. Entries that are disabled, need an `${input:…}` value the client prompts for, or have neither `command` nor `url` are listed as skipped, never dropped silently.
37
+
38
+ ## Starting servers
39
+
40
+ To read a stdio server's tools, `scan` starts it with the command, arguments and environment from the config, exactly as your client would, and stops it after `tools/list`. It never calls a tool. That still runs the program. Where you do not want that, such as CI on a pull request, `--no-launch` vets the launch line only: the threat feed's command records, and the lock's launch identity.
41
+
42
+ Remote servers (`url`, streamable HTTP or the older HTTP+SSE) are asked over the network with the headers from the config. `--public-only` refuses any that resolve to loopback, private, link-local or cloud-metadata addresses, checked on the address actually dialled. Redirects are not followed.
43
+
44
+ ## Output and exit codes
45
+
46
+ | Option | Writes |
47
+ |---|---|
48
+ | (default) | a table on stdout |
49
+ | `--json` | the JSON report on stdout |
50
+ | `--json-file FILE` | the JSON report to a file, alongside the table |
51
+ | `--sarif FILE` | SARIF 2.1.0 for GitHub code scanning; blocking findings only, located at the server's line in the config |
52
+ | `--markdown FILE` | a summary with collapsible details, for `$GITHUB_STEP_SUMMARY` or a PR comment |
53
+
54
+ Exit `0`: nothing blocked. `1`: a server was blocked, or with `--fail-on-error` could not be checked. `2`: a usage or config error. `--fail-on SEVERITY` moves the block threshold; `--policy FILE` takes the same strict policy file as `wrap`.
55
+
56
+ Credentials in launch lines and URLs are shown as `***`. Tool descriptions in the Markdown report are inside code blocks that the text cannot close.
57
+
58
+ ## The lock file: review the definitions, not just the command
59
+
60
+ A config line says which program starts. It does not say what that program will tell your model. The lock records the latter, so that a pull request shows it.
61
+
62
+ ```bash
63
+ warden-mcp scan --project --lock warden.lock.json --update-lock # after reading what changed
64
+ git add .mcp.json warden.lock.json
65
+ ```
66
+
67
+ `--update-lock` writes each server's launch identity and full tool definitions, and refuses to record a server WARDEN blocks. Re-running it on unchanged servers changes nothing in the file. With `--lock` alone:
68
+
69
+ - a server the lock does not know is blocked (`LOCK_MISSING`);
70
+ - a server started differently is blocked (`SERVER_IDENTITY_DRIFT`);
71
+ - a server that now advertises different tools is blocked (`TOOL_DEF_DRIFT`), and the Markdown report shows the change as a diff of names, descriptions and schemas.
72
+
73
+ Lock entries no config starts are listed, and pruned on the next update.
74
+
75
+ ## GitHub Action
76
+
77
+ ```yaml
78
+ name: MCP servers
79
+ on: [pull_request]
80
+ permissions:
81
+ contents: read
82
+ security-events: write # only for 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
+ 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
+ ## 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 # changed configs; does not start stdio servers
103
+ - id: warden-lock # the project matches warden.lock.json; starts stdio servers
104
+ ```
105
+
106
+ Both run the published package through `npx`, so Node 20 or later must be on `PATH`.
107
+
108
+ ## Claude Code plugin
109
+
110
+ ```text
111
+ /plugin marketplace add alexar76/warden
112
+ /plugin install warden@warden
113
+ ```
114
+
115
+ 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
+
117
+ ## HISTOR: is this server serving you what it serves everyone?
118
+
119
+ [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
+
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.
122
+
123
+ | Answer | Meaning |
124
+ |---|---|
125
+ | `same` | You were served the set HISTOR currently observes |
126
+ | `different` | HISTOR has never seen this set: it changed after the last daily crawl, or the server serves you something it does not serve the public crawler. Reported as advisory `HISTOR_UNSEEN_TOOLSET` |
127
+ | `previously-observed` | A set HISTOR saw earlier, not the current one. Advisory `HISTOR_OLDER_TOOLSET` |
128
+ | `not-listed`, `not-observed` | HISTOR does not know this endpoint, or has not read it successfully |
129
+
130
+ HISTOR answers never block. An outage is reported and the scan continues.
131
+
132
+ ## The optional classifier
133
+
134
+ WARDEN's rules are offline and deterministic, and they miss what no rule names: a paraphrase, an instruction in another language. `--classifier-url` and `--classifier-model` add a second opinion from a model you choose, through any OpenAI-compatible endpoint: a local Ollama, vLLM or LM Studio, or a hosted 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
+ - **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.
145
+
146
+ 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
+
148
+ | | Rules (v10) | Rules + classifier, blocking at `high` | Rules + any classifier flag (advisory) |
149
+ |---|---|---|---|
150
+ | MCPTox held-out half, 218 poisoned tools caught | 171 | 191 | 218 |
151
+ | 45 clean MCPTox servers, blocked or flagged | 0 | 0 | 4 flagged |
152
+ | 200 corpus servers drawn at random, blocked or flagged | 2 blocked | 2 blocked | 2 blocked, 12 more flagged |
153
+ | 23 attacks we wrote / 12 hard benign cases | 20 / 0 | 22 / 0 | 23 / 0 |
154
+
155
+ The two the rules block are the identity service and the deploy tool from the comparison below. The 13 servers the classifier flagged were flagged at `medium` or `low`, never `high`. Six of them are worth a human look: one tells the model to make an irreversible ENS name transfer "as the first and only action" without asking the user, another tells it not to disclose where its data comes from. The model also flags the official Fetch server, whose description tells the model it now has internet access and should not refuse. That is an instruction to the model, though not an attack. The model missed what the rules catch: an injection in annotations, a private key or seed phrase asked for as a parameter, and `rm -rf ~`.
156
+
157
+ ## How it compares
158
+
159
+ On 2026-10-09 we ran WARDEN, mcp-audit 0.18.2 (`--connect`) and mcp-shield 1.0.4 over the same servers. Each server was replayed over stdio, so every scanner connected to it as it would in the field. The harness, the sets and every judgment are in [`scripts/scanner-comparison`](../scripts/scanner-comparison/). The full write-up, with the method and every caveat, is the [scanner comparison](scanner-comparison.md).
160
+
161
+ The last three rows come from MCPTox (Wang et al., AAAI 2026), a published benchmark of 485 poisoned tools written for 45 real MCP servers. We split its servers in two by a fixed hash. Ruleset v10 was written from 22 servers; the table reports the other 23, with 218 poisoned tools no rule was fitted to.
162
+
163
+ | Servers | WARDEN 0.8.2 (v8) | WARDEN 0.9.0 (v10) | mcp-audit 0.18.2 | mcp-shield 1.0.4 |
164
+ |---|---|---|---|---|
165
+ | 23 attacks we wrote, blocked | 14 | 20 | 10 | 6 |
166
+ | 10 attacks from mcp-audit's and mcp-shield's own fixtures, blocked | 7 | 10 | 10 | 8 |
167
+ | 12 hard benign cases, blocked | 0 | 0 | 1 | 1 |
168
+ | 986 public servers, blocked | 3 | 3 | 33 | 343 |
169
+ | …blocks that hold up when the text is read | 1, and 1 arguable | 1, and 1 arguable | 0 | 0 of 20 drawn at random |
170
+ | 218 poisoned MCPTox tools on the held-out servers, blocked | 26 | 171 | 25 | 41 |
171
+ | 225 MCPTox tools with an `<IMPORTANT>` or "Ignore the previous instructions" prefix, blocked | 225 | 225 | 222 | not run |
172
+ | 45 clean MCPTox servers, blocked | 0 | 0 | 2 | 3 |
173
+
174
+ - **Read the corpus row first.** No rule was fitted to it. mcp-audit's 33 blocks are 16 "homoglyphs" in text written wholly in its own script (Chinese punctuation, Greek symbols, Cyrillic), 12 honesty instructions such as "do not tell the user the check digits are wrong", 3 base64 utilities, an SSH public-key tool, and the deploy tool WARDEN also blocks by mistake. mcp-shield blocks 35% of real servers, almost all on a keyword: "API key", "token", `.env`, "..". An ellipsis ("Shopify...") counts as path traversal. It does block the identity service WARDEN blocks, on the word "credentials".
175
+ - **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
+ - **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
+ - **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.
179
+
180
+ ## What scan does not do
181
+
182
+ - It reads definitions once. A server that changes later is caught by the next scan, by the lock in CI, or at every call by `wrap`.
183
+ - It does not scan call arguments, results, prompts or resources, and it is not a sandbox: starting a stdio server runs it.
184
+ - Its verdicts are WARDEN's static rules and threat records. A paraphrase no rule covers passes; see [the gate chain](gates.md) and the [field survey](mcp-survey.md) for what the rules catch and how often they are wrong.
@@ -0,0 +1,184 @@
1
+ # Проверка серверов, которые запускают ваши MCP-клиенты
2
+
3
+ > 🌐 [English](scan.md) · **Русский** · [Español](scan.es.md) · [Français](scan.fr.md) · [中文](scan.zh.md)
4
+
5
+ `warden-mcp scan` читает уже существующую у вас конфигурацию MCP, подключается к каждому серверу из неё и проверяет определения инструментов до того, как их увидит модель. Это та же цепочка гейтов, что у [`wrap`](../README-ru.md) и у [библиотеки](integration.ru.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`, проверяется как сервер за обёрткой и под тем же id пина, что использует `wrap`, поэтому дрейф относительно одобренного снимка попадает в отчёт. Выключенные записи, записи с значением `${input:…}`, которое клиент спрашивает у пользователя, и записи без `command` и `url` показываются как пропущенные и никогда не исчезают молча.
37
+
38
+ ## Запуск серверов
39
+
40
+ Чтобы прочитать инструменты stdio-сервера, `scan` запускает его с командой, аргументами и окружением из конфига — ровно так, как это сделал бы клиент, — и останавливает после `tools/list`. Инструменты он не вызывает никогда. Но программа при этом всё равно выполняется. Там, где это нежелательно, например в CI на pull request, `--no-launch` проверяет только строку запуска: по записям threat feed о командах и по идентичности запуска из лок-файла.
41
+
42
+ Удалённые серверы (`url`, streamable HTTP или старый HTTP+SSE) опрашиваются по сети с заголовками из конфига. `--public-only` отказывает тем, что разрешаются в loopback, частные, link-local адреса или адрес облачных метаданных; проверяется адрес, к которому реально идёт подключение. Редиректы не выполняются.
43
+
44
+ ## Вывод и коды выхода
45
+
46
+ | Опция | Что пишет |
47
+ |---|---|
48
+ | (по умолчанию) | таблицу в stdout |
49
+ | `--json` | JSON-отчёт в stdout |
50
+ | `--json-file FILE` | JSON-отчёт в файл, вместе с таблицей |
51
+ | `--sarif FILE` | SARIF 2.1.0 для GitHub code scanning; только блокирующие находки, с указанием строки сервера в конфиге |
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
+ ## Лок-файл: ревьюим определения, а не только команду
59
+
60
+ Строка конфига говорит, какая программа запускается. Она не говорит, что эта программа скажет вашей модели. Лок-файл записывает второе, чтобы 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_MISSING`);
70
+ - сервер, запускаемый иначе, блокируется (`SERVER_IDENTITY_DRIFT`);
71
+ - сервер, который теперь объявляет другие инструменты, блокируется (`TOOL_DEF_DRIFT`), а Markdown-отчёт показывает изменение диффом имён, описаний и схем.
72
+
73
+ Записи лок-файла, которые не запускает ни один конфиг, перечисляются и удаляются при следующем обновлении.
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
+ По умолчанию 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
+ ## 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
+ - **По умолчанию выключен и отправляет текст инструментов.** С обоими флагами имя, описание и схемы каждого инструмента уходят на этот адрес. Локальная модель оставляет их на вашей машине. Ключ, если нужен, берётся только из `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
+ - **Аннотации и поля расширений он не читает.** Их читают правила.
145
+
146
+ Замер 9 октября 2026 года на `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`. Шесть из них стоит показать человеку: один велит модели провести необратимую передачу ENS-имени «первым и единственным действием», не спрашивая пользователя, другой велит не раскрывать, откуда его данные. Модель помечает и официальный сервер Fetch: его описание говорит модели, что у неё теперь есть интернет и отказываться не нужно. Это инструкция модели, но не атака. Модель пропустила то, что ловят правила: инъекцию в аннотациях, запрос приватного ключа или seed-фразы как параметра и `rm -rf ~`.
156
+
157
+ ## Сравнение
158
+
159
+ 9 октября 2026 года мы прогнали WARDEN, mcp-audit 0.18.2 (`--connect`) и mcp-shield 1.0.4 по одним и тем же серверам. Каждый сервер воспроизводился по stdio, так что каждый сканер подключался к нему так же, как в жизни. Стенд, наборы и разметка каждого случая лежат в [`scripts/scanner-comparison`](../scripts/scanner-comparison/). Полный разбор с методом и всеми оговорками — в [сравнении сканеров](scanner-comparison.ru.md).
160
+
161
+ Последние три строки взяты из MCPTox (Wang et al., AAAI 2026) — опубликованного бенчмарка из 485 отравленных инструментов, написанных для 45 настоящих MCP-серверов. Мы разделили его серверы пополам по фиксированному хешу. 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
+ | 10 атак из фикстур самих mcp-audit и mcp-shield, заблокировано | 7 | 10 | 10 | 8 |
167
+ | 12 трудных безобидных случаев, заблокировано | 0 | 0 | 1 | 1 |
168
+ | 986 публичных серверов, заблокировано | 3 | 3 | 33 | 343 |
169
+ | …из них по делу, если прочитать текст | 1 и 1 спорный | 1 и 1 спорный | 0 | 0 из 20 случайно выбранных |
170
+ | 218 отравленных инструментов MCPTox на отложенных серверах, заблокировано | 26 | 171 | 25 | 41 |
171
+ | 225 инструментов MCPTox с префиксом `<IMPORTANT>` или «Ignore the previous instructions», заблокировано | 225 | 225 | 222 | не запускали |
172
+ | 45 чистых серверов MCPTox, заблокировано | 0 | 0 | 2 | 3 |
173
+
174
+ - **Сначала читайте строку корпуса.** Под неё не подгонялось ни одно правило. 33 блокировки mcp-audit — это 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.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
+ - **Что 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 в этой нише не один.
179
+
180
+ ## Чего scan не делает
181
+
182
+ - Он читает определения один раз. Сервер, изменившийся позже, поймает следующий запуск, лок-файл в CI или `wrap` при каждом вызове.
183
+ - Он не проверяет аргументы и результаты вызовов, prompts и resources и не является песочницей: запуск stdio-сервера выполняет его.
184
+ - Его вердикты — это статические правила и записи угроз WARDEN. Перефразировка, которую не покрывает ни одно правило, пройдёт; что правила ловят и как часто ошибаются — в [цепочке гейтов](gates.ru.md) и в [полевом замере](mcp-survey.ru.md).