@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,138 @@
1
+ import type { McpServerRef, ThreatRecord, ToolDef, WardenFinding, WardenGate, WardenGateInput, WardenGateResult, WardenLogger } from "./types.js";
2
+ /**
3
+ * Threat-intel feed for known-bad MCP servers/tools.
4
+ *
5
+ * Reputation answers "is this server trusted?"; the threat feed answers "is this
6
+ * server a *known* bad actor?". It ships with a small built-in deny-list of
7
+ * patterns (credential-stealing, destructive commands, crypto-drainer keywords,
8
+ * typosquat-style names) and can be topped up from a signed remote feed.
9
+ *
10
+ * **In the shipping configuration there is no remote feed.** `warden.threatFeedUrl`
11
+ * and `warden.feedPublicKey` default to `undefined` (see `src/config.ts`), so
12
+ * unless an operator sets both, this gate *is* the {@link BUILTIN} list — a fixed
13
+ * floor of 11 patterns, not live intel. That is a deliberate default: ARGUS ships
14
+ * no feed endpoint and no publisher key, because a feed URL baked into the binary
15
+ * is a single point every install would have to trust.
16
+ *
17
+ * When a feed *is* configured, three properties are enforced fail-closed — any of
18
+ * them failing keeps the built-in floor and changes nothing else:
19
+ *
20
+ * - **Authenticity.** An Ed25519 signature over the canonical form of
21
+ * `{records, timestamp}`, verified with a pre-configured public key. Unsigned
22
+ * feeds are refused outright.
23
+ * - **Freshness.** The signed `timestamp` must be within {@link
24
+ * DEFAULT_FEED_MAX_AGE_MS} (configurable). Without this, whoever serves the feed
25
+ * URL can replay a months-old snapshot forever and silently erase every threat
26
+ * record added since — a signature says *who* wrote a document, never *when you
27
+ * were handed it*.
28
+ * - **Determinism.** The signed bytes are RFC 8785 canonical (§4 of
29
+ * `awr/SPEC.md`), so publisher and verifier agree on them regardless of the key
30
+ * order the wire used.
31
+ *
32
+ * The remote fetch degrades silently otherwise: a feed outage must never weaken
33
+ * the built-in floor or crash a connection check.
34
+ */
35
+ /**
36
+ * Default freshness window for a signed remote feed: 24 h.
37
+ *
38
+ * Rationale for a *default* rather than an opt-in: a feed whose age is unchecked
39
+ * is indistinguishable from a feed that is being replayed. An operator publishing
40
+ * on a slower cadence should raise `warden.feedMaxAgeMs`, not disable the check.
41
+ *
42
+ * (Note the deliberate difference from AWR documents, where age is *policy, not
43
+ * validity* — SPEC §11.3. A work receipt from two years ago is still a true
44
+ * statement about the past; a deny-list from two years ago is a false statement
45
+ * about the present.)
46
+ */
47
+ export declare const DEFAULT_FEED_MAX_AGE_MS: number;
48
+ /**
49
+ * Tolerance for a feed timestamp in the future — publisher/host clock skew.
50
+ * Beyond it the feed is refused: a future-dated snapshot would otherwise pass the
51
+ * freshness check for as long as the date it claims.
52
+ */
53
+ export declare const FEED_CLOCK_SKEW_MS: number;
54
+ export interface ThreatFeedOptions {
55
+ /** Ed25519 public key (hex-encoded SPKI DER) of the feed publisher. */
56
+ feedPublicKey?: string;
57
+ /**
58
+ * Maximum age of a signed feed's `timestamp`. Defaults to
59
+ * {@link DEFAULT_FEED_MAX_AGE_MS}; a non-finite or non-positive value falls back
60
+ * to the default rather than disabling the check.
61
+ */
62
+ maxAgeMs?: number;
63
+ log?: WardenLogger;
64
+ /** Clock source. Injectable so freshness is testable without waiting a day. */
65
+ now?: () => number;
66
+ }
67
+ export declare class ThreatFeed {
68
+ private records;
69
+ private compiled;
70
+ private feedPublicKey?;
71
+ private readonly maxAgeMs;
72
+ private readonly now;
73
+ private readonly log?;
74
+ constructor(opts?: ThreatFeedOptions);
75
+ /** The built-in floor, always present regardless of remote load state. */
76
+ get builtins(): ThreatRecord[];
77
+ all(): ThreatRecord[];
78
+ /** Single place where the record set changes, so the match cache cannot go stale. */
79
+ private setRecords;
80
+ /**
81
+ * Fetch and verify a signed remote threat feed.
82
+ *
83
+ * Feed format: `{ records: ThreatRecord[], timestamp: number, signature: string }`
84
+ * where `timestamp` is **epoch milliseconds** and `signature` is a hex-encoded
85
+ * Ed25519 signature over the RFC 8785 (JCS) canonical form of
86
+ * `{records, timestamp}` — see {@link canonicalize}. A publisher that signs
87
+ * `JSON.stringify` output instead will not verify here, and that is the point:
88
+ * `JSON.stringify` bytes depend on the key order of whatever object the
89
+ * publisher happened to build, so the same logical feed could be signed and then
90
+ * fail to verify.
91
+ *
92
+ * - Without a configured `feedPublicKey`, remote feeds are refused.
93
+ * - Signature failure logs a warning and preserves the built-in floor.
94
+ * - A missing/non-integer `timestamp`, a snapshot older than `maxAgeMs`, or one
95
+ * dated further ahead than {@link FEED_CLOCK_SKEW_MS} is refused — a replayed
96
+ * old snapshot would otherwise erase every newer threat record.
97
+ * - Network/parse errors degrade silently — built-ins remain.
98
+ */
99
+ load(feedUrl?: string): Promise<void>;
100
+ /**
101
+ * Match a server — and its advertised tool definitions — against every record.
102
+ * One finding per matched record per surface.
103
+ *
104
+ * Two surfaces, because a threat record about an MCP server is not only a
105
+ * statement about the config the user wrote:
106
+ *
107
+ * - **Server identity** (`id`, `name`, `url`, `command`, `args`): the local
108
+ * config. This is the only surface a pattern matches when its `scope` is
109
+ * `"server"`.
110
+ * - **Tool definitions** (`name`, `description`, `inputSchema`): attacker-
111
+ * controlled text the *server* chose, which is where a poisoned tool actually
112
+ * lives. Matched for records scoped `"any"` (the default) or `"tool"`.
113
+ *
114
+ * Findings from the tool surface carry `tool`, so `Warden.partitionTools()` can
115
+ * quarantine the one tool instead of attributing the match to the whole
116
+ * connection. The connection is still blocked when the severity reaches
117
+ * `policy.blockAtSeverity` — per-tool attribution narrows the *blame*, it does
118
+ * not soften the policy.
119
+ */
120
+ match(server: McpServerRef, tools?: ToolDef[]): WardenFinding[];
121
+ }
122
+ /**
123
+ * Gate wrapper around a ThreatFeed. Any match is disqualifying for the score; a
124
+ * critical-severity match against the *server's identity* short-circuits the chain
125
+ * (fatal), because nothing about that connection is salvageable.
126
+ *
127
+ * A critical match against a single tool definition is deliberately not fatal: it
128
+ * still scores 0 and still blocks under the default `blockAtSeverity`, but letting
129
+ * the remaining gates run means the verdict names every problem and every affected
130
+ * tool instead of only the first one found.
131
+ */
132
+ export declare class ThreatGate implements WardenGate {
133
+ private readonly feed;
134
+ readonly name = "threat-feed";
135
+ constructor(feed: ThreatFeed);
136
+ evaluate(input: WardenGateInput): Promise<WardenGateResult>;
137
+ }
138
+ //# sourceMappingURL=threat-feed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"threat-feed.d.ts","sourceRoot":"","sources":["../src/threat-feed.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,YAAY,EAEZ,YAAY,EAEZ,OAAO,EACP,aAAa,EACb,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,uBAAuB,QAAsB,CAAC;AAE3D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,QAAgB,CAAC;AA2HhD,MAAM,WAAW,iBAAiB;IAChC,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,YAAY,CAAC;IACnB,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAcD,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAAgC;IAC/C,OAAO,CAAC,QAAQ,CAA2C;IAC3D,OAAO,CAAC,aAAa,CAAC,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAe;gBAExB,IAAI,CAAC,EAAE,iBAAiB;IAUpC,0EAA0E;IAC1E,IAAI,QAAQ,IAAI,YAAY,EAAE,CAE7B;IAED,GAAG,IAAI,YAAY,EAAE;IAIrB,qFAAqF;IACrF,OAAO,CAAC,UAAU;IAKlB;;;;;;;;;;;;;;;;;;OAkBG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA8I3C;;;;;;;;;;;;;;;;;;;OAmBG;IACH,KAAK,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,GAAE,OAAO,EAAO,GAAG,aAAa,EAAE;CAmDpE;AAED;;;;;;;;;GASG;AACH,qBAAa,UAAW,YAAW,UAAU;IAG/B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAFjC,QAAQ,CAAC,IAAI,iBAAiB;gBAED,IAAI,EAAE,UAAU;IAEvC,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAMlE"}