@aimarket/warden 0.0.1 → 0.4.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 (53) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/LICENSE +21 -0
  3. package/README-es.md +190 -0
  4. package/README-fr.md +190 -0
  5. package/README-ru.md +189 -0
  6. package/README-zh.md +173 -0
  7. package/README.md +179 -6
  8. package/dist/glob.d.ts +81 -0
  9. package/dist/glob.d.ts.map +1 -0
  10. package/dist/glob.js +180 -0
  11. package/dist/glob.js.map +1 -0
  12. package/dist/index.d.ts +106 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +218 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/jcs.d.ts +86 -0
  17. package/dist/jcs.d.ts.map +1 -0
  18. package/dist/jcs.js +296 -0
  19. package/dist/jcs.js.map +1 -0
  20. package/dist/logger.d.ts +10 -0
  21. package/dist/logger.d.ts.map +1 -0
  22. package/dist/logger.js +19 -0
  23. package/dist/logger.js.map +1 -0
  24. package/dist/origin.d.ts +25 -0
  25. package/dist/origin.d.ts.map +1 -0
  26. package/dist/origin.js +49 -0
  27. package/dist/origin.js.map +1 -0
  28. package/dist/pinning.d.ts +91 -0
  29. package/dist/pinning.d.ts.map +1 -0
  30. package/dist/pinning.js +227 -0
  31. package/dist/pinning.js.map +1 -0
  32. package/dist/sandbox.d.ts +38 -0
  33. package/dist/sandbox.d.ts.map +1 -0
  34. package/dist/sandbox.js +79 -0
  35. package/dist/sandbox.js.map +1 -0
  36. package/dist/sanitize.d.ts +38 -0
  37. package/dist/sanitize.d.ts.map +1 -0
  38. package/dist/sanitize.js +53 -0
  39. package/dist/sanitize.js.map +1 -0
  40. package/dist/static-scan.d.ts +110 -0
  41. package/dist/static-scan.d.ts.map +1 -0
  42. package/dist/static-scan.js +539 -0
  43. package/dist/static-scan.js.map +1 -0
  44. package/dist/threat-feed.d.ts +138 -0
  45. package/dist/threat-feed.d.ts.map +1 -0
  46. package/dist/threat-feed.js +546 -0
  47. package/dist/threat-feed.js.map +1 -0
  48. package/dist/types.d.ts +173 -0
  49. package/dist/types.d.ts.map +1 -0
  50. package/dist/types.js +11 -0
  51. package/dist/types.js.map +1 -0
  52. package/package.json +67 -9
  53. package/index.js +0 -6
