@aimarket/warden 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -1
- package/README-es.md +17 -7
- package/README-fr.md +17 -7
- package/README-ru.md +17 -7
- package/README-zh.md +17 -7
- package/README.md +17 -7
- package/dist/classifier.d.ts +50 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +164 -0
- package/dist/classifier.js.map +1 -0
- package/dist/claude-hook.d.ts +53 -0
- package/dist/claude-hook.d.ts.map +1 -0
- package/dist/claude-hook.js +192 -0
- package/dist/claude-hook.js.map +1 -0
- package/dist/histor.d.ts +69 -0
- package/dist/histor.d.ts.map +1 -0
- package/dist/histor.js +82 -0
- package/dist/histor.js.map +1 -0
- package/dist/mcp-client.d.ts +58 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +451 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/mcp-rpc.js +1 -1
- package/dist/mcp-server.js +8 -0
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools.js +2 -2
- package/dist/mcp-tools.js.map +1 -1
- package/dist/mtl.d.ts +28 -0
- package/dist/mtl.d.ts.map +1 -0
- package/dist/mtl.js +77 -0
- package/dist/mtl.js.map +1 -0
- package/dist/scan-cli.d.ts +3 -0
- package/dist/scan-cli.d.ts.map +1 -0
- package/dist/scan-cli.js +36 -0
- package/dist/scan-cli.js.map +1 -0
- package/dist/scan-config.d.ts +72 -0
- package/dist/scan-config.d.ts.map +1 -0
- package/dist/scan-config.js +239 -0
- package/dist/scan-config.js.map +1 -0
- package/dist/scan-lock.d.ts +46 -0
- package/dist/scan-lock.d.ts.map +1 -0
- package/dist/scan-lock.js +112 -0
- package/dist/scan-lock.js.map +1 -0
- package/dist/scan-report.d.ts +21 -0
- package/dist/scan-report.d.ts.map +1 -0
- package/dist/scan-report.js +273 -0
- package/dist/scan-report.js.map +1 -0
- package/dist/scan.d.ts +137 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +534 -0
- package/dist/scan.js.map +1 -0
- package/dist/static-scan.d.ts +1 -1
- package/dist/static-scan.d.ts.map +1 -1
- package/dist/static-scan.js +189 -4
- package/dist/static-scan.js.map +1 -1
- package/dist/tool-list.d.ts +34 -0
- package/dist/tool-list.d.ts.map +1 -0
- package/dist/tool-list.js +57 -0
- package/dist/tool-list.js.map +1 -0
- package/dist/wrap.d.ts.map +1 -1
- package/dist/wrap.js +2 -33
- package/dist/wrap.js.map +1 -1
- package/docs/gates.es.md +40 -7
- package/docs/gates.fr.md +39 -6
- package/docs/gates.md +38 -7
- package/docs/gates.ru.md +40 -7
- package/docs/gates.zh.md +10 -5
- package/docs/scan.es.md +184 -0
- package/docs/scan.fr.md +184 -0
- package/docs/scan.md +184 -0
- package/docs/scan.ru.md +184 -0
- package/docs/scan.zh.md +184 -0
- package/docs/scanner-comparison.es.md +218 -0
- package/docs/scanner-comparison.fr.md +223 -0
- package/docs/scanner-comparison.md +209 -0
- package/docs/scanner-comparison.ru.md +214 -0
- package/docs/scanner-comparison.zh.md +135 -0
- package/package.json +5 -3
package/docs/scan.es.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Analiza los servidores que inician tus clientes MCP
|
|
2
|
+
|
|
3
|
+
> 🌐 [English](scan.md) · [Русский](scan.ru.md) · **Español** · [Français](scan.fr.md) · [中文](scan.zh.md)
|
|
4
|
+
|
|
5
|
+
`warden-mcp scan` lee la configuración MCP que ya tienes, se conecta a cada servidor que inicia y revisa las definiciones de herramientas antes de que un modelo las vea. Es la misma cadena de compuertas que [`wrap`](../README-es.md) y la [biblioteca](integration.es.md), ejecutada una vez sobre tus configuraciones en lugar de delante de un solo servidor.
|
|
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
|
+
Sin cuenta, sin clave de API, sin modelo. El único tráfico de red va a los servidores de tu configuración y, si pasas `--histor`, al registro HISTOR.
|
|
23
|
+
|
|
24
|
+
## Dónde busca
|
|
25
|
+
|
|
26
|
+
Sin argumentos, cada archivo de la tabla que exista. Pasa archivos para analizar solo esos, `--project` para los archivos del proyecto en el directorio de trabajo, o `--client NAME` para un solo cliente.
|
|
27
|
+
|
|
28
|
+
| Cliente | Archivo del proyecto | Archivo del usuario |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| Claude Code | `.mcp.json` | `~/.claude.json` (servidores del usuario y de este proyecto) |
|
|
31
|
+
| Claude Desktop | — | `claude_desktop_config.json` en el directorio de configuración de la aplicación |
|
|
32
|
+
| Cursor | `.cursor/mcp.json` | `~/.cursor/mcp.json` |
|
|
33
|
+
| VS Code | `.vscode/mcp.json` (JSONC) | `mcp.json` en el directorio de ajustes del usuario |
|
|
34
|
+
| Windsurf | — | `~/.codeium/windsurf/mcp_config.json` |
|
|
35
|
+
|
|
36
|
+
Un servidor que la configuración inicia mediante `warden-mcp wrap` se analiza como el servidor que hay detrás del envoltorio, con el id de pin que usa `wrap`, de modo que la deriva respecto de la instantánea aprobada aparece en el informe. Las entradas desactivadas, las que necesitan un valor `${input:…}` que el cliente pide al usuario y las que no tienen ni `command` ni `url` se listan como omitidas; nunca desaparecen en silencio.
|
|
37
|
+
|
|
38
|
+
## Iniciar servidores
|
|
39
|
+
|
|
40
|
+
Para leer las herramientas de un servidor stdio, `scan` lo inicia con el comando, los argumentos y el entorno de la configuración, igual que lo haría tu cliente, y lo detiene después de `tools/list`. Nunca llama a una herramienta. Aun así, el programa se ejecuta. Donde no lo quieras, por ejemplo en CI sobre un pull request, `--no-launch` revisa solo la línea de arranque: los registros de comandos del threat feed y la identidad de arranque del lock.
|
|
41
|
+
|
|
42
|
+
Los servidores remotos (`url`, streamable HTTP o el antiguo HTTP+SSE) se consultan por la red con las cabeceras de la configuración. `--public-only` rechaza los que se resuelven a direcciones loopback, privadas, link-local o de metadatos de la nube, comprobando la dirección a la que realmente se conecta. No se siguen redirecciones.
|
|
43
|
+
|
|
44
|
+
## Salida y códigos de salida
|
|
45
|
+
|
|
46
|
+
| Opción | Escribe |
|
|
47
|
+
|---|---|
|
|
48
|
+
| (por defecto) | una tabla en stdout |
|
|
49
|
+
| `--json` | el informe JSON en stdout |
|
|
50
|
+
| `--json-file FILE` | el informe JSON en un archivo, junto con la tabla |
|
|
51
|
+
| `--sarif FILE` | SARIF 2.1.0 para GitHub code scanning; solo hallazgos que bloquean, ubicados en la línea del servidor en la configuración |
|
|
52
|
+
| `--markdown FILE` | un resumen con detalles plegables, para `$GITHUB_STEP_SUMMARY` o un comentario en el PR |
|
|
53
|
+
|
|
54
|
+
Código `0`: nada bloqueado. `1`: se bloqueó un servidor o, con `--fail-on-error`, no se pudo comprobar. `2`: error de uso o de configuración. `--fail-on SEVERITY` mueve el umbral de bloqueo; `--policy FILE` acepta el mismo archivo de política estricto que `wrap`.
|
|
55
|
+
|
|
56
|
+
Las credenciales en líneas de arranque y URL se muestran como `***`. Las descripciones de herramientas del informe Markdown van dentro de bloques de código que ese texto no puede cerrar.
|
|
57
|
+
|
|
58
|
+
## El archivo lock: revisa las definiciones, no solo el comando
|
|
59
|
+
|
|
60
|
+
Una línea de configuración dice qué programa arranca. No dice qué le contará ese programa a tu modelo. El lock registra lo segundo, para que un pull request lo muestre.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
warden-mcp scan --project --lock warden.lock.json --update-lock # después de leer qué cambió
|
|
64
|
+
git add .mcp.json warden.lock.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`--update-lock` escribe la identidad de arranque de cada servidor y sus definiciones completas, y se niega a registrar un servidor que WARDEN bloquea. Volver a ejecutarlo sobre servidores sin cambios no modifica el archivo. Solo con `--lock`:
|
|
68
|
+
|
|
69
|
+
- un servidor que el lock no conoce se bloquea (`LOCK_MISSING`);
|
|
70
|
+
- un servidor que arranca de otra forma se bloquea (`SERVER_IDENTITY_DRIFT`);
|
|
71
|
+
- un servidor que ahora anuncia otras herramientas se bloquea (`TOOL_DEF_DRIFT`), y el informe Markdown muestra el cambio como un diff de nombres, descripciones y esquemas.
|
|
72
|
+
|
|
73
|
+
Las entradas del lock que ninguna configuración inicia se listan y se eliminan en la siguiente actualización.
|
|
74
|
+
|
|
75
|
+
## GitHub Action
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
name: MCP servers
|
|
79
|
+
on: [pull_request]
|
|
80
|
+
permissions:
|
|
81
|
+
contents: read
|
|
82
|
+
security-events: write # solo para 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
|
+
Por defecto lee los archivos del proyecto, no inicia servidores stdio (ese programa lo elige el pull request), rechaza direcciones no públicas, usa `warden.lock.json` si existe, escribe el resumen del job y falla ante un servidor bloqueado. Entradas: `config`, `working-directory`, `lock`, `launch-stdio`, `public-only`, `fail-on`, `histor`, `classifier-url`, `classifier-model`, `classifier-blocks`, `sarif`, `upload-sarif`, `version`. Salidas: `blocked`, `servers`, `sarif`. En workflows de producción, fija la acción por 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 # configuraciones modificadas; no inicia servidores stdio
|
|
103
|
+
- id: warden-lock # el proyecto coincide con warden.lock.json; inicia servidores stdio
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Ambos ejecutan el paquete publicado mediante `npx`, así que necesitas Node 20 o posterior en el `PATH`.
|
|
107
|
+
|
|
108
|
+
## Plugin de Claude Code
|
|
109
|
+
|
|
110
|
+
```text
|
|
111
|
+
/plugin marketplace add alexar76/warden
|
|
112
|
+
/plugin install warden@warden
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Al iniciar la sesión analiza los servidores que Claude Code inicia para el proyecto, te dice cuáles están bloqueados y al modelo solo le da el nombre y el código del hallazgo. Una descripción bloqueada nunca entra en el contexto del modelo. Después, un hook `PreToolUse` deniega las llamadas a herramientas de un servidor bloqueado o a una herramienta bloqueada; lee un archivo pequeño y no inicia nada. Detalles y límites: [claude-plugin/README.md](../claude-plugin/README.md).
|
|
116
|
+
|
|
117
|
+
## HISTOR: ¿este servidor te sirve lo mismo que a todos?
|
|
118
|
+
|
|
119
|
+
[HISTOR](https://histor.modelmarket.dev) es un registro público que anota cada día las definiciones de herramientas de todos los servidores remotos del registro oficial de MCP. `--histor` le hace una pregunta por cada servidor remoto: ¿el conjunto de herramientas que te acaban de servir es el que observa HISTOR?
|
|
120
|
+
|
|
121
|
+
Qué se envía: el endpoint, solo esquema, host y ruta (sin query, sin usuario ni contraseña), y el digest [MTL/1](https://github.com/alexar76/awr) del conjunto de herramientas. Ninguna descripción de herramienta, ninguna cabecera, ningún servidor stdio. Los endpoints en hosts o direcciones privadas, y las rutas que parecen llevar una clave, no se envían; el informe dice por qué.
|
|
122
|
+
|
|
123
|
+
| Respuesta | Significado |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `same` | Te sirvieron el conjunto que HISTOR observa ahora |
|
|
126
|
+
| `different` | HISTOR nunca vio este conjunto: cambió después del último rastreo diario, o el servidor te sirve algo que no sirve al rastreador público. Se informa como hallazgo consultivo `HISTOR_UNSEEN_TOOLSET` |
|
|
127
|
+
| `previously-observed` | Un conjunto que HISTOR vio antes, no el actual. Hallazgo consultivo `HISTOR_OLDER_TOOLSET` |
|
|
128
|
+
| `not-listed`, `not-observed` | HISTOR no conoce este endpoint o todavía no lo ha leído con éxito |
|
|
129
|
+
|
|
130
|
+
Las respuestas de HISTOR nunca bloquean. Si HISTOR no responde, se informa y el análisis continúa.
|
|
131
|
+
|
|
132
|
+
## El clasificador opcional
|
|
133
|
+
|
|
134
|
+
Las reglas de WARDEN funcionan sin conexión y de forma determinista, y no ven lo que ninguna regla nombra: una paráfrasis, una instrucción en otro idioma. `--classifier-url` y `--classifier-model` añaden una segunda opinión de un modelo que tú eliges, mediante cualquier endpoint compatible con OpenAI: Ollama, vLLM o LM Studio en local, o una API alojada.
|
|
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
|
+
- **Está apagado por defecto y envía el texto de las herramientas.** Con ambos flags, el nombre, la descripción y los esquemas de cada herramienta van a ese endpoint. Un modelo local los mantiene en tu máquina. La clave, si hace falta, viene solo de `WARDEN_CLASSIFIER_API_KEY`, nunca de un flag.
|
|
142
|
+
- **Consultivo salvo con `--classifier-blocks`.** Sus veredictos se informan como `TOOL_DEF_CLASSIFIER`. Con `--classifier-blocks`, un veredicto en el umbral de bloqueo o por encima (`high` por defecto) bloquea la herramienta como una regla. Un clasificador que no responde se informa y nunca bloquea.
|
|
143
|
+
- **La misma pregunta que HISTOR.** El prompt, las cuatro categorías (`instruction_to_model`, `exfiltration`, `secret_request`, `concealment`) y el formato de respuesta son los del clasificador del registro HISTOR. El texto de las herramientas va entre marcadores con un sufijo aleatorio que no puede falsificar, y la respuesta del modelo se comprueba campo por campo. Con `--histor` también se muestra el veredicto que el propio registro guarda sobre el conjunto que te sirvieron (`HISTOR_CLASSIFIER`, consultivo).
|
|
144
|
+
- **No lee anotaciones ni campos de extensión.** Las reglas sí.
|
|
145
|
+
|
|
146
|
+
Medido el 9 de octubre de 2026 con `deepseek-flash`, el modelo que usa HISTOR, sobre MCPTox (el benchmark publicado que se describe en la comparación de abajo) y sobre nuestros conjuntos:
|
|
147
|
+
|
|
148
|
+
| | Reglas (v10) | Reglas + clasificador, bloqueando en `high` | Reglas + cualquier marca del clasificador (consultiva) |
|
|
149
|
+
|---|---|---|---|
|
|
150
|
+
| Mitad reservada de MCPTox, detectadas de 218 herramientas envenenadas | 171 | 191 | 218 |
|
|
151
|
+
| 45 servidores limpios de MCPTox, bloqueados o marcados | 0 | 0 | 4 marcados |
|
|
152
|
+
| 200 servidores del corpus elegidos al azar, bloqueados o marcados | 2 bloqueados | 2 bloqueados | 2 bloqueados, 12 marcados más |
|
|
153
|
+
| 23 ataques nuestros / 12 casos benignos difíciles | 20 / 0 | 22 / 0 | 23 / 0 |
|
|
154
|
+
|
|
155
|
+
Los dos que bloquean las reglas son el servicio de identidad y la herramienta de despliegue de la comparación de abajo. Los 13 servidores que marcó el clasificador lo fueron con `medium` o `low`, nunca `high`. Seis merecen que los mire una persona: uno le dice al modelo que haga una transferencia irreversible de un nombre ENS «como primera y única acción» sin preguntar al usuario, otro que no revele de dónde salen sus datos. El modelo también marca el servidor oficial Fetch, cuya descripción le dice al modelo que ahora tiene acceso a internet y no debe negarse. Es una instrucción al modelo, aunque no un ataque. El modelo no detectó lo que sí detectan las reglas: una inyección en anotaciones, una clave privada o frase semilla pedida como parámetro y `rm -rf ~`.
|
|
156
|
+
|
|
157
|
+
## Cómo se compara
|
|
158
|
+
|
|
159
|
+
El 9 de octubre de 2026 ejecutamos WARDEN, mcp-audit 0.18.2 (`--connect`) y mcp-shield 1.0.4 sobre los mismos servidores. Cada servidor se reprodujo por stdio, así que cada escáner se conectó a él como lo haría en la práctica. El banco de pruebas, los conjuntos y cada juicio están en [`scripts/scanner-comparison`](../scripts/scanner-comparison/). El estudio completo, con el método y todas las salvedades, es la [comparación de escáneres](scanner-comparison.es.md).
|
|
160
|
+
|
|
161
|
+
Las tres últimas filas vienen de MCPTox (Wang et al., AAAI 2026), un benchmark publicado de 485 herramientas envenenadas escritas para 45 servidores MCP reales. Dividimos sus servidores en dos con un hash fijo. El ruleset v10 se escribió a partir de 22 servidores; la tabla informa sobre los otros 23, con 218 herramientas envenenadas a las que no se ajustó ninguna regla.
|
|
162
|
+
|
|
163
|
+
| Servidores | WARDEN 0.8.2 (v8) | WARDEN 0.9.0 (v10) | mcp-audit 0.18.2 | mcp-shield 1.0.4 |
|
|
164
|
+
|---|---|---|---|---|
|
|
165
|
+
| 23 ataques escritos por nosotros, bloqueados | 14 | 20 | 10 | 6 |
|
|
166
|
+
| 10 ataques de los propios fixtures de mcp-audit y mcp-shield, bloqueados | 7 | 10 | 10 | 8 |
|
|
167
|
+
| 12 casos benignos difíciles, bloqueados | 0 | 0 | 1 | 1 |
|
|
168
|
+
| 986 servidores públicos, bloqueados | 3 | 3 | 33 | 343 |
|
|
169
|
+
| …bloqueos que se sostienen al leer el texto | 1, y 1 discutible | 1, y 1 discutible | 0 | 0 de 20 elegidos al azar |
|
|
170
|
+
| 218 herramientas envenenadas de MCPTox en los servidores reservados, bloqueadas | 26 | 171 | 25 | 41 |
|
|
171
|
+
| 225 herramientas de MCPTox con el prefijo `<IMPORTANT>` o «Ignore the previous instructions», bloqueadas | 225 | 225 | 222 | no ejecutado |
|
|
172
|
+
| 45 servidores limpios de MCPTox, bloqueados | 0 | 0 | 2 | 3 |
|
|
173
|
+
|
|
174
|
+
- **Lee primero la fila del corpus.** Ninguna regla se ajustó a ella. Los 33 bloqueos de mcp-audit son 16 «homoglifos» en texto escrito por completo en su propio alfabeto (puntuación china, símbolos griegos, cirílico), 12 instrucciones de honestidad como «do not tell the user the check digits are wrong», 3 utilidades base64, una herramienta de clave pública SSH y la herramienta de despliegue que WARDEN también bloquea por error. mcp-shield bloquea el 35 % de los servidores reales, casi siempre por una palabra clave: «API key», «token», `.env`, «..». Unos puntos suspensivos («Shopify...») cuentan como salto de directorio. También bloquea el servicio de identidad que bloquea WARDEN, pero por la palabra «credentials».
|
|
175
|
+
- **Ten presente qué números están ajustados.** El primer conjunto de ataques lo escribimos nosotros. El ruleset v9 se escribió después de leer ambos conjuntos de ataques, y v10 después de los 22 servidores de MCPTox de la otra mitad. Así que 20 y 10 son la exhaustividad de reglas ajustadas a lo que vieron. La fila reservada de MCPTox, los servidores limpios, el corpus y toda la columna 0.8.2 no están ajustados. Sobre el corpus, v10 bloquea exactamente lo mismo que v8.
|
|
176
|
+
- **MCPTox prueba una forma, no una palabra clave.** Casi todas las herramientas envenenadas le dicen al modelo que llame antes a otra herramienta o que cambie los argumentos de otra. v10 nombra esa forma (`TOOL_DEF_CROSS_TOOL`, ver [la cadena de compuertas](gates.es.md)) y bloquea 171 de las 218 herramientas reservadas, frente a 189 de 267 en la mitad con la que se escribió. Con el prefijo `<IMPORTANT>` o «Ignore the previous instructions» del benchmark, todos los escáneres que ejecutamos las detectan; mcp-shield no lo ejecutamos sobre ese conjunto. En los servidores limpios, mcp-audit bloquea AmapMap y BaiduMap, y mcp-shield bloquea FileSystem, AdFin y Commander por «sensitive file access».
|
|
177
|
+
- **Lo que WARDEN todavía no detecta:** 47 de las 218 herramientas reservadas de MCPTox, una instrucción en chino para leer `.env`, una paráfrasis sin palabras clave («gather every credential file … keep this step to yourself») y «include the output of env». Informa 41 de esas 47, y las tres nuestras, como avisos, no como bloqueos. Necesitan una comprobación por significado, no otra regla: con el clasificador opcional de arriba, 191 de las 218 quedan bloqueadas en `high` y las 218 quedan marcadas.
|
|
178
|
+
- **No comparado:** Snyk Agent Scan, antes Invariant mcp-scan y el escáner más usado. Juzga las descripciones en los servidores de Snyk y necesita un token de cuenta. mcp-audit también tiene archivo lock, modo diff para PR y SARIF; WARDEN no está solo en ese hueco.
|
|
179
|
+
|
|
180
|
+
## Lo que scan no hace
|
|
181
|
+
|
|
182
|
+
- Lee las definiciones una vez. Un servidor que cambie después lo detecta el siguiente análisis, el lock en CI o `wrap` en cada llamada.
|
|
183
|
+
- No analiza argumentos ni resultados de llamadas, prompts ni resources, y no es un sandbox: iniciar un servidor stdio lo ejecuta.
|
|
184
|
+
- Sus veredictos son las reglas estáticas y los registros de amenazas de WARDEN. Una paráfrasis que ninguna regla cubre pasa; consulta [la cadena de compuertas](gates.es.md) y el [estudio de campo](mcp-survey.es.md) para ver qué detectan las reglas y con qué frecuencia se equivocan.
|
package/docs/scan.fr.md
ADDED
|
@@ -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.
|