@aimarket/warden 0.6.0 → 0.8.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 (69) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/README-es.md +57 -6
  3. package/README-fr.md +57 -6
  4. package/README-ru.md +57 -6
  5. package/README-zh.md +57 -6
  6. package/README.md +57 -6
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +8 -0
  10. package/dist/index.js.map +1 -1
  11. package/dist/mcp-rpc.d.ts +3 -1
  12. package/dist/mcp-rpc.d.ts.map +1 -1
  13. package/dist/mcp-rpc.js +32 -8
  14. package/dist/mcp-rpc.js.map +1 -1
  15. package/dist/mcp-server.js +50 -44
  16. package/dist/mcp-server.js.map +1 -1
  17. package/dist/mcp-tools.d.ts +1 -12
  18. package/dist/mcp-tools.d.ts.map +1 -1
  19. package/dist/mcp-tools.js +92 -18
  20. package/dist/mcp-tools.js.map +1 -1
  21. package/dist/pin-store.d.ts +13 -0
  22. package/dist/pin-store.d.ts.map +1 -0
  23. package/dist/pin-store.js +44 -0
  24. package/dist/pin-store.js.map +1 -0
  25. package/dist/pinning.d.ts +4 -3
  26. package/dist/pinning.d.ts.map +1 -1
  27. package/dist/pinning.js +16 -3
  28. package/dist/pinning.js.map +1 -1
  29. package/dist/state.d.ts +7 -0
  30. package/dist/state.d.ts.map +1 -0
  31. package/dist/state.js +61 -0
  32. package/dist/state.js.map +1 -0
  33. package/dist/static-scan.d.ts +2 -2
  34. package/dist/static-scan.d.ts.map +1 -1
  35. package/dist/static-scan.js +107 -8
  36. package/dist/static-scan.js.map +1 -1
  37. package/dist/threat-feed.d.ts +9 -0
  38. package/dist/threat-feed.d.ts.map +1 -1
  39. package/dist/threat-feed.js +50 -7
  40. package/dist/threat-feed.js.map +1 -1
  41. package/dist/types.d.ts +9 -0
  42. package/dist/types.d.ts.map +1 -1
  43. package/dist/wrap-cli.d.ts +21 -0
  44. package/dist/wrap-cli.d.ts.map +1 -0
  45. package/dist/wrap-cli.js +158 -0
  46. package/dist/wrap-cli.js.map +1 -0
  47. package/dist/wrap-state.d.ts +19 -0
  48. package/dist/wrap-state.d.ts.map +1 -0
  49. package/dist/wrap-state.js +62 -0
  50. package/dist/wrap-state.js.map +1 -0
  51. package/dist/wrap-wire.d.ts +13 -0
  52. package/dist/wrap-wire.d.ts.map +1 -0
  53. package/dist/wrap-wire.js +88 -0
  54. package/dist/wrap-wire.js.map +1 -0
  55. package/dist/wrap.d.ts +3 -0
  56. package/dist/wrap.d.ts.map +1 -0
  57. package/dist/wrap.js +316 -0
  58. package/dist/wrap.js.map +1 -0
  59. package/docs/gates.es.md +224 -0
  60. package/docs/gates.fr.md +228 -0
  61. package/docs/gates.md +219 -0
  62. package/docs/gates.ru.md +225 -0
  63. package/docs/gates.zh.md +178 -0
  64. package/docs/security-hardening.es.md +40 -0
  65. package/docs/security-hardening.fr.md +40 -0
  66. package/docs/security-hardening.md +40 -0
  67. package/docs/security-hardening.ru.md +40 -0
  68. package/docs/security-hardening.zh.md +40 -0
  69. package/package.json +7 -4