@@ -0,0 +1,546 @@
1
+ import { verify, createPublicKey } from "node:crypto";
2
+ import { countWildcards, MAX_GLOB_WILDCARDS, threatMatch } from "./glob.js";
3
+ import { canonicalize, parseJsonStrict, CanonicalizationError } from "./jcs.js";
4
+ import { displaySafe } from "./sanitize.js";
5
+ /**
6
+ * Threat-intel feed for known-bad MCP servers/tools.
7
+ *
8
+ * Reputation answers "is this server trusted?"; the threat feed answers "is this
9
+ * server a *known* bad actor?". It ships with a small built-in deny-list of
10
+ * patterns (credential-stealing, destructive commands, crypto-drainer keywords,
11
+ * typosquat-style names) and can be topped up from a signed remote feed.
12
+ *
13
+ * **In the shipping configuration there is no remote feed.** `warden.threatFeedUrl`
14
+ * and `warden.feedPublicKey` default to `undefined` (see `src/config.ts`), so
15
+ * unless an operator sets both, this gate *is* the {@link BUILTIN} list — a fixed
16
+ * floor of 11 patterns, not live intel. That is a deliberate default: ARGUS ships
17
+ * no feed endpoint and no publisher key, because a feed URL baked into the binary
18
+ * is a single point every install would have to trust.
19
+ *
20
+ * When a feed *is* configured, three properties are enforced fail-closed — any of
21
+ * them failing keeps the built-in floor and changes nothing else:
22
+ *
23
+ * - **Authenticity.** An Ed25519 signature over the canonical form of
24
+ * `{records, timestamp}`, verified with a pre-configured public key. Unsigned
25
+ * feeds are refused outright.
26
+ * - **Freshness.** The signed `timestamp` must be within {@link
27
+ * DEFAULT_FEED_MAX_AGE_MS} (configurable). Without this, whoever serves the feed
28
+ * URL can replay a months-old snapshot forever and silently erase every threat
29
+ * record added since — a signature says *who* wrote a document, never *when you
30
+ * were handed it*.
31
+ * - **Determinism.** The signed bytes are RFC 8785 canonical (§4 of
32
+ * `awr/SPEC.md`), so publisher and verifier agree on them regardless of the key
33
+ * order the wire used.
34
+ *
35
+ * The remote fetch degrades silently otherwise: a feed outage must never weaken
36
+ * the built-in floor or crash a connection check.
37
+ */
38
+ /**
39
+ * Default freshness window for a signed remote feed: 24 h.
40
+ *
41
+ * Rationale for a *default* rather than an opt-in: a feed whose age is unchecked
42
+ * is indistinguishable from a feed that is being replayed. An operator publishing
43
+ * on a slower cadence should raise `warden.feedMaxAgeMs`, not disable the check.
44
+ *
45
+ * (Note the deliberate difference from AWR documents, where age is *policy, not
46
+ * validity* — SPEC §11.3. A work receipt from two years ago is still a true
47
+ * statement about the past; a deny-list from two years ago is a false statement
48
+ * about the present.)
49
+ */
50
+ export const DEFAULT_FEED_MAX_AGE_MS = 24 * 60 * 60 * 1000;
51
+ /**
52
+ * Tolerance for a feed timestamp in the future — publisher/host clock skew.
53
+ * Beyond it the feed is refused: a future-dated snapshot would otherwise pass the
54
+ * freshness check for as long as the date it claims.
55
+ */
56
+ export const FEED_CLOCK_SKEW_MS = 5 * 60 * 1000;
57
+ /** Hard cap on a feed response body (OOM guard). */
58
+ const MAX_FEED_BYTES = 512_000;
59
+ /**
60
+ * Hard cap on how many records a feed may add.
61
+ *
62
+ * Matching is `records x (1 + tools)` string walks on every single connection
63
+ * check, so an enormous deny-list is a way to make vetting too slow to keep
64
+ * enabled. The body cap alone bounds this loosely (a minimal record is ~120
65
+ * bytes); this states the limit instead of leaving it to arithmetic. Exceeding it
66
+ * refuses the whole feed loudly rather than truncating it silently: a deny-list
67
+ * quietly cut off at record 2000 is worse than one that says it was refused.
68
+ */
69
+ const MAX_FEED_RECORDS = 2000;
70
+ /**
71
+ * Built-in deny-list. Small but real — patterns seen in poisoned MCP servers.
72
+ *
73
+ * `scope` says where each pattern means something (see {@link ThreatFeed.match}):
74
+ *
75
+ * - `server` for the **command-line and identity** signatures. `rm -rf`, a fork
76
+ * bomb or a typosquatted name are evidence in `server.command`/`args` or in a
77
+ * server's name; in prose they are as likely to be documentation as attack — a
78
+ * security tool's own description may well list `offical-mcp`.
79
+ * - `any` for the **content** signatures. A credential path, a seed phrase or a
80
+ * drainer keyword appearing in a tool name, description or input schema is
81
+ * exactly the tool-poisoning shape `static-scan` also looks for, so those
82
+ * patterns are matched against tool definitions as well as server identity.
83
+ */
84
+ const BUILTIN = [
85
+ {
86
+ pattern: "*~/.ssh*",
87
+ severity: "critical",
88
+ code: "THREAT_SSH_KEY_READ",
89
+ reason: "Server references the user's SSH key directory.",
90
+ source: "builtin",
91
+ scope: "any",
92
+ },
93
+ {
94
+ pattern: "*id_rsa*",
95
+ severity: "critical",
96
+ code: "THREAT_SSH_KEY_READ",
97
+ reason: "Server references a private SSH key file.",
98
+ source: "builtin",
99
+ scope: "any",
100
+ },
101
+ {
102
+ pattern: "*rm -rf*",
103
+ severity: "critical",
104
+ code: "THREAT_DESTRUCTIVE_CMD",
105
+ reason: "Server command performs a destructive recursive delete.",
106
+ source: "builtin",
107
+ scope: "server",
108
+ },
109
+ {
110
+ pattern: "*:(){ :|:&};:*",
111
+ severity: "critical",
112
+ code: "THREAT_FORK_BOMB",
113
+ reason: "Server command contains a shell fork bomb.",
114
+ source: "builtin",
115
+ scope: "server",
116
+ },
117
+ {
118
+ pattern: "*drain*wallet*",
119
+ severity: "critical",
120
+ code: "THREAT_CRYPTO_DRAINER",
121
+ reason: "Crypto-drainer keyword in server identity.",
122
+ source: "builtin",
123
+ scope: "any",
124
+ },
125
+ {
126
+ pattern: "*seed*phrase*",
127
+ severity: "high",
128
+ code: "THREAT_SEED_PHRASE",
129
+ reason: "Server references wallet seed phrases.",
130
+ source: "builtin",
131
+ scope: "any",
132
+ },
133
+ {
134
+ pattern: "*sweep*funds*",
135
+ severity: "critical",
136
+ code: "THREAT_CRYPTO_DRAINER",
137
+ reason: "Crypto-drainer keyword in server identity.",
138
+ source: "builtin",
139
+ scope: "any",
140
+ },
141
+ {
142
+ pattern: "*.env*exfil*",
143
+ severity: "critical",
144
+ code: "THREAT_ENV_EXFIL",
145
+ reason: "Server references exfiltrating environment files.",
146
+ source: "builtin",
147
+ scope: "any",
148
+ },
149
+ // Typosquat-style names mimicking the official reference servers.
150
+ {
151
+ pattern: "*offical-mcp*",
152
+ severity: "high",
153
+ code: "THREAT_TYPOSQUAT",
154
+ reason: "Typosquat of an official MCP server name.",
155
+ source: "builtin",
156
+ scope: "server",
157
+ },
158
+ {
159
+ pattern: "*modelcontextprotocoll*",
160
+ severity: "high",
161
+ code: "THREAT_TYPOSQUAT",
162
+ reason: "Typosquat of modelcontextprotocol.",
163
+ source: "builtin",
164
+ scope: "server",
165
+ },
166
+ {
167
+ pattern: "*filesytem*",
168
+ severity: "medium",
169
+ code: "THREAT_TYPOSQUAT",
170
+ reason: "Typosquat of the filesystem reference server.",
171
+ source: "builtin",
172
+ scope: "server",
173
+ },
174
+ ];
175
+ export class ThreatFeed {
176
+ records = [...BUILTIN];
177
+ compiled = compile([...BUILTIN]);
178
+ feedPublicKey;
179
+ maxAgeMs;
180
+ now;
181
+ log;
182
+ constructor(opts) {
183
+ this.feedPublicKey = opts?.feedPublicKey;
184
+ this.maxAgeMs =
185
+ opts?.maxAgeMs != null && Number.isFinite(opts.maxAgeMs) && opts.maxAgeMs > 0
186
+ ? opts.maxAgeMs
187
+ : DEFAULT_FEED_MAX_AGE_MS;
188
+ this.now = opts?.now ?? (() => Date.now());
189
+ this.log = opts?.log;
190
+ }
191
+ /** The built-in floor, always present regardless of remote load state. */
192
+ get builtins() {
193
+ return [...BUILTIN];
194
+ }
195
+ all() {
196
+ return [...this.records];
197
+ }
198
+ /** Single place where the record set changes, so the match cache cannot go stale. */
199
+ setRecords(records) {
200
+ this.records = records;
201
+ this.compiled = compile(records);
202
+ }
203
+ /**
204
+ * Fetch and verify a signed remote threat feed.
205
+ *
206
+ * Feed format: `{ records: ThreatRecord[], timestamp: number, signature: string }`
207
+ * where `timestamp` is **epoch milliseconds** and `signature` is a hex-encoded
208
+ * Ed25519 signature over the RFC 8785 (JCS) canonical form of
209
+ * `{records, timestamp}` — see {@link canonicalize}. A publisher that signs
210
+ * `JSON.stringify` output instead will not verify here, and that is the point:
211
+ * `JSON.stringify` bytes depend on the key order of whatever object the
212
+ * publisher happened to build, so the same logical feed could be signed and then
213
+ * fail to verify.
214
+ *
215
+ * - Without a configured `feedPublicKey`, remote feeds are refused.
216
+ * - Signature failure logs a warning and preserves the built-in floor.
217
+ * - A missing/non-integer `timestamp`, a snapshot older than `maxAgeMs`, or one
218
+ * dated further ahead than {@link FEED_CLOCK_SKEW_MS} is refused — a replayed
219
+ * old snapshot would otherwise erase every newer threat record.
220
+ * - Network/parse errors degrade silently — built-ins remain.
221
+ */
222
+ async load(feedUrl) {
223
+ if (!feedUrl)
224
+ return;
225
+ if (!this.feedPublicKey) {
226
+ this.log?.warn("threat feed URL configured but no feedPublicKey set — remote feed REFUSED (unsigned feeds not allowed)");
227
+ return;
228
+ }
229
+ try {
230
+ // AbortController with a 10 s timeout — a hanging feed must not block startup.
231
+ const ctrl = new AbortController();
232
+ const timer = setTimeout(() => ctrl.abort(), 10_000);
233
+ const res = await fetch(feedUrl, { signal: ctrl.signal }).finally(() => clearTimeout(timer));
234
+ if (!res.ok) {
235
+ this.log?.warn(`threat feed fetch returned ${res.status} — keeping built-in floor`);
236
+ return;
237
+ }
238
+ // Reject oversized responses before reading them (OOM guard).
239
+ const cl = res.headers.get("content-length");
240
+ if (cl && Number(cl) > MAX_FEED_BYTES) {
241
+ this.log?.warn(`threat feed: content-length ${cl} exceeds ${MAX_FEED_BYTES} byte limit — rejected`);
242
+ return;
243
+ }
244
+ // Read text, not res.json(): the received characters are what the canonical
245
+ // parser needs for its duplicate-key and number-literal checks. Read it in
246
+ // BOUNDED chunks rather than with res.text(): content-length is advisory —
247
+ // a chunked response can omit it or lie — and res.text() buffers whatever
248
+ // arrives before anyone can check the size, so the guard above was one
249
+ // missing header away from being no guard at all.
250
+ const body = await readCapped(res, MAX_FEED_BYTES);
251
+ if (body === undefined) {
252
+ this.log?.warn(`threat feed: response exceeds the ${MAX_FEED_BYTES} byte limit (content-length absent or wrong) — ` +
253
+ `download aborted, feed rejected`);
254
+ return;
255
+ }
256
+ let data;
257
+ try {
258
+ data = parseJsonStrict(body);
259
+ }
260
+ catch (err) {
261
+ this.log?.warn(`threat feed: body rejected by the canonical JSON parser (${err.message})`);
262
+ return;
263
+ }
264
+ if (!data || typeof data !== "object")
265
+ return;
266
+ const pkg = data;
267
+ const records = pkg.records;
268
+ const signature = pkg.signature;
269
+ const timestamp = pkg.timestamp;
270
+ if (!Array.isArray(records) || typeof signature !== "string") {
271
+ this.log?.warn("threat feed: missing or invalid fields (records, signature) — rejected");
272
+ return;
273
+ }
274
+ // The timestamp is REQUIRED: it is what the freshness check is made of, and
275
+ // an optional one would mean "a feed that omits it is fresh forever".
276
+ if (typeof timestamp !== "number" || !Number.isInteger(timestamp)) {
277
+ this.log?.warn("threat feed: missing or non-integer `timestamp` (epoch ms) — rejected, freshness cannot be checked");
278
+ return;
279
+ }
280
+ // Ed25519 signature over the CANONICAL JSON of {records, timestamp}.
281
+ let payload;
282
+ try {
283
+ payload = canonicalize({ records, timestamp });
284
+ }
285
+ catch (err) {
286
+ const detail = err instanceof CanonicalizationError ? err.message : err.message;
287
+ this.log?.warn(`threat feed: payload has no canonical form (${detail}) — rejected`);
288
+ return;
289
+ }
290
+ const sigBuf = Buffer.from(signature, "hex");
291
+ let pubKey;
292
+ try {
293
+ pubKey = createPublicKey({
294
+ key: Buffer.from(this.feedPublicKey, "hex"),
295
+ format: "der",
296
+ type: "spki",
297
+ });
298
+ }
299
+ catch (err) {
300
+ // A misconfigured key must say so out loud: silently degrading here would
301
+ // look exactly like "the feed had nothing new".
302
+ this.log?.warn(`threat feed: feedPublicKey is not a valid hex SPKI DER key (${err.message}) — feed REFUSED`);
303
+ return;
304
+ }
305
+ // An RSA or P-256 key parses fine and then makes verify() throw, which used
306
+ // to land in the outer catch and be logged at debug — indistinguishable from
307
+ // "the feed had nothing new". Say it here, once, at warn level.
308
+ if (pubKey.asymmetricKeyType !== "ed25519") {
309
+ this.log?.warn(`threat feed: feedPublicKey is a ${String(pubKey.asymmetricKeyType)} key, but feed signatures are ` +
310
+ `Ed25519 — feed REFUSED (built-in floor preserved)`);
311
+ return;
312
+ }
313
+ const ok = verify(null, Buffer.from(payload, "utf8"), pubKey, sigBuf);
314
+ if (!ok) {
315
+ this.log?.warn("threat feed: Ed25519 signature INVALID — feed rejected, built-in floor preserved");
316
+ return;
317
+ }
318
+ // Freshness is checked only now: until the signature verifies, `timestamp`
319
+ // is a number an attacker chose, and refusing on it would prove nothing.
320
+ const age = this.now() - timestamp;
321
+ if (age > this.maxAgeMs) {
322
+ this.log?.warn(`threat feed: signed snapshot is ${formatDuration(age)} old, past the ${formatDuration(this.maxAgeMs)} limit — ` +
323
+ `REJECTED as stale (possible replay of a superseded feed); built-in floor preserved`);
324
+ return;
325
+ }
326
+ if (age < -FEED_CLOCK_SKEW_MS) {
327
+ this.log?.warn(`threat feed: signed snapshot is dated ${formatDuration(-age)} in the future, beyond the ` +
328
+ `${formatDuration(FEED_CLOCK_SKEW_MS)} clock-skew allowance — REJECTED; built-in floor preserved`);
329
+ return;
330
+ }
331
+ if (records.length > MAX_FEED_RECORDS) {
332
+ this.log?.warn(`threat feed: ${records.length} records exceeds the ${MAX_FEED_RECORDS} cap — feed REFUSED ` +
333
+ `(built-in floor preserved); split the feed or raise the cap deliberately`);
334
+ return;
335
+ }
336
+ const remote = records.filter(isThreatRecord);
337
+ const dropped = records.length - remote.length;
338
+ if (dropped > 0) {
339
+ // Silence here used to hide a publisher shipping records nobody enforces:
340
+ // a wildcard-heavy pattern or a bad severity is dropped, not applied.
341
+ this.log?.warn(`threat feed: ${dropped} record(s) refused by validation — the rest are in effect`);
342
+ }
343
+ this.setRecords([...BUILTIN, ...remote]);
344
+ this.log?.info(`threat feed loaded: ${this.records.length} records (${BUILTIN.length} builtin + ${remote.length} remote, ` +
345
+ `signature valid, snapshot ${formatDuration(age)} old)`);
346
+ }
347
+ catch (err) {
348
+ // Degrade gracefully: built-ins remain in effect.
349
+ this.log?.debug(`threat feed load error: ${err.message}`);
350
+ }
351
+ }
352
+ /**
353
+ * Match a server — and its advertised tool definitions — against every record.
354
+ * One finding per matched record per surface.
355
+ *
356
+ * Two surfaces, because a threat record about an MCP server is not only a
357
+ * statement about the config the user wrote:
358
+ *
359
+ * - **Server identity** (`id`, `name`, `url`, `command`, `args`): the local
360
+ * config. This is the only surface a pattern matches when its `scope` is
361
+ * `"server"`.
362
+ * - **Tool definitions** (`name`, `description`, `inputSchema`): attacker-
363
+ * controlled text the *server* chose, which is where a poisoned tool actually
364
+ * lives. Matched for records scoped `"any"` (the default) or `"tool"`.
365
+ *
366
+ * Findings from the tool surface carry `tool`, so `Warden.partitionTools()` can
367
+ * quarantine the one tool instead of attributing the match to the whole
368
+ * connection. The connection is still blocked when the severity reaches
369
+ * `policy.blockAtSeverity` — per-tool attribution narrows the *blame*, it does
370
+ * not soften the policy.
371
+ */
372
+ match(server, tools = []) {
373
+ const serverHay = [
374
+ server.id,
375
+ server.name,
376
+ server.url ?? "",
377
+ server.command ?? "",
378
+ (server.args ?? []).join(" "),
379
+ ]
380
+ .join("\n")
381
+ .toLowerCase();
382
+ // Built once: the record loop below would otherwise re-stringify every schema.
383
+ const toolHays = tools.map((tool) => ({
384
+ name: tool.name,
385
+ hay: [tool.name, tool.description ?? "", stringifySchema(tool.inputSchema)].join("\n").toLowerCase(),
386
+ }));
387
+ const findings = [];
388
+ for (const { rec, pattern, glob } of this.compiled) {
389
+ const scope = rec.scope ?? "any";
390
+ // `reason`, `pattern` and `source` come off the wire from a publisher; the
391
+ // tool name comes from the server being inspected. All four end up in an
392
+ // operator's terminal, so none of them is interpolated raw.
393
+ const why = `${displaySafe(rec.reason, 400)} (matched "${displaySafe(pattern)}" in`;
394
+ const from = `source: ${displaySafe(rec.source, 80)})`;
395
+ if (scope !== "tool" && matches(pattern, glob, serverHay)) {
396
+ findings.push({
397
+ gate: "threat-feed",
398
+ severity: rec.severity,
399
+ code: rec.code,
400
+ message: `${why} server identity, ${from}`,
401
+ });
402
+ }
403
+ if (scope === "server")
404
+ continue;
405
+ for (const tool of toolHays) {
406
+ if (matches(pattern, glob, tool.hay)) {
407
+ findings.push({
408
+ gate: "threat-feed",
409
+ severity: rec.severity,
410
+ code: rec.code,
411
+ message: `Tool "${displaySafe(tool.name)}": ${why} the tool definition, ${from}`,
412
+ // Raw name: this is the key the host filters its tool list with.
413
+ tool: tool.name,
414
+ });
415
+ }
416
+ }
417
+ }
418
+ return findings;
419
+ }
420
+ }
421
+ /**
422
+ * Gate wrapper around a ThreatFeed. Any match is disqualifying for the score; a
423
+ * critical-severity match against the *server's identity* short-circuits the chain
424
+ * (fatal), because nothing about that connection is salvageable.
425
+ *
426
+ * A critical match against a single tool definition is deliberately not fatal: it
427
+ * still scores 0 and still blocks under the default `blockAtSeverity`, but letting
428
+ * the remaining gates run means the verdict names every problem and every affected
429
+ * tool instead of only the first one found.
430
+ */
431
+ export class ThreatGate {
432
+ feed;
433
+ name = "threat-feed";
434
+ constructor(feed) {
435
+ this.feed = feed;
436
+ }
437
+ async evaluate(input) {
438
+ const findings = this.feed.match(input.server, input.tools);
439
+ if (findings.length === 0)
440
+ return { findings, score: 1 };
441
+ const fatal = findings.some((f) => f.severity === "critical" && !f.tool);
442
+ return { findings, score: 0, fatal };
443
+ }
444
+ }
445
+ /**
446
+ * Read a response body as UTF-8 text, giving up as soon as it passes `cap` bytes.
447
+ *
448
+ * Returns `undefined` when the cap is exceeded, having cancelled the download —
449
+ * the point is to stop reading, not to read it all and then complain. Falls back
450
+ * to `res.text()` only when the runtime gives us no stream to read.
451
+ */
452
+ async function readCapped(res, cap) {
453
+ const stream = res.body;
454
+ if (!stream) {
455
+ const text = await res.text();
456
+ return Buffer.byteLength(text, "utf8") > cap ? undefined : text;
457
+ }
458
+ const reader = stream.getReader();
459
+ const chunks = [];
460
+ let total = 0;
461
+ try {
462
+ for (;;) {
463
+ const { done, value } = await reader.read();
464
+ if (done)
465
+ break;
466
+ if (!value)
467
+ continue;
468
+ total += value.byteLength;
469
+ if (total > cap) {
470
+ await reader.cancel().catch(() => { });
471
+ return undefined;
472
+ }
473
+ chunks.push(value);
474
+ }
475
+ }
476
+ finally {
477
+ reader.releaseLock();
478
+ }
479
+ return Buffer.concat(chunks).toString("utf8");
480
+ }
481
+ /** Lowercase every pattern once, and note whether it needs wildcard matching. */
482
+ function compile(records) {
483
+ return records.map((rec) => {
484
+ const pattern = rec.pattern.toLowerCase();
485
+ return { rec, pattern, glob: pattern.includes("*") };
486
+ });
487
+ }
488
+ /**
489
+ * Case-insensitive match against a pre-lowercased haystack. A pattern with no
490
+ * `*` is a plain substring test; one with `*` goes to `threatMatch`, which is the
491
+ * linear glob matcher plus a bounded interior gap and a word-boundary start.
492
+ *
493
+ * This used to compile `*` into `.*` and run a regex, which took 112 seconds on a
494
+ * 32-character pattern against a 220-character haystack — see ./glob.ts. A signed
495
+ * record could therefore hang every connection check, which is the one thing the
496
+ * feed's trust model says a publisher must not be able to do.
497
+ *
498
+ * It then used plain glob semantics, and the field survey (docs/mcp-survey.md)
499
+ * showed what that costs on real tool definitions: every multi-segment built-in
500
+ * fired on unrelated words in the same paragraph, and `*sweep*funds*` matched
501
+ * `funds` inside "refunds". Those are the constraints `threatMatch` adds.
502
+ */
503
+ function matches(pattern, glob, hay) {
504
+ return glob ? threatMatch(pattern, hay) : hay.includes(pattern);
505
+ }
506
+ /**
507
+ * Flatten a tool's input schema for pattern matching only. Key order is
508
+ * irrelevant here (a glob over a lowercased blob), so plain `JSON.stringify` is
509
+ * the right tool — unlike the signed and hashed paths, which need
510
+ * {@link canonicalize}.
511
+ */
512
+ function stringifySchema(schema) {
513
+ try {
514
+ return JSON.stringify(schema) ?? "";
515
+ }
516
+ catch {
517
+ return String(schema ?? "");
518
+ }
519
+ }
520
+ /** Human-readable duration for log lines: minutes under an hour, else hours. */
521
+ function formatDuration(ms) {
522
+ const minutes = Math.round(ms / 60_000);
523
+ if (Math.abs(minutes) < 60)
524
+ return `${minutes} min`;
525
+ return `${Math.round(ms / 360_000) / 10} h`;
526
+ }
527
+ const THREAT_SCOPES = ["server", "tool", "any"];
528
+ function isThreatRecord(v) {
529
+ if (!v || typeof v !== "object")
530
+ return false;
531
+ const r = v;
532
+ const severities = ["info", "low", "medium", "high", "critical"];
533
+ return (typeof r.pattern === "string" && r.pattern.length > 0 && r.pattern.length <= 2000 &&
534
+ // Bounded wildcards: see MAX_GLOB_WILDCARDS. A pattern past the bound is
535
+ // refused, never run, because the cost of running it is the attack.
536
+ countWildcards(r.pattern) <= MAX_GLOB_WILDCARDS &&
537
+ typeof r.code === "string" && r.code.length > 0 && r.code.length <= 200 &&
538
+ typeof r.reason === "string" && r.reason.length > 0 && r.reason.length <= 2000 &&
539
+ typeof r.source === "string" && r.source.length > 0 && r.source.length <= 200 &&
540
+ typeof r.severity === "string" &&
541
+ severities.includes(r.severity) &&
542
+ // An unrecognised scope drops the record instead of defaulting it: a publisher
543
+ // that meant "server" must not have its pattern silently widened to tool text.
544
+ (r.scope === undefined || (typeof r.scope === "string" && THREAT_SCOPES.includes(r.scope))));
545
+ }
546
+ //# sourceMappingURL=threat-feed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threat-feed.js","sourceRoot":"","sources":["../src/threat-feed.ts"],"names":[],"mappings":"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"}