@aimarket/warden 0.5.0 → 0.7.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 (57) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README-es.md +14 -6
  3. package/README-fr.md +14 -6
  4. package/README-ru.md +14 -6
  5. package/README-zh.md +14 -6
  6. package/README.md +14 -6
  7. package/dist/fold.d.ts +38 -0
  8. package/dist/fold.d.ts.map +1 -0
  9. package/dist/fold.js +99 -0
  10. package/dist/fold.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +8 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/mcp-rpc.d.ts +3 -1
  16. package/dist/mcp-rpc.d.ts.map +1 -1
  17. package/dist/mcp-rpc.js +32 -8
  18. package/dist/mcp-rpc.js.map +1 -1
  19. package/dist/mcp-server.d.ts +7 -2
  20. package/dist/mcp-server.d.ts.map +1 -1
  21. package/dist/mcp-server.js +43 -49
  22. package/dist/mcp-server.js.map +1 -1
  23. package/dist/mcp-tools.d.ts +0 -12
  24. package/dist/mcp-tools.d.ts.map +1 -1
  25. package/dist/mcp-tools.js +104 -23
  26. package/dist/mcp-tools.js.map +1 -1
  27. package/dist/pin-store.d.ts +13 -0
  28. package/dist/pin-store.d.ts.map +1 -0
  29. package/dist/pin-store.js +44 -0
  30. package/dist/pin-store.js.map +1 -0
  31. package/dist/pinning.d.ts +4 -3
  32. package/dist/pinning.d.ts.map +1 -1
  33. package/dist/pinning.js +16 -3
  34. package/dist/pinning.js.map +1 -1
  35. package/dist/sanitize.d.ts.map +1 -1
  36. package/dist/sanitize.js +6 -3
  37. package/dist/sanitize.js.map +1 -1
  38. package/dist/state.d.ts +7 -0
  39. package/dist/state.d.ts.map +1 -0
  40. package/dist/state.js +61 -0
  41. package/dist/state.js.map +1 -0
  42. package/dist/static-scan.d.ts +15 -2
  43. package/dist/static-scan.d.ts.map +1 -1
  44. package/dist/static-scan.js +300 -25
  45. package/dist/static-scan.js.map +1 -1
  46. package/dist/threat-feed.d.ts +9 -0
  47. package/dist/threat-feed.d.ts.map +1 -1
  48. package/dist/threat-feed.js +50 -7
  49. package/dist/threat-feed.js.map +1 -1
  50. package/dist/types.d.ts +9 -0
  51. package/dist/types.d.ts.map +1 -1
  52. package/docs/security-hardening.es.md +40 -0
  53. package/docs/security-hardening.fr.md +40 -0
  54. package/docs/security-hardening.md +40 -0
  55. package/docs/security-hardening.ru.md +40 -0
  56. package/docs/security-hardening.zh.md +40 -0
  57. package/package.json +3 -2
package/CHANGELOG.md CHANGED
@@ -1,7 +1,52 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## 0.7.0 — 2026-09-30
5
+
6
+ - Add `vetLaunch` for identity/origin/threat checks before starting a server.
7
+ - Persist signed-feed snapshots per publisher key; reject rollback/equivocation across restarts; retain last-good rules with freshness status. The timeout now covers the response body.
8
+ - Validate JSON-RPC envelopes, preserve split UTF-8, and bound NDJSON/LSP framing.
9
+ - Add atomic `FilePinStore` and MCP status/approve/revoke operations. Mutations require operator opt-in and a reviewed revision; stale concurrent approvals are rejected.
10
+ - Hash all advertised tool fields (format v2); require review when legacy pins lack extended-field coverage. Ruleset v6 scans title/output schema/annotations/extensions and does not confuse JSON delimiters with benign quotation.
11
+ - ARGUS integration checks before launch and every call, handles list-change notifications, and quarantines drift or listing failure. See the five-language [migration guide](docs/security-hardening.md).
12
+
3
13
  All notable changes to `@aimarket/warden`.
4
14
 