package/dist/wrap.js ADDED
@@ -0,0 +1,316 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { randomUUID } from 'node:crypto';
3
+ import { appendFileSync, mkdirSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { constants } from 'node:os';
6
+ import { Warden, FilePinStore, canonicalToolsHash, displaySafe, StaticScanGate } from './index.js';
7
+ import { WrapFrames, readFrames } from './wrap-wire.js';
8
+ import { loadWrapFeed, wrapLogger, wrapServer } from './wrap-cli.js';
9
+ import { observe, vetAndPin, observationPath } from './wrap-state.js';
10
+ import { readState } from './state.js';
11
+ function parse(body) {
12
+ const msg = JSON.parse(body);
13
+ if (!msg || Array.isArray(msg) || msg.jsonrpc !== '2.0' ||
14
+ (msg.id !== undefined && msg.id !== null && typeof msg.id !== 'string' && typeof msg.id !== 'number'))
15
+ throw new Error('Invalid JSON-RPC message');
16
+ return msg;
17
+ }
18
+ const LIST_BYTES = 1_048_576;
19
+ export async function runWrap(opts) {
20
+ const server = wrapServer(opts), log = wrapLogger(opts.auditOnly);
21
+ const feed = await loadWrapFeed(opts), store = new FilePinStore(join(opts.stateDir, 'pins'));
22
+ const warden = Warden.create({ policy: opts.policy, threatFeed: feed, store });
23
+ if (opts.verdictLog)
24
+ mkdirSync(dirname(opts.verdictLog), { recursive: true, mode: 0o700 });
25
+ const record = (verdict) => {
26
+ process.stderr.write(`warden: ${opts.auditOnly ? 'AUDIT-ONLY ' : ''}${verdict.allow ? 'ALLOW' : 'BLOCK'} ${displaySafe(server.id)} decidedBy=${displaySafe(verdict.decidedBy ?? '-')} score=${verdict.score.toFixed(2)} ${verdict.findings.map(f => `${displaySafe(f.code)}${f.tool ? `(${displaySafe(f.tool)})` : ''}`).join(' ')}\n`);
27
+ if (opts.verdictLog)
28
+ appendFileSync(opts.verdictLog, JSON.stringify({ timestamp: new Date().toISOString(), serverId: server.id,
29
+ mode: opts.auditOnly ? 'AUDIT-ONLY' : 'ENFORCE', ...verdict }) + '\n', { mode: 0o600 });
30
+ };
31
+ const launch = await warden.vetLaunch(server);
32
+ record(launch);
33
+ const prior = await readState(observationPath(opts.stateDir, server.id));
34
+ const blockedLaunch = !opts.auditOnly && (!launch.allow || prior?.revoked);
35
+ if (blockedLaunch)
36
+ await observe(opts.stateDir, { server, policy: opts.policy });
37
+ const reason = (v) => `blocked by WARDEN: ${v.findings.filter(f => !f.advisory).map(f => f.code).join(', ')}; warden-mcp pins status --id ${server.id}`;
38
+ const frames = new WrapFrames();
39
+ let child;
40
+ let finished = false, closing = false, generation = 0, quarantined = true;
41
+ let blockReason = blockedLaunch ? (prior?.revoked ? 'Approval revoked; run warden-mcp pins status --id ' + server.id : reason(launch)) : '';
42
+ let visible = [];
43
+ let refresh;
44
+ const prefix = `warden:${randomUUID()}:`;
45
+ let seq = 0;
46
+ const pending = new Map();
47
+ const tracked = new Map();
48
+ let activeChecks = 0;
49
+ let stopTimer, killTimer;
50
+ return new Promise(resolve => {
51
+ const finish = (code) => {
52
+ if (finished)
53
+ return;
54
+ finished = true;
55
+ clearTimeout(stopTimer);
56
+ clearTimeout(killTimer);
57
+ for (const p of pending.values()) {
58
+ clearTimeout(p.timer);
59
+ p.reject(new Error('MCP child exited'));
60
+ }
61
+ pending.clear();
62
+ process.stdin.pause();
63
+ process.removeListener('SIGTERM', signalStop);
64
+ process.removeListener('SIGINT', signalStop);
65
+ resolve(code);
66
+ };
67
+ const stop = () => {
68
+ if (closing)
69
+ return;
70
+ closing = true;
71
+ quarantined = true;
72
+ generation++;
73
+ if (!child) {
74
+ finish(blockedLaunch ? 3 : 0);
75
+ return;
76
+ }
77
+ child.stdin.end();
78
+ stopTimer = setTimeout(() => child?.kill('SIGTERM'), 5000);
79
+ killTimer = setTimeout(() => child?.kill('SIGKILL'), 9900);
80
+ };
81
+ const signalStop = () => stop();
82
+ const fatal = (error) => { log.error(error instanceof Error ? error.message : String(error)); stop(); };
83
+ const clientWrite = (body) => {
84
+ if (finished)
85
+ return;
86
+ const data = frames.mode === 'lsp' ? `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}` : body + '\n';
87
+ if (!process.stdout.write(data)) {
88
+ child?.stdout.pause();
89
+ process.stdout.once('drain', () => child?.stdout.resume());
90
+ }
91
+ };
92
+ const send = (msg) => clientWrite(JSON.stringify(msg));
93
+ const fail = (msg, message, code = -32602) => {
94
+ if (msg.id !== undefined)
95
+ send({ jsonrpc: '2.0', id: msg.id, error: { code, message: displaySafe(message, 8192) } });
96
+ };
97
+ const childWrite = (body) => {
98
+ if (!child || closing || finished || child.stdin.destroyed)
99
+ throw new Error('MCP child unavailable');
100
+ if (!child.stdin.write(body + '\n')) {
101
+ process.stdin.pause();
102
+ child.stdin.once('drain', () => { if (!closing)
103
+ process.stdin.resume(); });
104
+ }
105
+ };
106
+ const requestPage = (cursor) => new Promise((resolvePage, reject) => {
107
+ const id = prefix + seq++;
108
+ const timer = setTimeout(() => { pending.delete(id); reject(new Error('MCP tools/list timed out')); }, 10_000);
109
+ pending.set(id, { resolve: resolvePage, reject, timer });
110
+ try {
111
+ childWrite(JSON.stringify({ jsonrpc: '2.0', id, method: 'tools/list', params: cursor === undefined ? {} : { cursor } }));
112
+ }
113
+ catch (e) {
114
+ clearTimeout(timer);
115
+ pending.delete(id);
116
+ reject(e);
117
+ }
118
+ });
119
+ const listAll = async () => {
120
+ const tools = [], names = new Set(), cursors = new Set();
121
+ let cursor, bytes = 0;
122
+ for (let page = 0; page < 32; page++) {
123
+ const response = await requestPage(cursor), listed = response.result;
124
+ bytes += response.bytes;
125
+ if (bytes > LIST_BYTES)
126
+ throw new Error('MCP tools/list exceeds 1 MiB');
127
+ if (!listed || !Array.isArray(listed.tools))
128
+ throw new Error('Invalid MCP tools/list');
129
+ if (tools.length + listed.tools.length > 256)
130
+ throw new Error('MCP tools/list exceeds 256 tools');
131
+ for (const t of listed.tools) {
132
+ if (!t || typeof t !== 'object' || Array.isArray(t) || typeof t.name !== 'string' || !t.name || names.has(t.name) ||
133
+ (t.description !== undefined && typeof t.description !== 'string') || (t.title !== undefined && typeof t.title !== 'string'))
134
+ throw new Error('Invalid or duplicate MCP tool definition');
135
+ for (const key of ['inputSchema', 'outputSchema', 'annotations']) {
136
+ if (t[key] !== undefined && (!t[key] || typeof t[key] !== 'object' || Array.isArray(t[key])))
137
+ throw new Error(`Invalid MCP ${key}`);
138
+ }
139
+ names.add(t.name);
140
+ tools.push({ ...t, description: t.description ?? '', inputSchema: t.inputSchema ?? { type: 'object', properties: {} } });
141
+ }
142
+ if (listed.nextCursor === undefined)
143
+ return tools;
144
+ cursor = listed.nextCursor;
145
+ if (typeof cursor !== 'string' || !cursor || cursors.has(cursor))
146
+ throw new Error('Invalid or repeated MCP next-page token');
147
+ cursors.add(cursor);
148
+ }
149
+ throw new Error('MCP tools/list exceeds 32 pages');
150
+ };
151
+ const check = () => {
152
+ if (refresh)
153
+ return refresh;
154
+ const epoch = generation;
155
+ const work = (async () => {
156
+ const tools = await listAll();
157
+ const verdict = await vetAndPin(opts.stateDir, server, tools, opts.policy, store, warden, feed, opts.auditOnly, () => epoch === generation && !closing, record);
158
+ if (!opts.auditOnly && epoch !== generation)
159
+ throw new Error('tools changed, blocked by WARDEN during verification');
160
+ if (!opts.auditOnly && !verdict.allow)
161
+ throw new Error(reason(verdict));
162
+ if (closing)
163
+ throw new Error('MCP connection closing');
164
+ blockReason = '';
165
+ quarantined = false;
166
+ return opts.auditOnly ? tools : tools.filter(t => verdict.allowedTools.includes(t.name));
167
+ })();
168
+ refresh = work;
169
+ void work.then(() => { if (refresh === work)
170
+ refresh = undefined; }, e => {
171
+ if (refresh === work)
172
+ refresh = undefined;
173
+ quarantined = true;
174
+ blockReason = e instanceof Error ? e.message : String(e);
175
+ log.warn(blockReason);
176
+ });
177
+ return work;
178
+ };
179
+ const fromClient = async (body) => {
180
+ const msg = parse(body);
181
+ if (typeof msg.id === 'string' && msg.id.startsWith(prefix)) {
182
+ fail(msg, 'Reserved proxy request ID');
183
+ return;
184
+ }
185
+ if (blockedLaunch) {
186
+ fail(msg, blockReason, -32000);
187
+ if (msg.method === 'initialize')
188
+ finish(3);
189
+ return;
190
+ }
191
+ if (closing)
192
+ return;
193
+ if (msg.method === 'tools/list' || msg.method === 'tools/call') {
194
+ if (msg.id === undefined)
195
+ return; // These are requests, never notifications.
196
+ if (activeChecks >= 256) {
197
+ fail(msg, 'Too many pending MCP requests');
198
+ return;
199
+ }
200
+ activeChecks++;
201
+ try {
202
+ if (msg.method === 'tools/list') {
203
+ const tools = await check();
204
+ if (!opts.auditOnly && quarantined)
205
+ throw new Error(blockReason || 'tools changed, blocked by WARDEN');
206
+ visible = tools;
207
+ send({ jsonrpc: '2.0', id: msg.id, result: { tools } });
208
+ }
209
+ else {
210
+ const name = msg.params?.name;
211
+ const seen = visible.find(t => t.name === name);
212
+ if (!opts.auditOnly && (quarantined || !seen))
213
+ throw new Error(blockReason || 'tool blocked by WARDEN');
214
+ const epoch = generation;
215
+ const current = await check();
216
+ const def = current.find(t => t.name === name);
217
+ if (!opts.auditOnly && (quarantined || epoch !== generation || !def || !seen || canonicalToolsHash([def]) !== canonicalToolsHash([seen])))
218
+ throw new Error('tool blocked by WARDEN: definition changed since exposure');
219
+ if (tracked.size >= 256 || tracked.has(msg.id))
220
+ throw new Error('Too many or duplicate pending MCP requests');
221
+ tracked.set(msg.id, { kind: 'call', generation });
222
+ childWrite(body);
223
+ }
224
+ }
225
+ catch (e) {
226
+ fail(msg, e instanceof Error ? e.message : String(e));
227
+ }
228
+ finally {
229
+ activeChecks--;
230
+ }
231
+ return;
232
+ }
233
+ if (msg.method === 'initialize' && msg.id !== undefined) {
234
+ if (tracked.size >= 256 || tracked.has(msg.id)) {
235
+ fail(msg, 'Too many or duplicate pending MCP requests');
236
+ return;
237
+ }
238
+ tracked.set(msg.id, { kind: 'initialize', generation });
239
+ }
240
+ childWrite(body);
241
+ };
242
+ const fromChild = async (body) => {
243
+ const msg = parse(body);
244
+ if (msg.method === undefined && typeof msg.id === 'string' && msg.id.startsWith(prefix)) {
245
+ const p = pending.get(msg.id);
246
+ if (!p)
247
+ return; // Timed-out internal response must never leak to the client.
248
+ clearTimeout(p.timer);
249
+ pending.delete(msg.id);
250
+ if (msg.error)
251
+ p.reject(new Error('MCP tools/list failed: ' + JSON.stringify(msg.error)));
252
+ else
253
+ p.resolve({ result: msg.result, bytes: Buffer.byteLength(body) });
254
+ return;
255
+ }
256
+ if (msg.method === 'notifications/tools/list_changed') {
257
+ generation++;
258
+ quarantined = true;
259
+ blockReason = 'tools changed, blocked by WARDEN';
260
+ const epoch = generation;
261
+ // Do not reuse a check that started before the notification.
262
+ void (async () => {
263
+ await refresh?.catch(() => { });
264
+ if (epoch !== generation || closing)
265
+ return;
266
+ await check();
267
+ if (epoch === generation && !closing && (!quarantined || opts.auditOnly))
268
+ clientWrite(body);
269
+ })().catch(e => log.warn(e instanceof Error ? e.message : String(e)));
270
+ return;
271
+ }
272
+ if (msg.method === undefined && msg.id !== undefined) {
273
+ const request = tracked.get(msg.id);
274
+ if (request) {
275
+ tracked.delete(msg.id);
276
+ if (request.kind === 'call' && !opts.auditOnly && (quarantined || request.generation !== generation)) {
277
+ fail(msg, 'tools changed, blocked by WARDEN; result withheld, execution may already have occurred');
278
+ return;
279
+ }
280
+ if (request.kind === 'initialize' && msg.result && typeof msg.result === 'object') {
281
+ msg.result.capabilities = { ...msg.result.capabilities, tools: { ...msg.result.capabilities?.tools, listChanged: true } };
282
+ if (typeof msg.result.instructions === 'string') {
283
+ const scan = new Warden({ policy: opts.policy, gates: [new StaticScanGate()] });
284
+ const verdict = await scan.vet(server, [{ name: 'initialize.instructions', description: msg.result.instructions, inputSchema: { type: 'object' } }]);
285
+ record(verdict);
286
+ if (!verdict.allow && !opts.auditOnly)
287
+ delete msg.result.instructions;
288
+ }
289
+ else if (msg.result.instructions !== undefined && !opts.auditOnly)
290
+ delete msg.result.instructions;
291
+ send(msg);
292
+ return;
293
+ }
294
+ }
295
+ }
296
+ clientWrite(body);
297
+ };
298
+ process.stdout.on('error', fatal);
299
+ process.once('SIGTERM', signalStop);
300
+ process.once('SIGINT', signalStop);
301
+ if (!blockedLaunch) {
302
+ child = spawn(server.command, server.args, { stdio: ['pipe', 'pipe', 'pipe'], env: process.env });
303
+ child.on('error', e => { log.error(e.message); finish(1); });
304
+ child.on('close', (code, signal) => finish(code ?? (signal ? 128 + (constants.signals[signal] ?? 0) : 1)));
305
+ child.stdin.on('error', fatal);
306
+ // Bound and sanitize each chunk; a child cannot accumulate an unterminated stderr line.
307
+ child.stderr.on('data', chunk => {
308
+ for (const line of String(chunk).split('\n').filter(Boolean))
309
+ process.stderr.write(`warden: ${opts.auditOnly ? 'AUDIT-ONLY ' : ''}[child ${displaySafe(server.id)}] ${displaySafe(line, 8192)}\n`);
310
+ });
311
+ readFrames(child.stdout, new WrapFrames('ndjson'), body => { void fromChild(body).catch(fatal); }, fatal, () => { });
312
+ }
313
+ readFrames(process.stdin, frames, body => { void fromClient(body).catch(fatal); }, fatal, stop);
314
+ });
315
+ }
316
+ //# sourceMappingURL=wrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrap.js","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,224 @@
1
+ # La cadena de puertas
2
+
3
+ > 🌐 [English](gates.md) · [Русский](gates.ru.md) · **Español** · [Français](gates.fr.md) · [中文](gates.zh.md)
4
+
5
+ > [Seguridad y migración de 0.7.0](security-hardening.es.md).
6
+
7
+ `Warden.vet(server, tools)` ejecuta una cadena ordenada y devuelve un solo veredicto. Esta página es
8
+ todo el procedimiento de decisión: qué mira cada puerta, qué puede bloquear y cómo se construye el
9
+ número final.
10
+
11
+ ```
12
+ static-scan → threat-feed → origin → pinning
13
+ (gratis) (gratis tras (gratis) (gratis)
14
+ load)
15
+ ```
16
+
17
+ El orden va de lo más barato y local primero. Nada en la cadena hace una petición de red: la única
18
+ descarga que WARDEN llega a hacer es `ThreatFeed.load(url)`, que llamas tú, antes de examinar nada.
19
+
20
+ ## Cómo se ensambla un veredicto
21
+
22
+ Cada puerta devuelve `{ findings, score, fatal? }`. La cadena:
23
+
24
+ 1. ejecuta todas las puertas en orden, acumulando hallazgos (cada puerta ve `prior`);
25
+ 2. multiplica las puntuaciones de las puertas — la compuesta es un **producto**, así que una puerta
26
+ mala arrastra al servidor hacia abajo en vez de quedar promediada por tres buenas;
27
+ 3. bloquea si alguna puerta devolvió `fatal`, o si algún hallazgo no advisory alcanza
28
+ `policy.blockAtSeverity`;
29
+ 4. corta la cadena **solo** ante un `fatal` explícito. Un hallazgo bloqueante pero no fatal deja que
30
+ las puertas restantes informen, para que el registro del *por qué* quede completo.
31
+
32
+ ```ts
33
+ const SEVERITY_RANK = { info: 0, low: 1, medium: 2, high: 3, critical: 4 };
34
+ ```
35
+
36
+ Si `policy.blockAtSeverity` no es una de esas cinco claves, el constructor registra un aviso y cae a
37
+ `"high"`. Una errata ahí era antes el peor fallo posible: `rank >= undefined` es `false` en toda
38
+ comparación, de modo que un umbral mal escrito desactivaba el bloqueo por completo, en silencio.
39
+
40
+ ### Dos ejes: severidad y nivel
41
+
42
+ La severidad responde a *cuánta atención merece esto*. El **nivel** (tier) responde a *¿es esto un
43
+ defecto?* — y es un dato del hallazgo (`advisory: true`), no una consecuencia de la severidad.
44
+
45
+ Un hallazgo `advisory` se reporta, nunca bloquea y nunca cuesta una herramienta, con **cualquier**
46
+ `blockAtSeverity`. Una herramienta cuyo esquema acepta un `api_key` merece que se la señale y no es un
47
+ defecto; expresarlo bajando su severidad la volvía bloqueante para quien endureciera el umbral.
48
+
49
+ ## static-scan
50
+
51
+ Escaneo local con regex sobre el `name`, la `description` y el `inputSchema` de cada herramienta. 26
52
+ reglas en el conjunto **v7**: 15 `block`, 11 `advise`, y 15 de ellas llevan un **guard** de contexto:
53
+ una comprobación con nombre que decide si una coincidencia es de verdad lo que la regla busca. Véase
54
+ [el estudio de campo](mcp-survey.es.md), la ejecución sobre 1 108 servidores con la que se calibró v4.
55
+
56
+ **v5: el texto se normaliza antes de que lo lea ninguna regla.** NFKC convierte letras de ancho completo,
57
+ ligaduras y otras formas de compatibilidad en letras normales; los caracteres invisibles dentro de una
58
+ palabra se eliminan; el bloque de etiquetas Unicode (copias invisibles de ASCII capaces de llevar una frase
59
+ entera) se decodifica; y dentro de una palabra que mezcla latino con cirílico o griego, las letras sosias
60
+ pasan a latinas, mientras que una palabra escrita entera en un solo alfabeto no se toca. Una regla escrita
61
+ en inglés ya no se elude con letras de ancho completo, un espacio de ancho cero, etiquetas o una `о`
62
+ cirílica, sea cual sea el idioma del texto. La normalización se publica como `fold` y forma parte del
63
+ digest. Las dos reglas de cargas ocultas leen el texto **en bruto** (`raw: true`).
64
+
65
+ Una tabla de regex no lee significado: una frase en un idioma para el que las reglas no están escritas queda
66
+ fuera. v5 añade lo que no depende del idioma: la normalización, el par `TOOL_DEF_SECRET_EXFIL` (un almacén de
67
+ secretos y una dirección externa a menos de 100 caracteres; solo de aviso, porque su única coincidencia en
68
+ 10 645 servidores reales era honesta), el bloque de etiquetas y los aislantes bidi en `TOOL_DEF_HIDDEN_UNICODE`;
69
+ y HISTOR señala toda dirección externa que aparece por primera vez. Detectar por significado corresponde a un
70
+ clasificador, no a esta tabla.
71
+
72
+ v5 también elimina tres falsos positivos medidos: «send the user to https://…» (redirigir a una persona, guard
73
+ `navigation`), «keep calling … without asking the user» (autonomía, guard `autonomy`) y el unificador de ancho
74
+ cero dentro de un emoji. Sobre el corpus de 10 645 servidores v5 bloquea 56 donde v4 bloqueaba 63, y ninguno
75
+ que v4 no bloqueara.
76
+
77
+ **v7** elimina tres falsos positivos que el conjunto v6 aún daba sobre el corpus versionado del
78
+ 2026-10-01 ([el estudio de campo](mcp-survey.es.md)). «Private key/value memory» nombra un almacén
79
+ clave-valor, no una clave privada (guard `keyValue`: un compuesto con barra o guion seguido de un
80
+ sustantivo de almacén). «Find … without asking the user for ids» es una herramienta que resuelve sola
81
+ un identificador (guard `autonomy`, que ahora también acepta un verbo de búsqueda cuando todo el
82
+ objeto es un identificador). «The key is read from the MCP connection's X-API-Key header» es un
83
+ servidor que describe su propia autenticación (guard `ownAuthHeader`: pasiva «is read from», una
84
+ cabecera de la petición, no ajena, y la clave no se envía a ninguna parte). Además, `autonomy` ya no
85
+ exime un «without asking the user» cuyo objeto es el consentimiento: «keep retrying the transfer
86
+ without asking the user for approval» vuelve a bloquear. Sobre ese corpus v7 bloquea 3 servidores
87
+ donde v6 bloqueaba 6, y el resto de hallazgos no cambia.
88
+
89
+ Cada regla declara sobre cuál de esas tres **superficies** se ejecuta, y 17 de las 26 incluyen el
90
+ nombre. Las cuatro que no lo hacen son las que se apoyan en un SUSTANTIVO
91
+ (`TOOL_DEF_SECRET_REQUEST`, `TOOL_DEF_CREDENTIAL_PARAM`, `TOOL_DEF_ENV_REFERENCE`, `TOOL_DEF_SECRET_EXFIL`): un nombre es un
92
+ identificador, `api_key` y `private_key` son partes ordinarias de uno, y rechazar
93
+ `sign_with_private_key` sería cometer el error de calibración de v1 en una superficie nueva. Las
94
+ reglas que se apoyan en una FRASE necesitan espacios y no pueden coincidir con `snake_case` en
95
+ absoluto, y las dos reglas de carga oculta hablan de caracteres que nunca son legítimos en un
96
+ nombre: esas se ejecutan en todas partes.
97
+
98
+ Hasta v3 el nombre no lo escaneaba **nada**, así que una frase de inyección, un carácter de ancho
99
+ cero o un blob base64 en el primer campo que lee el modelo no se reportaban en absoluto.
100
+
101
+ La puntuación de la puerta es `1 − penalización(peor severidad bloqueante)`; los avisos nunca la
102
+ afectan.
103
+
104
+ | peor severidad bloqueante | ninguna | info | low | medium | high | critical |
105
+ |---|---|---|---|---|---|---|
106
+ | puntuación | 1 | 1 | 0.9 | 0.7 | 0.4 | 0 |
107
+
108
+ | Código | Severidad | Nivel | ¿Nombre? | Qué detecta |
109
+ |---|---|---|---|---|
110
+ | `TOOL_DEF_INJECTION` | critical / high | block | ✅ | «ignore all previous instructions», «do not tell the user», etiquetas `<system>`, referencias al prompt del desarrollador |
111
+ | `TOOL_DEF_SECRET_REQUEST` | critical | block | — | `private_key`, `seed_phrase`/`mnemonic`, rutas `~/.ssh` |
112
+ | `TOOL_DEF_SECRET_HARVEST` | critical | block | ✅ | una herramienta cuyo cometido declarado es leer/volcar/revelar secretos |
113
+ | `TOOL_DEF_EXFIL` | critical / high | block | ✅ | «post to https://…», «forward it to…», «exfiltrate», fraseo de subida a un host |
114
+ | `TOOL_DEF_HIDDEN_UNICODE` | high | block | ✅ | caracteres de ancho cero y de control bidi — texto que el revisor no ve |
115
+ | `TOOL_DEF_BASE64_BLOB` | high | block | ✅ | una tirada base64 de 120+ caracteres en un nombre, una descripción o un esquema |
116
+ | `TOOL_DEF_DATA_URL` | high | block | ✅ | URLs `data:…;base64,` y `javascript:` |
117
+ | `TOOL_DEF_CREDENTIAL_PARAM` | medium / low | advise | — | esquema o descripción que pide `api_key`, `password`, `secret`, tokens bearer |
118
+ | `TOOL_DEF_ENV_REFERENCE` | medium | advise | — | `.env`, «environment variables» |
119
+ | `TOOL_DEF_IMPERATIVE` | low / info | advise | ✅ | «you must», «instead of» — fraseo con forma de prompt, que por sí solo no prueba nada |
120
+
121
+ `staticScanRuleset()` devuelve cada regla con **el fuente de su regex, sus flags y sus superficies**, para que un
122
+ tercero pueda reejecutar exactamente la misma regla, más `{ version, digest }`, donde el digest es
123
+ sha256 sobre la forma canónica RFC 8785 de la lista ordenada de reglas. La ordenación es por
124
+ comparación de unidades de código, nunca `localeCompare`: una collation dependiente del locale haría
125
+ que la misma tabla produjera un digest distinto en un host configurado de otra forma, que es justo la
126
+ divergencia que el digest existe para detectar.
127
+
128
+ ## threat-feed
129
+
130
+ Compara la identidad del servidor y las definiciones de herramientas con registros `ThreatRecord` — 11
131
+ integrados más lo que haya añadido un feed firmado (ver [el contrato del feed](threat-feed.es.md)).
132
+
133
+ - Cualquier coincidencia ⇒ puntuación de la puerta **0**.
134
+ - `fatal` **solo** para un registro `critical` que coincide contra el *servidor*. Una coincidencia
135
+ crítica en una *herramienta* no es fatal, así que el resto de la cadena sigue informando y la culpa
136
+ queda circunscrita a esa herramienta — eso es lo que permite que un servidor casi correcto siga
137
+ funcionando con una herramienta en cuarentena.
138
+ - `ThreatRecord.scope` elige la superficie: `server` (id/name/url/command/args), `tool`
139
+ (name/description/inputSchema) o `any` — el valor por defecto cuando el registro lo omite.
140
+
141
+ Códigos integrados: `THREAT_TYPOSQUAT`, `THREAT_CRYPTO_DRAINER`, `THREAT_SEED_PHRASE`,
142
+ `THREAT_SSH_KEY_READ`, `THREAT_ENV_EXFIL`, `THREAT_DESTRUCTIVE_CMD`, `THREAT_FORK_BOMB`.
143
+
144
+ ## origin
145
+
146
+ ¿Declaró el operador este servidor, o llegó desde un catálogo remoto (`McpServerRef.catalog` está
147
+ puesto)?
148
+
149
+ | `allowUnknownServers` | hallazgo | puntuación | fatal |
150
+ |---|---|---|---|
151
+ | `false` (fail-closed) | `SERVER_UNDECLARED`, high | 0 | sí |
152
+ | `true` | `SERVER_UNDECLARED`, info | 1 | no |
153
+
154
+ Este interruptor significaba antes «todavía no tiene puntuación de reputación», algo que ningún
155
+ despliegue podía satisfacer: nunca se suministraron aristas de confianza al oráculo, así que todo
156
+ servidor volvía sin avalar y `false` los bloqueaba todos. La procedencia de catálogo es un hecho que
157
+ el host ya tiene en local, no necesita red y no puede provocar un bloqueo mutuo.
158
+
159
+ ## pinning
160
+
161
+ Compara las definiciones actuales con la instantánea que aprobó el usuario. El hash es sha256 sobre la
162
+ forma canónica RFC 8785 del conjunto de definiciones — la misma canonicalización que usa la firma del
163
+ feed, no una segunda serialización.
164
+
165
+ | Situación | Código | Severidad | Puntuación | Fatal |
166
+ |---|---|---|---|---|
167
+ | Aún sin pin (primer contacto) | `TOOL_DEF_UNPINNED` | info | 0.9 | no |
168
+ | El hash difiere del pin | `TOOL_DEF_DRIFT` | high | 0 | con `pinToolDefs` |
169
+ | Las definiciones no tienen forma canónica (sin pin) | `TOOL_DEF_UNCANONICAL` | medium | 0.5 | no |
170
+ | Las definiciones no tienen forma canónica (con pin) | `TOOL_DEF_UNCANONICAL` | high | 0 | con `pinToolDefs` |
171
+
172
+ El primer contacto cuesta 0.1, no un bloqueo: un servidor limpio, declarado y sin pin puntúa
173
+ exactamente **0.9**, y `TOOL_DEF_UNPINNED` es `info` a propósito — con `blockAtSeverity: "info"`, un
174
+ primer encuentro bloqueante haría que todo servidor fuera inservible para siempre, ya que nada puede
175
+ fijarse antes de ser aprobado una vez.
176
+
177
+ `warden.approve(server, tools)` escribe el pin a través de tu `PinStore`. Es idempotente.
178
+
179
+ ## Partición por herramienta
180
+
181
+ `allowedTools` / `blockedTools` reparten las herramientas anunciadas:
182
+
183
+ - una herramienta está **bloqueada** si un hallazgo no advisory la nombra (`finding.tool`) y alcanza
184
+ el umbral;
185
+ - todas las demás quedan permitidas;
186
+ - las herramientas sensibles (`policy.sensitiveToolPatterns`) siguen *permitidas*: quedan marcadas
187
+ para que el bucle de tu agente pueda exigir aprobación en cada llamada en tiempo de ejecución. Ver
188
+ `classifyTools` / `isSensitiveTool`.
189
+
190
+ ## Añadir una puerta
191
+
192
+ `WardenGate` son tres líneas de interfaz, y `new Warden({ gates, policy, log })` toma la cadena
193
+ directamente, así que puedes insertar la tuya sin hacer un fork:
194
+
195
+ ```ts
196
+ import { Warden, StaticScanGate, ThreatGate, OriginGate, PinningGate } from "@aimarket/warden";
197
+ import type { WardenGate, WardenGateInput, WardenGateResult } from "@aimarket/warden";
198
+
199
+ class DenyByPublisher implements WardenGate {
200
+ readonly name = "publisher-allowlist";
201
+ async evaluate(input: WardenGateInput): Promise<WardenGateResult> {
202
+ const ok = ALLOWED.has(input.server.name);
203
+ return ok
204
+ ? { findings: [], score: 1 }
205
+ : { findings: [{ gate: this.name, severity: "high", code: "PUBLISHER_UNKNOWN",
206
+ message: `${input.server.name} no es un editor permitido` }],
207
+ score: 0, fatal: true };
208
+ }
209
+ }
210
+
211
+ const warden = new Warden({
212
+ gates: [new StaticScanGate(), new ThreatGate(feed), new DenyByPublisher(), new OriginGate(), new PinningGate(store)],
213
+ policy,
214
+ });
215
+ ```
216
+
217
+ Dos reglas para una puerta propia: **nunca afirmes que un servicio remoto es inalcanzable si no
218
+ enviaste realmente una petición** (`test/no-phantom-gate.test.ts` lo impone sobre las puertas que se
219
+ envían) y devuelve una puntuación que puedas defender — una puerta que no midió nada debe devolver
220
+ `1`, no un 0.6 «neutro», o penaliza a cada servidor por una medición que nunca hizo.
221
+
222
+ ## Las puertas en modo wrap
223
+
224
+ `vetLaunch` precede al arranque. `initialize.instructions` pasa por static-scan y se elimina al bloquearse. `tools/list` reúne como máximo 32 páginas, 256 herramientas únicas y 1 MiB antes de `vet`; solo se exponen definiciones permitidas en una página. `list_changed` activa cuarentena inmediatamente y se reenvía solo tras verificar. Cada llamada vuelve a listar y compara con la definición mostrada al cliente. Un cambio durante la comprobación impide enviar; durante la ejecución retiene el resultado. Los mensajes transparentes tienen límite de 32 MiB. Las peticiones internas caducan a los 10 segundos. EOF cierra stdin del hijo, SIGTERM llega tras 5 segundos y SIGKILL antes de 10. Las peticiones del servidor y demás mensajes conservan ID y JSON original. Consulte README para política, TOFU, revisión humana y audit-only.