@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.
- package/CHANGELOG.md +45 -0
- package/README-es.md +14 -6
- package/README-fr.md +14 -6
- package/README-ru.md +14 -6
- package/README-zh.md +14 -6
- package/README.md +14 -6
- package/dist/fold.d.ts +38 -0
- package/dist/fold.d.ts.map +1 -0
- package/dist/fold.js +99 -0
- package/dist/fold.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-rpc.d.ts +3 -1
- package/dist/mcp-rpc.d.ts.map +1 -1
- package/dist/mcp-rpc.js +32 -8
- package/dist/mcp-rpc.js.map +1 -1
- package/dist/mcp-server.d.ts +7 -2
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +43 -49
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools.d.ts +0 -12
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +104 -23
- package/dist/mcp-tools.js.map +1 -1
- package/dist/pin-store.d.ts +13 -0
- package/dist/pin-store.d.ts.map +1 -0
- package/dist/pin-store.js +44 -0
- package/dist/pin-store.js.map +1 -0
- package/dist/pinning.d.ts +4 -3
- package/dist/pinning.d.ts.map +1 -1
- package/dist/pinning.js +16 -3
- package/dist/pinning.js.map +1 -1
- package/dist/sanitize.d.ts.map +1 -1
- package/dist/sanitize.js +6 -3
- package/dist/sanitize.js.map +1 -1
- package/dist/state.d.ts +7 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +61 -0
- package/dist/state.js.map +1 -0
- package/dist/static-scan.d.ts +15 -2
- package/dist/static-scan.d.ts.map +1 -1
- package/dist/static-scan.js +300 -25
- package/dist/static-scan.js.map +1 -1
- package/dist/threat-feed.d.ts +9 -0
- package/dist/threat-feed.d.ts.map +1 -1
- package/dist/threat-feed.js +50 -7
- package/dist/threat-feed.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/security-hardening.es.md +40 -0
- package/docs/security-hardening.fr.md +40 -0
- package/docs/security-hardening.md +40 -0
- package/docs/security-hardening.ru.md +40 -0
- package/docs/security-hardening.zh.md +40 -0
- 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="
|
|
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
|
+
[](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/>
|
|
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 —
|
|
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: "
|
|
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 #
|
|
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="
|
|
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
|
+
[](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/>
|
|
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 —
|
|
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: "
|
|
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 #
|
|
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="
|
|
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
|
+
[](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/>
|
|
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` инструмента —
|
|
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: "
|
|
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 #
|
|
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="
|
|
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
|
+
[](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/>
|
|
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 迹象——
|
|
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: "
|
|
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 #
|
|
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="
|
|
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
|
+
[](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/>
|
|
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` —
|
|
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: "
|
|
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 #
|
|
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. `ignore`, 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. `ignore`, 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
|
package/dist/fold.js.map
ADDED
|
@@ -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.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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 = [];
|