@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.
- package/CHANGELOG.md +24 -1
- package/README-es.md +57 -6
- package/README-fr.md +57 -6
- package/README-ru.md +57 -6
- package/README-zh.md +57 -6
- package/README.md +57 -6
- 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.js +50 -44
- package/dist/mcp-server.js.map +1 -1
- package/dist/mcp-tools.d.ts +1 -12
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +92 -18
- 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/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 +2 -2
- package/dist/static-scan.d.ts.map +1 -1
- package/dist/static-scan.js +107 -8
- 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/dist/wrap-cli.d.ts +21 -0
- package/dist/wrap-cli.d.ts.map +1 -0
- package/dist/wrap-cli.js +158 -0
- package/dist/wrap-cli.js.map +1 -0
- package/dist/wrap-state.d.ts +19 -0
- package/dist/wrap-state.d.ts.map +1 -0
- package/dist/wrap-state.js +62 -0
- package/dist/wrap-state.js.map +1 -0
- package/dist/wrap-wire.d.ts +13 -0
- package/dist/wrap-wire.d.ts.map +1 -0
- package/dist/wrap-wire.js +88 -0
- package/dist/wrap-wire.js.map +1 -0
- package/dist/wrap.d.ts +3 -0
- package/dist/wrap.d.ts.map +1 -0
- package/dist/wrap.js +316 -0
- package/dist/wrap.js.map +1 -0
- package/docs/gates.es.md +224 -0
- package/docs/gates.fr.md +228 -0
- package/docs/gates.md +219 -0
- package/docs/gates.ru.md +225 -0
- package/docs/gates.zh.md +178 -0
- 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 +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
|
package/dist/wrap.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrap.js","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"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"}
|
package/docs/gates.es.md
ADDED
|
@@ -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.
|