15
+ ## 0.6.0 — 2026-09-23
16
+
17
+ Ruleset **v5**: language-independent coverage. A rule table written in one language cannot read
18
+ meaning in every other, so v5 strengthens what does not depend on the language at all, and says
19
+ plainly where the table stops.
20
+
21
+ - **Text is folded before any rule reads it** (`src/fold.ts`, published as `fold` beside the rules
22
+ and part of the digest): NFKC for fullwidth letters, ligatures and other compatibility forms;
23
+ invisible characters inside a word dropped; the Unicode TAG block (invisible ASCII that can spell a
24
+ whole sentence) decoded; look-alike Cyrillic/Greek letters mapped to Latin inside a word that
25
+ already mixes scripts, while text written wholly in one script is left alone. An English rule can
26
+ no longer be dodged by `ignore`, a zero-width space inside the word, tag characters or a Cyrillic
27
+ `о`. The two hidden-payload rules read the raw text (`raw: true` in the published table).
28
+ - **`TOOL_DEF_HIDDEN_UNICODE`** now also catches the Unicode-tag block and the bidi isolates
29
+ (U+2066–2069). `displaySafe` escapes tag characters, so a finding never prints them invisibly.
30
+ - **`TOOL_DEF_SECRET_EXFIL`** (new, advisory): a secret store (`.env`, `~/.ssh/…`,
31
+ `~/.aws/credentials`, `.npmrc`, …) and a URL, e-mail address or host within 100 characters of each
32
+ other — the shape of "read this, send it there" whatever language the connective words are in.
33
+ Advisory because on 10 645 live servers its only hit was honest (a deploy tool's ssh command to its
34
+ own host); refusing a server on a rule whose only real-world hit is honest is the v1 mistake again.
35
+ - **Three measured false positives removed**: "send the user to https://…" is a redirect of a person
36
+ (guard `navigation`); "keep calling this until done without asking the user" is autonomy, not
37
+ concealment (guard `autonomy`); a zero-width joiner inside an emoji sequence is not concealment.
38
+
39
+ Measured on the 10 645 distinct tool sets HISTOR holds (172 771 tools): v4 blocks 63, v5 blocks 56,
40
+ and v5 blocks none that v4 did not. The 1 108-server survey figures (v3 → v4) are unchanged and
41
+ still describe v4's calibration; this corpus is HISTOR's, not the survey's.
42
+
43
+ ## 0.5.1 — 2026-09-05
44
+
45
+ stdio wire fix for Glama: replies are newline-delimited JSON (MCP stdio / mcp-proxy).
46
+ Content-Length framing on stdout made Glama's health check time out (`ignoring non-JSON
47
+ output [ 'Content-Length: …' ]`). Input still accepts Content-Length and mirrors it on
48
+ the reply for legacy probes.
49
+
5
50
  ## 0.5.0 — 2026-09-03
6
51
 
7
52
  stdio MCP server for Glama / Claude Desktop / Cursor (`warden-mcp` / `node dist/mcp-server.js`).
package/README-es.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
9
9
  <a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm version" /></a>
10
10
  <img src="docs/badges/deps.svg" alt="Cero dependencias de ejecución" />
11
- <img src="docs/badges/tests.svg" alt="166 pruebas en verde" />
11
+ <img src="docs/badges/tests.svg" alt="200 pruebas en verde" />
12
12
  <img src="docs/badges/node.svg" alt="Node >= 20" />
13
13
  <a href="LICENSE"><img src="docs/badges/license.svg" alt="Licencia: MIT" /></a>
14
14
  </p>
@@ -23,6 +23,8 @@
23
23
 
24
24
  > 🌐 [English](README.md) · [Русский](README-ru.md) · **Español** · [Français](README-fr.md) · [中文](README-zh.md) · [Glosario](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
25
25
 
26
+ > [Seguridad y migración de 0.7.0](docs/security-hardening.es.md): `vetLaunch`, durable pins, anti-rollback, ruleset v6, runtime revalidation.
27
+
26
28
  **Un servidor MCP. Cortafuegos de las definiciones de herramienta anunciadas. Biblioteca incluida.**
27
29
 
28
30
  Transporte: **stdio** (`npx -y @aimarket/warden` / `node dist/mcp-server.js`). Hosts compatibles:
@@ -82,10 +84,16 @@ El proceso **no** arranca, no hace de proxy ni aísla otro servidor MCP: pasas u
82
84
  | `check_egress_url` | Allowlist de hosts (lista vacía = denegar todos) |
83
85
  | `canonicalize_json` | Bytes RFC 8785 |
84
86
  | `list_scan_rules` | Tabla de reglas publicada |
87
+ | `status_mcp_server` | Revisar aprobación guardada y hashes actuales |
88
+ | `approve_mcp_server` | Aprobar el snapshot revisado (habilitación del operador) |
89
+ | `revoke_mcp_server` | Revocar la aprobación revisada (habilitación del operador) |
85
90
 
86
91
  ### Publicar en Glama
87
92
 
88
- Listado: **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)**
93
+ Listado: **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)** ·
94
+ quality score: **[glama.ai/mcp/servers/alexar76/warden/score](https://glama.ai/mcp/servers/alexar76/warden/score)**
95
+
96
+ [![alexar76/warden MCP server](https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg)](https://glama.ai/mcp/servers/alexar76/warden)
89
97
 
90
98
  El mismo patrón que [ARGUS](https://github.com/alexar76/argus) y
91
99
  [aimarket-mcp](https://github.com/alexar76/aimarket-mcp): [`glama.json`](glama.json) +
@@ -133,7 +141,7 @@ descarga del threat feed que tú pediste al pasar una URL a `load()`.
133
141
 
134
142
  ```mermaid
135
143
  flowchart LR
136
- T["definiciones<br/>de herramientas"] --> S["static scan<br/>25 reglas"]
144
+ T["definiciones<br/>de herramientas"] --> S["static scan<br/>26 reglas"]
137
145
  S --> F["threat feed<br/>11 integradas + firmado"]
138
146
  F --> O["origin<br/>declarado vs catálogo"]
139
147
  O --> P["pinning<br/>deriva vs aprobación"]
@@ -142,7 +150,7 @@ flowchart LR
142
150
 
143
151
  | Puerta | Qué decide | Red | ¿Fatal? |
144
152
  |---|---|---|---|
145
- | **static-scan** | Inyección, exfiltración, peticiones de credenciales y señales de Unicode oculto/base64 en el `name`, la `description` y el `inputSchema` de la herramienta — 25 reglas, v4, de las cuales 15 pueden bloquear y 10 son solo de aviso, 17 cubren también el nombre y 12 llevan un guard de contexto | ninguna | no |
153
+ | **static-scan** | Inyección, exfiltración, peticiones de credenciales y señales de Unicode oculto/base64 en el `name`, la `description` y el `inputSchema` de la herramienta — 26 reglas, v6, de las cuales 15 pueden bloquear y 11 son solo de aviso, 17 cubren también el nombre y 15 llevan un guard de contexto. v5 normaliza antes el texto (ancho completo, caracteres invisibles, etiquetas Unicode, letras sosias), así que la ofuscación no elude ninguna regla en ningún idioma | ninguna | no |
146
154
  | **threat-feed** | Identidad de servidor o herramienta conocida como maliciosa: 11 registros integrados más un feed firmado opcional | solo la descarga del feed | sí, para un `critical` con alcance de servidor |
147
155
  | **origin** | Si el operador declaró este servidor o llegó desde un catálogo remoto | ninguna | sí, con `allowUnknownServers: false` |
148
156
  | **pinning** | Si las definiciones de herramientas siguen coincidiendo con lo que el usuario aprobó | ninguna | sí, con `pinToolDefs: true` |
@@ -164,7 +172,7 @@ endureciera el umbral.
164
172
  findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
165
173
  allowedTools: ["add"],
166
174
  blockedTools: ["sweeper"],
167
- rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
175
+ rulesets: { staticScan: { version: "6", digest: "sha256-dop0ekCh…" } }
168
176
  }
169
177
  ```
170
178
 
@@ -242,7 +250,7 @@ degradar a ninguna protección:
242
250
  ## Desarrollo
243
251
 
244
252
  ```bash
245
- npm install && npm run build && npm test # 166 pruebas
253
+ npm install && npm run build && npm test # 200 pruebas
246
254
  ```
247
255
 
248
256
  `test/packaging.test.ts` es lo que mantiene honesto el titular: falla si aparece una dependencia de
package/README-fr.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
9
9
  <a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="version npm" /></a>
10
10
  <img src="docs/badges/deps.svg" alt="Zéro dépendance d'exécution" />
11
- <img src="docs/badges/tests.svg" alt="166 tests au vert" />
11
+ <img src="docs/badges/tests.svg" alt="200 tests au vert" />
12
12
  <img src="docs/badges/node.svg" alt="Node >= 20" />
13
13
  <a href="LICENSE"><img src="docs/badges/license.svg" alt="Licence : MIT" /></a>
14
14
  </p>
@@ -23,6 +23,8 @@
23
23
 
24
24
  > 🌐 [English](README.md) · [Русский](README-ru.md) · [Español](README-es.md) · **Français** · [中文](README-zh.md) · [Glossaire](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
25
25
 
26
+ > [Sécurité et migration 0.7.0](docs/security-hardening.fr.md): `vetLaunch`, durable pins, anti-rollback, ruleset v6, runtime revalidation.
27
+
26
28
  **Un serveur MCP. Pare-feu des définitions d'outils annoncées. Bibliothèque incluse.**
27
29
 
28
30
  Transport : **stdio** (`npx -y @aimarket/warden` / `node dist/mcp-server.js`). Hôtes compatibles :
@@ -82,10 +84,16 @@ Le processus ne démarre, ne proxifie ni n'isole un autre serveur MCP : vous pas
82
84
  | `check_egress_url` | Allowlist d'hôtes (liste vide = tout refuser) |
83
85
  | `canonicalize_json` | Octets RFC 8785 |
84
86
  | `list_scan_rules` | Table de règles publiée |
87
+ | `status_mcp_server` | Comparer approbation et empreintes actuelles |
88
+ | `approve_mcp_server` | Approuver le snapshot examiné (activation opérateur) |
89
+ | `revoke_mcp_server` | Révoquer une approbation examinée (activation opérateur) |
85
90
 
86
91
  ### Publier sur Glama
87
92
 
88
- Listing : **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)**
93
+ Listing : **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)** ·
94
+ quality score : **[glama.ai/mcp/servers/alexar76/warden/score](https://glama.ai/mcp/servers/alexar76/warden/score)**
95
+
96
+ [![alexar76/warden MCP server](https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg)](https://glama.ai/mcp/servers/alexar76/warden)
89
97
 
90
98
  Même modèle que [ARGUS](https://github.com/alexar76/argus) et
91
99
  [aimarket-mcp](https://github.com/alexar76/aimarket-mcp) : [`glama.json`](glama.json) +
@@ -133,7 +141,7 @@ téléchargement du threat feed que vous avez demandé en passant une URL à `lo
133
141
 
134
142
  ```mermaid
135
143
  flowchart LR
136
- T["définitions<br/>des outils"] --> S["static scan<br/>25 règles"]
144
+ T["définitions<br/>des outils"] --> S["static scan<br/>26 règles"]
137
145
  S --> F["threat feed<br/>11 intégrées + signé"]
138
146
  F --> O["origin<br/>déclaré vs catalogue"]
139
147
  O --> P["pinning<br/>dérive vs approbation"]
@@ -142,7 +150,7 @@ flowchart LR
142
150
 
143
151
  | Porte | Ce qu'elle décide | Réseau | Fatale ? |
144
152
  |---|---|---|---|
145
- | **static-scan** | Injection, exfiltration, demandes d'identifiants et indices d'Unicode masqué/base64 dans le `name`, la `description` et l'`inputSchema` de l'outil — 25 règles, v4, dont 15 peuvent bloquer et 10 sont purement indicatives, 17 couvrent aussi le nom et 12 portent un guard de contexte | aucun | non |
153
+ | **static-scan** | Injection, exfiltration, demandes d'identifiants et indices d'Unicode masqué/base64 dans le `name`, la `description` et l'`inputSchema` de l'outil — 26 règles, v6, dont 15 peuvent bloquer et 11 sont purement indicatives, 17 couvrent aussi le nom et 15 portent un guard de contexte. v5 normalise d'abord le texte (pleine chasse, caractères invisibles, balises Unicode, lettres sosies) : l'obfuscation ne contourne aucune règle, quelle que soit la langue | aucun | non |
146
154
  | **threat-feed** | Identité de serveur ou outil connu comme malveillant : 11 enregistrements intégrés plus un feed signé optionnel | seulement le téléchargement du feed | oui, pour un `critical` de portée serveur |
147
155
  | **origin** | Si l'opérateur a déclaré ce serveur ou s'il provient d'un catalogue distant | aucun | oui, avec `allowUnknownServers: false` |
148
156
  | **pinning** | Si les définitions d'outils correspondent encore à ce que l'utilisateur a approuvé | aucun | oui, avec `pinToolDefs: true` |
@@ -164,7 +172,7 @@ bloquante pour quiconque durcissait le seuil.
164
172
  findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
165
173
  allowedTools: ["add"],
166
174
  blockedTools: ["sweeper"],
167
- rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
175
+ rulesets: { staticScan: { version: "6", digest: "sha256-dop0ekCh…" } }
168
176
  }
169
177
  ```
170
178
 
@@ -242,7 +250,7 @@ une absence de protection :
242
250
  ## Développement
243
251
 
244
252
  ```bash
245
- npm install && npm run build && npm test # 166 tests
253
+ npm install && npm run build && npm test # 200 tests
246
254
  ```
247
255
 
248
256
  `test/packaging.test.ts` est ce qui tient l'accroche honnête : il échoue si une dépendance d'exécution
package/README-ru.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
9
9
  <a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm version" /></a>
10
10
  <img src="docs/badges/deps.svg" alt="Zero runtime dependencies" />
11
- <img src="docs/badges/tests.svg" alt="166 tests passing" />
11
+ <img src="docs/badges/tests.svg" alt="200 tests passing" />
12
12
  <img src="docs/badges/node.svg" alt="Node >= 20" />
13
13
  <a href="LICENSE"><img src="docs/badges/license.svg" alt="License: MIT" /></a>
14
14
  </p>
@@ -23,6 +23,8 @@
23
23
 
24
24
  > 🌐 [English](README.md) · **Русский** · [Español](README-es.md) · [Français](README-fr.md) · [中文](README-zh.md) · [Глоссарий](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
25
25
 
26
+ > [Изменения безопасности 0.7.0 и миграция](docs/security-hardening.ru.md): `vetLaunch`, durable pins, anti-rollback, ruleset v6, runtime revalidation.
27
+
26
28
  **Один MCP-сервер. Файрвол для объявленных определений инструментов. Библиотека в комплекте.**
27
29
 
28
30
  Транспорт: **stdio** (`npx -y @aimarket/warden` / `node dist/mcp-server.js`). Совместимые хосты:
@@ -82,10 +84,16 @@ Claude Desktop / Cursor (`mcpServers`):
82
84
  | `check_egress_url` | Allowlist хостов (пустой список — отказ всем) |
83
85
  | `canonicalize_json` | Байты RFC 8785 |
84
86
  | `list_scan_rules` | Опубликованная таблица правил |
87
+ | `status_mcp_server` | Сравнить сохранённое одобрение и текущие хеши |
88
+ | `approve_mcp_server` | Одобрить рассмотренный снимок (разрешение оператора) |
89
+ | `revoke_mcp_server` | Отозвать рассмотренное одобрение (разрешение оператора) |
85
90
 
86
91
  ### Публикация на Glama
87
92
 
88
- Листинг: **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)**
93
+ Листинг: **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)** ·
94
+ quality score: **[glama.ai/mcp/servers/alexar76/warden/score](https://glama.ai/mcp/servers/alexar76/warden/score)**
95
+
96
+ [![alexar76/warden MCP server](https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg)](https://glama.ai/mcp/servers/alexar76/warden)
89
97
 
90
98
  Тот же шаблон, что у [ARGUS](https://github.com/alexar76/argus) и
91
99
  [aimarket-mcp](https://github.com/alexar76/aimarket-mcp): [`glama.json`](glama.json) +
@@ -133,7 +141,7 @@ await warden.approve(server, tools); // зафиксировать (pin) то,
133
141
 
134
142
  ```mermaid
135
143
  flowchart LR
136
- T["определения<br/>инструментов"] --> S["static scan<br/>25 правил"]
144
+ T["определения<br/>инструментов"] --> S["static scan<br/>26 правил"]
137
145
  S --> F["threat feed<br/>11 встроенных + подписанный"]
138
146
  F --> O["origin<br/>объявлен или из каталога"]
139
147
  O --> P["pinning<br/>дрейф против подтверждения"]
@@ -142,7 +150,7 @@ flowchart LR
142
150
 
143
151
  | Гейт | Что решает | Сеть | Fatal? |
144
152
  |---|---|---|---|
145
- | **static-scan** | Инъекции, эксфильтрация, запросы учётных данных, скрытый Unicode и base64-признаки в `name`, `description` и `inputSchema` инструмента — 25 правил, версия 4, из них 15 могут блокировать и 10 только сообщают, 17 покрывают и имя, а у 12 есть контекстный guard | нет | нет |
153
+ | **static-scan** | Инъекции, эксфильтрация, запросы учётных данных, скрытый Unicode и base64-признаки в `name`, `description` и `inputSchema` инструмента — 26 правил, версия 6, из них 15 могут блокировать и 11 только сообщают, 17 покрывают и имя, а у 15 есть контекстный guard. v5 сначала приводит текст к обычному виду (широкие буквы, невидимые символы, Unicode-теги, буквы-двойники), поэтому обфускация не обходит правило ни на каком языке | нет | нет |
146
154
  | **threat-feed** | Известный плохой сервер или инструмент: 11 встроенных записей плюс опциональный подписанный feed | только загрузка feed | да, для `critical` на уровне сервера |
147
155
  | **origin** | Объявил ли оператор этот сервер, или он пришёл из удалённого каталога | нет | да, при `allowUnknownServers: false` |
148
156
  | **pinning** | Совпадают ли определения инструментов с тем, что подтвердил пользователь | нет | да, при `pinToolDefs: true` |
@@ -163,7 +171,7 @@ severity снова делало находку блокирующей для в
163
171
  findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
164
172
  allowedTools: ["add"],
165
173
  blockedTools: ["sweeper"],
166
- rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
174
+ rulesets: { staticScan: { version: "6", digest: "sha256-dop0ekCh…" } }
167
175
  }
168
176
  ```
169
177
 
@@ -241,7 +249,7 @@ GET <ваш feed url>
241
249
  ## Разработка
242
250
 
243
251
  ```bash
244
- npm install && npm run build && npm test # 166 тестов
252
+ npm install && npm run build && npm test # 200 тестов
245
253
  ```
246
254
 
247
255
  `test/packaging.test.ts` — то, что удерживает заголовок честным: он падает, если появляется
package/README-zh.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
9
9
  <a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm 版本" /></a>
10
10
  <img src="docs/badges/deps.svg" alt="零运行时依赖" />
11
- <img src="docs/badges/tests.svg" alt="166 项测试通过" />
11
+ <img src="docs/badges/tests.svg" alt="200 项测试通过" />
12
12
  <img src="docs/badges/node.svg" alt="Node >= 20" />
13
13
  <a href="LICENSE"><img src="docs/badges/license.svg" alt="许可证:MIT" /></a>
14
14
  </p>
@@ -23,6 +23,8 @@
23
23
 
24
24
  > 🌐 [English](README.md) · [Русский](README-ru.md) · [Español](README-es.md) · [Français](README-fr.md) · **中文** · [术语表](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
25
25
 
26
+ > [0.7.0 安全更新与迁移](docs/security-hardening.zh.md): `vetLaunch`, durable pins, anti-rollback, ruleset v6, runtime revalidation.
27
+
26
28
  **一个 MCP 服务器。针对已广告工具定义的安全防火墙。附带库。**
27
29
 
28
30
  传输:**stdio**(`npx -y @aimarket/warden` / `node dist/mcp-server.js`)。兼容主机:Claude Desktop、
@@ -77,10 +79,16 @@ Claude Desktop / Cursor(`mcpServers`):
77
79
  | `check_egress_url` | 主机 allowlist(空列表拒绝全部) |
78
80
  | `canonicalize_json` | RFC 8785 字节 |
79
81
  | `list_scan_rules` | 已发布的规则表 |
82
+ | `status_mcp_server` | 比较已保存审批与当前哈希 |
83
+ | `approve_mcp_server` | 批准已审阅快照(需操作员开启) |
84
+ | `revoke_mcp_server` | 撤销已审阅审批(需操作员开启) |
80
85
 
81
86
  ### 发布到 Glama
82
87
 
83
- 列表页:**[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)**
88
+ 列表页:**[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)** ·
89
+ quality score:**[glama.ai/mcp/servers/alexar76/warden/score](https://glama.ai/mcp/servers/alexar76/warden/score)**
90
+
91
+ [![alexar76/warden MCP server](https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg)](https://glama.ai/mcp/servers/alexar76/warden)
84
92
 
85
93
  与 [ARGUS](https://github.com/alexar76/argus)、[aimarket-mcp](https://github.com/alexar76/aimarket-mcp)
86
94
  相同:[`glama.json`](glama.json) + [`Dockerfile`](Dockerfile) + `node dist/mcp-server.js`。
@@ -128,7 +136,7 @@ await warden.approve(server, tools); // 把用户认可的内容固定(pin)
128
136
 
129
137
  ```mermaid
130
138
  flowchart LR
131
- T["服务器给出的<br/>工具定义"] --> S["static scan<br/>25 条规则"]
139
+ T["服务器给出的<br/>工具定义"] --> S["static scan<br/>26 条规则"]
132
140
  S --> F["threat feed<br/>11 条内置 + 已签名"]
133
141
  F --> O["origin<br/>已声明 vs 来自目录"]
134
142
  O --> P["pinning<br/>漂移 vs 已批准"]
@@ -137,7 +145,7 @@ flowchart LR
137
145
 
138
146
  | 门控 | 判定什么 | 网络 | 是否 fatal |
139
147
  |---|---|---|---|
140
- | **static-scan** | 工具 `name`、`description` 与 `inputSchema` 中的注入、外泄、索要凭据,以及隐藏 Unicode/base64 迹象——25 条规则(v4),其中 15 条可阻止、10 条仅提示,17 条同时覆盖名称,12 条带有上下文 guard | 无 | 否 |
148
+ | **static-scan** | 工具 `name`、`description` 与 `inputSchema` 中的注入、外泄、索要凭据,以及隐藏 Unicode/base64 迹象——26 条规则(v6),其中 15 条可阻止、11 条仅提示,17 条同时覆盖名称,15 条带有上下文 guard。v5 先对文本做归一化(全角、不可见字符、Unicode 标签、形近字母),因此无论何种语言,混淆都绕不过规则 | 无 | 否 |
141
149
  | **threat-feed** | 已知恶意的服务器身份或工具:11 条内置记录,外加可选的已签名 feed | 仅 feed 下载 | 是,服务器范围的 `critical` |
142
150
  | **origin** | 该服务器是运营者声明的,还是来自远端目录 | 无 | 是,当 `allowUnknownServers: false` |
143
151
  | **pinning** | 工具定义是否仍与用户批准过的一致 | 无 | 是,当 `pinToolDefs: true` |
@@ -157,7 +165,7 @@ flowchart LR
157
165
  findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
158
166
  allowedTools: ["add"],
159
167
  blockedTools: ["sweeper"],
160
- rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
168
+ rulesets: { staticScan: { version: "6", digest: "sha256-dop0ekCh…" } }
161
169
  }
162
170
  ```
163
171
 
@@ -224,7 +232,7 @@ GET <你的 feed url>
224
232
  ## 开发
225
233
 
226
234
  ```bash
227
- npm install && npm run build && npm test # 166 项测试
235
+ npm install && npm run build && npm test # 200 项测试
228
236
  ```
229
237
 
230
238
  `test/packaging.test.ts` 正是让标题保持诚实的东西:一旦出现运行时依赖、任何源文件从包外 import、或者入口点不
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <a href="https://glama.ai/mcp/servers/alexar76/warden"><img src="https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg" alt="warden MCP server" /></a>
9
9
  <a href="https://warden.modelmarket.dev/"><img src="https://img.shields.io/npm/v/@aimarket/warden?color=cb3837&label=npm" alt="npm version" /></a>
10
10
  <img src="docs/badges/deps.svg" alt="Zero runtime dependencies" />
11
- <img src="docs/badges/tests.svg" alt="166 tests passing" />
11
+ <img src="docs/badges/tests.svg" alt="200 tests passing" />
12
12
  <img src="docs/badges/node.svg" alt="Node >= 20" />
13
13
  <a href="LICENSE"><img src="docs/badges/license.svg" alt="License: MIT" /></a>
14
14
  </p>
@@ -23,6 +23,8 @@
23
23
 
24
24
  > 🌐 **English** · [Русский](README-ru.md) · [Español](README-es.md) · [Français](README-fr.md) · [中文](README-zh.md) · [Glossary](https://github.com/alexar76/aicom/blob/main/docs/localization-glossary.md)
25
25
 
26
+ > [0.7.0 security changes and migration](docs/security-hardening.md): `vetLaunch`, durable pins, anti-rollback, ruleset v6, runtime revalidation.
27
+
26
28
  **One MCP server. Security firewall for advertised tool definitions. Library included.**
27
29
 
28
30
  Transport: **stdio** (`npx -y @aimarket/warden` / `node dist/mcp-server.js`). Compatible hosts:
@@ -82,13 +84,19 @@ in, you get a verdict out.
82
84
  | `check_egress_url` | Hostname allowlist (empty list denies every host) |
83
85
  | `canonicalize_json` | RFC 8785 bytes for feeds and pins |
84
86
  | `list_scan_rules` | Published rule table + digest |
87
+ | `status_mcp_server` | Review the saved approval and current hashes |
88
+ | `approve_mcp_server` | Approve the reviewed snapshot (operator opt-in) |
89
+ | `revoke_mcp_server` | Revoke a reviewed approval (operator opt-in) |
85
90
 
86
91
  Glama TDQS: MCP `annotations` (readOnly / destructive / idempotent / openWorld), when-to-use /
87
92
  when-not naming siblings, every `inputSchema` property described, `outputSchema` on every tool.
88
93
 
89
94
  ### Publish on Glama
90
95
 
91
- Listing: **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)**
96
+ Listing: **[glama.ai/mcp/servers/alexar76/warden](https://glama.ai/mcp/servers/alexar76/warden)** ·
97
+ quality score: **[glama.ai/mcp/servers/alexar76/warden/score](https://glama.ai/mcp/servers/alexar76/warden/score)**
98
+
99
+ [![alexar76/warden MCP server](https://glama.ai/mcp/servers/alexar76/warden/badges/score.svg)](https://glama.ai/mcp/servers/alexar76/warden)
92
100
 
93
101
  Same pattern as **[ARGUS](https://github.com/alexar76/argus)** and
94
102
  **[aimarket-mcp](https://github.com/alexar76/aimarket-mcp)**: repo-root [`glama.json`](glama.json) +
@@ -136,7 +144,7 @@ asked for by passing a URL to `load()`.
136
144
 
137
145
  ```mermaid
138
146
  flowchart LR
139
- T["tool defs<br/>from the server"] --> S["static scan<br/>25 rules"]
147
+ T["tool defs<br/>from the server"] --> S["static scan<br/>26 rules"]
140
148
  S --> F["threat feed<br/>11 built-ins + signed"]
141
149
  F --> O["origin<br/>declared vs catalog"]
142
150
  O --> P["pinning<br/>drift vs approval"]
@@ -145,7 +153,7 @@ flowchart LR
145
153
 
146
154
  | Gate | What it decides | Network | Fatal? |
147
155
  |---|---|---|---|
148
- | **static-scan** | Injection, exfiltration, credential requests and hidden-Unicode/base64 tells in the tool `name`, its `description` and its `inputSchema` — 25 rules, v4, of which 15 can block and 10 are advisory-only, 17 also cover the name, and 12 carry a context guard | none | no |
156
+ | **static-scan** | Injection, exfiltration, credential requests and hidden-Unicode/base64 tells in the tool `name`, its `description` and its `inputSchema` — 26 rules, v6, of which 15 can block and 11 are advisory-only, 17 also cover the name, and 15 carry a context guard. v5 folds the text first (fullwidth, invisible characters, Unicode tags, look-alike letters) so obfuscation cannot dodge a rule in any language | none | no |
149
157
  | **threat-feed** | Known-bad server identity or tool, from 11 built-in records plus an optional signed feed | only the feed fetch | yes, for a server-scoped `critical` |
150
158
  | **origin** | Whether the operator declared this server or it arrived from a remote catalog | none | yes, under `allowUnknownServers: false` |
151
159
  | **pinning** | Whether the tool defs still match what the user approved | none | yes, under `pinToolDefs: true` |
@@ -166,7 +174,7 @@ second as a low severity made it blocking again for anyone who tightened the thr
166
174
  findings: [{ gate, severity, code: "THREAT_TOOL_MATCH", message, tool, advisory? }],
167
175
  allowedTools: ["add"],
168
176
  blockedTools: ["sweeper"],
169
- rulesets: { staticScan: { version: "4", digest: "sha256-klRyTiD3…" } }
177
+ rulesets: { staticScan: { version: "6", digest: "sha256-dop0ekCh…" } }
170
178
  }
171
179
  ```
172
180
 
@@ -242,7 +250,7 @@ no protection:
242
250
  ## Development
243
251
 
244
252
  ```bash
245
- npm install && npm run build && npm test # 166 tests
253
+ npm install && npm run build && npm test # 200 tests
246
254
  ```
247
255
 
248
256
  `test/packaging.test.ts` is what keeps the headline honest: it fails if an npm runtime dependency
package/dist/fold.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Unicode folding before the rule table reads a tool definition.
3
+ *
4
+ * A rule written as `ignore previous instructions` must not be defeated by the
5
+ * same words spelled so that a regex no longer sees them while a model still
6
+ * does. Three spellings do that, and none of them depends on the language:
7
+ *
8
+ * 1. Compatibility forms. `ignore previous` (fullwidth), ligatures,
9
+ * mathematical alphanumerics, circled letters. NFKC maps each to the plain
10
+ * letter a reader sees.
11
+ * 2. Invisible characters inside a word. `ig​nore`, a soft hyphen, a
12
+ * variation selector. They are dropped here so they cannot split a rule word.
13
+ * The high-signal ones (zero-width, bidi, word-joiner, BOM, the tag block and
14
+ * the variation-selector supplement) are ALSO reported, because the
15
+ * hidden-character rule runs on the raw text; the purely cosmetic ones (a
16
+ * soft hyphen, an Indic/Arabic joiner control) are neutralised here and not
17
+ * separately flagged.
18
+ * 3. Look-alike letters from another script inside a Latin word: `іgnоre` with a
19
+ * Cyrillic і and о. A token that mixes Latin with Cyrillic or Greek letters has
20
+ * its look-alikes mapped to Latin. A token written wholly in one script is left
21
+ * alone, so Russian and Greek text still reads as Russian and Greek.
22
+ *
23
+ * Unicode TAG characters (U+E0020–U+E007E) are the exception to "dropped": each is
24
+ * an invisible copy of an ASCII character, and a whole instruction can be written in
25
+ * them. They are DECODED to that ASCII here, so the instruction they carry meets the
26
+ * same rules as visible text.
27
+ *
28
+ * The fold is part of the published ruleset (FOLD_ID is in its digest): the same
29
+ * regexes over folded and unfolded text are different scans.
30
+ */
31
+ /** Identity of this fold, carried in the ruleset preimage. Bump on any change below. */
32
+ export declare const FOLD_ID = "nfkc+tags-decoded+invisible-stripped+mixed-script-confusables/1";
33
+ /**
34
+ * Fold `text` for matching. Never shown to anyone: findings quote the folded span,
35
+ * and the raw text is what the hidden-character rules and the reader see.
36
+ */
37
+ export declare function foldForScan(text: string): string;
38
+ //# sourceMappingURL=fold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold.d.ts","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,wFAAwF;AACxF,eAAO,MAAM,OAAO,oEAAoE,CAAC;AAuCzF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBhD"}
package/dist/fold.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Unicode folding before the rule table reads a tool definition.
3
+ *
4
+ * A rule written as `ignore previous instructions` must not be defeated by the
5
+ * same words spelled so that a regex no longer sees them while a model still
6
+ * does. Three spellings do that, and none of them depends on the language:
7
+ *
8
+ * 1. Compatibility forms. `ignore previous` (fullwidth), ligatures,
9
+ * mathematical alphanumerics, circled letters. NFKC maps each to the plain
10
+ * letter a reader sees.
11
+ * 2. Invisible characters inside a word. `ig​nore`, a soft hyphen, a
12
+ * variation selector. They are dropped here so they cannot split a rule word.
13
+ * The high-signal ones (zero-width, bidi, word-joiner, BOM, the tag block and
14
+ * the variation-selector supplement) are ALSO reported, because the
15
+ * hidden-character rule runs on the raw text; the purely cosmetic ones (a
16
+ * soft hyphen, an Indic/Arabic joiner control) are neutralised here and not
17
+ * separately flagged.
18
+ * 3. Look-alike letters from another script inside a Latin word: `іgnоre` with a
19
+ * Cyrillic і and о. A token that mixes Latin with Cyrillic or Greek letters has
20
+ * its look-alikes mapped to Latin. A token written wholly in one script is left
21
+ * alone, so Russian and Greek text still reads as Russian and Greek.
22
+ *
23
+ * Unicode TAG characters (U+E0020–U+E007E) are the exception to "dropped": each is
24
+ * an invisible copy of an ASCII character, and a whole instruction can be written in
25
+ * them. They are DECODED to that ASCII here, so the instruction they carry meets the
26
+ * same rules as visible text.
27
+ *
28
+ * The fold is part of the published ruleset (FOLD_ID is in its digest): the same
29
+ * regexes over folded and unfolded text are different scans.
30
+ */
31
+ /** Identity of this fold, carried in the ruleset preimage. Bump on any change below. */
32
+ export const FOLD_ID = "nfkc+tags-decoded+invisible-stripped+mixed-script-confusables/1";
33
+ /**
34
+ * Characters with no visible form in running text. Dropped by the fold; the
35
+ * hidden-character rule reports them from the raw text.
36
+ */
37
+ const INVISIBLE_RE = new RegExp("[\\u00AD\\u034F\\u061C\\u115F\\u1160\\u17B4\\u17B5\\u180B-\\u180F\\u200B-\\u200F\\u202A-\\u202E" +
38
+ "\\u2060-\\u2064\\u2066-\\u206F\\u3164\\uFE00-\\uFE0F\\uFEFF\\uFFA0\\u{E0000}-\\u{E001F}" +
39
+ "\\u{E007F}\\u{E0100}-\\u{E01EF}]", "gu");
40
+ /** U+E0020–U+E007E: invisible ASCII. Decoded, not dropped. */
41
+ const TAG_RE = /[\u{E0020}-\u{E007E}]/gu;
42
+ /**
43
+ * Cyrillic and Greek letters that render like a Latin letter. Used only inside a
44
+ * token that already contains Latin letters.
45
+ */
46
+ const CONFUSABLE = {
47
+ // Cyrillic lower case
48
+ "а": "a", "в": "b", "е": "e", "ё": "e", "к": "k", "м": "m", "н": "h", "о": "o", "р": "p",
49
+ "с": "c", "т": "t", "у": "y", "х": "x", "і": "i", "ї": "i", "ј": "j", "ѕ": "s", "ԁ": "d",
50
+ "ԛ": "q", "ԝ": "w", "ӏ": "l", "һ": "h", "ɡ": "g",
51
+ // Cyrillic upper case
52
+ "А": "A", "В": "B", "Е": "E", "Ё": "E", "К": "K", "М": "M", "Н": "H", "О": "O", "Р": "P",
53
+ "С": "C", "Т": "T", "У": "Y", "Х": "X", "І": "I", "Ї": "I", "Ј": "J", "Ѕ": "S", "Һ": "H",
54
+ "Ԛ": "Q", "Ԝ": "W", "Ӏ": "I",
55
+ // Greek
56
+ "α": "a", "ε": "e", "ι": "i", "κ": "k", "ν": "v", "ο": "o", "ρ": "p", "τ": "t", "υ": "u",
57
+ "χ": "x", "Α": "A", "Β": "B", "Ε": "E", "Ζ": "Z", "Η": "H", "Ι": "I", "Κ": "K", "Μ": "M",
58
+ "Ν": "N", "Ο": "O", "Ρ": "P", "Τ": "T", "Υ": "Y", "Χ": "X",
59
+ };
60
+ const TOKEN_RE = /[\p{L}\p{M}]+/gu;
61
+ const LATIN_RE = /[A-Za-z]/;
62
+ const CONFUSABLE_RE = new RegExp(`[${Object.keys(CONFUSABLE).join("")}]`, "gu");
63
+ /**
64
+ * Fold `text` for matching. Never shown to anyone: findings quote the folded span,
65
+ * and the raw text is what the hidden-character rules and the reader see.
66
+ */
67
+ export function foldForScan(text) {
68
+ if (!text)
69
+ return text;
70
+ let out = text.normalize("NFKC");
71
+ out = out.replace(TAG_RE, (ch) => String.fromCharCode((ch.codePointAt(0) ?? 0xe0020) - 0xe0000));
72
+ out = out.replace(INVISIBLE_RE, "");
73
+ CONFUSABLE_RE.lastIndex = 0;
74
+ if (!CONFUSABLE_RE.test(out))
75
+ return out;
76
+ return out.replace(TOKEN_RE, (tok) => {
77
+ // Map a token that mixes Latin with look-alikes (`pаypаl`), OR one written
78
+ // WHOLLY in look-alike letters (`ЅУЅТЕМ`, `рrіvаtе` where every letter is in
79
+ // the table). A token with non-Latin letters that are NOT all confusables —
80
+ // ordinary Russian or Greek — is left alone, so those languages read normally.
81
+ // The scanner also matches the un-mapped raw text, so this only ever ADDS a
82
+ // reading; it never hides the original.
83
+ const marks = tok.replace(/\p{M}/gu, "");
84
+ if (!LATIN_RE.test(tok) && !allConfusable(marks))
85
+ return tok;
86
+ return tok.replace(CONFUSABLE_RE, (ch) => CONFUSABLE[ch] ?? ch);
87
+ });
88
+ }
89
+ /** Every letter of the token is one of the Latin look-alikes in the table. */
90
+ function allConfusable(letters) {
91
+ if (!letters)
92
+ return false;
93
+ for (const ch of letters) {
94
+ if (!(ch in CONFUSABLE) && !/[A-Za-z]/.test(ch))
95
+ return false;
96
+ }
97
+ return /[^A-Za-z]/.test(letters); // at least one non-Latin look-alike, else nothing to map
98
+ }
99
+ //# sourceMappingURL=fold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fold.js","sourceRoot":"","sources":["../src/fold.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import type { McpServerRef, PinStore, ToolDef, WardenGate, WardenLogger, WardenP
2
2
  import { ThreatFeed } from "./threat-feed.js";
3
3
  export { StaticScanGate, staticScanRuleset, staticScanRulesetRef, STATIC_SCAN_RULESET_VERSION, } from "./static-scan.js";
4
4
  export { PinningGate, canonicalToolsHash, tryCanonicalToolsHash, serverIdentityHash, UNCANONICAL_TOOLS_HASH } from "./pinning.js";
5
+ export { FilePinStore } from "./pin-store.js";
5
6
  export { OriginGate } from "./origin.js";
6
7
  export { ThreatFeed, ThreatGate, DEFAULT_FEED_MAX_AGE_MS, FEED_CLOCK_SKEW_MS } from "./threat-feed.js";
7
8
  export { EgressGuard, isSensitiveTool, classifyTools } from "./sandbox.js";
@@ -73,6 +74,7 @@ export declare class Warden {
73
74
  * circuiting only on a fatal gate result. A connection is blocked if any gate
74
75
  * is fatal or any finding reaches policy.blockAtSeverity.
75
76
  */
77
+ vetLaunch(server: McpServerRef): Promise<WardenVerdict>;
76
78
  vet(server: McpServerRef, tools: ToolDef[]): Promise<WardenVerdict>;
77
79
  /**
78
80
  * Run one gate, converting a thrown error into a blocking finding.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EAER,OAAO,EAEP,UAAU,EAEV,YAAY,EACZ,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,UAAU,EAAc,MAAM,kBAAkB,CAAC;AAK1D,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAClI,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACvG,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,cAAc,YAAY,CAAC;AAU3B,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,kEAAkE;IAClE,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE,UAAU;IAM5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,eAAe;IAWvB,yEAAyE;IACzE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM;IAU7C;;;;OAIG;IACG,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAqDzE;;;;;;;;;;;;;;OAcG;YACW,OAAO;IAiCrB;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpE;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,cAAc;CAGvB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EAER,OAAO,EAEP,UAAU,EAEV,YAAY,EACZ,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AAIpB,OAAO,EAAE,UAAU,EAAc,MAAM,kBAAkB,CAAC;AAK1D,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,oBAAoB,EACpB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAClI,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACvG,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC3E,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AACvG,YAAY,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AACrD,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1E,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,cAAc,YAAY,CAAC;AAU3B,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,kEAAkE;IAClE,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,QAAQ,CAAC;IAChB,MAAM,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,UAAU,EAAE,UAAU,CAAC;IACvB,GAAG,CAAC,EAAE,YAAY,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,qBAAa,MAAM;IACjB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAe;IACrC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,EAAE,UAAU;IAM5B;;;;;;;;;;;;OAYG;IACH,OAAO,CAAC,eAAe;IAWvB,yEAAyE;IACzE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM;IAU7C;;;;OAIG;IACG,SAAS,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;IAUvD,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;IAqDzE;;;;;;;;;;;;;;OAcG;YACW,OAAO;IAiCrB;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAUpE;;;;OAIG;IACH,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,cAAc;CAGvB"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { silentLogger } from "./logger.js";
7
7
  import { displaySafe } from "./sanitize.js";
8
8
  export { StaticScanGate, staticScanRuleset, staticScanRulesetRef, STATIC_SCAN_RULESET_VERSION, } from "./static-scan.js";
9
9
  export { PinningGate, canonicalToolsHash, tryCanonicalToolsHash, serverIdentityHash, UNCANONICAL_TOOLS_HASH } from "./pinning.js";
10
+ export { FilePinStore } from "./pin-store.js";
10
11
  export { OriginGate } from "./origin.js";
11
12
  export { ThreatFeed, ThreatGate, DEFAULT_FEED_MAX_AGE_MS, FEED_CLOCK_SKEW_MS } from "./threat-feed.js";
12
13
  export { EgressGuard, isSensitiveTool, classifyTools } from "./sandbox.js";
@@ -85,6 +86,13 @@ export class Warden {
85
86
  * circuiting only on a fatal gate result. A connection is blocked if any gate
86
87
  * is fatal or any finding reaches policy.blockAtSeverity.
87
88
  */
89
+ async vetLaunch(server) {
90
+ // Tool hashes cannot be checked until tools/list. Do not compare them to [].
91
+ const gates = this.gates.filter((g) => !(g instanceof StaticScanGate)).map((g) => g instanceof PinningGate
92
+ ? { name: g.name, evaluate: g.evaluateLaunch.bind(g) }
93
+ : g);
94
+ return new Warden({ gates, policy: this.policy, log: this.log }).vet(server, []);
95
+ }
88
96
  async vet(server, tools) {
89
97
  const findings = [];
90
98
  const scores = [];