@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,227 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalize, CanonicalizationError } from "./jcs.js";
3
+ import { displaySafe } from "./sanitize.js";
4
+ /**
5
+ * Tool-definition pinning + drift detection ("rug-pull" defence).
6
+ *
7
+ * A server can advertise benign tools at approval time and silently swap in a
8
+ * poisoned definition later. We hash the canonical tool-def set on approval and
9
+ * compare on every subsequent connection: a changed hash means the contract the
10
+ * user approved no longer holds, so (under policy.pinToolDefs) we block and force
11
+ * re-approval. First-contact servers are flagged UNPINNED so the chain knows the
12
+ * pin is established only when the user approves.
13
+ */
14
+ export class PinningGate {
15
+ store;
16
+ name = "pinning";
17
+ constructor(store) {
18
+ this.store = store;
19
+ }
20
+ async evaluate(input) {
21
+ const pin = await this.store.getPin(input.server.id);
22
+ let hash;
23
+ try {
24
+ hash = canonicalToolsHash(input.tools);
25
+ }
26
+ catch (err) {
27
+ if (!(err instanceof CanonicalizationError))
28
+ throw err;
29
+ return this.uncanonical(input, pin, err);
30
+ }
31
+ const identityDrift = this.identityDrift(input, pin);
32
+ if (!pin) {
33
+ const finding = {
34
+ gate: this.name,
35
+ severity: "info",
36
+ code: "TOOL_DEF_UNPINNED",
37
+ message: `Server "${input.server.id}" has no pinned tool-def snapshot yet; it will be pinned on approval.`,
38
+ // Report-only by necessity, not by preference: a server at first contact
39
+ // cannot be anything but unpinned, and Warden.approve() runs only after
40
+ // vet() passes. Letting this finding block — which it did at
41
+ // blockAtSeverity "info" — makes first contact impossible for every
42
+ // server, so no pin can ever be created.
43
+ advisory: true,
44
+ };
45
+ // Neutral-to-good: unpinned isn't unsafe, it's just unestablished.
46
+ return { findings: [finding], score: 0.9 };
47
+ }
48
+ const findings = [];
49
+ if (pin.toolsHash !== hash) {
50
+ findings.push({
51
+ gate: this.name,
52
+ severity: "high",
53
+ code: "TOOL_DEF_DRIFT",
54
+ message: `Tool definitions for "${displaySafe(input.server.id)}" changed since approval ` +
55
+ `(pinned ${short(pin.toolsHash)} → now ${short(hash)}). Possible rug-pull; re-approval required.`,
56
+ });
57
+ }
58
+ // Both are reported when both changed: "the tools moved AND so did the
59
+ // program serving them" is a different picture from either one alone, and
60
+ // returning on the first one found used to hide the second.
61
+ if (identityDrift)
62
+ findings.push(identityDrift);
63
+ if (findings.length > 0) {
64
+ return { findings, score: 0, fatal: input.policy.pinToolDefs === true };
65
+ }
66
+ return { findings: [], score: 1 };
67
+ }
68
+ /**
69
+ * Has the program behind this server changed since approval?
70
+ *
71
+ * Silent when the pin predates {@link PinnedServer.identityHash}: an absent
72
+ * value is "not recorded", never "changed". The next `approve()` records it.
73
+ */
74
+ identityDrift(input, pin) {
75
+ if (!pin?.identityHash)
76
+ return undefined;
77
+ const now = serverIdentityHash(input.server);
78
+ if (now === pin.identityHash)
79
+ return undefined;
80
+ return {
81
+ gate: this.name,
82
+ severity: "high",
83
+ code: "SERVER_IDENTITY_DRIFT",
84
+ message: `The launch identity of "${displaySafe(input.server.id)}" changed since approval ` +
85
+ `(pinned ${short(pin.identityHash)} → now ${short(now)}): transport, command, args, url or name ` +
86
+ `is not what was approved. The advertised tools may be identical while the program serving them is not.`,
87
+ };
88
+ }
89
+ /**
90
+ * The tool-def set has no canonical form (see {@link canonicalToolsHash}), so no
91
+ * hash can be produced for it. What that means depends on whether a pin exists:
92
+ *
93
+ * - **No pin yet** — nothing is being contradicted; the pin simply cannot be
94
+ * established, which is a `medium` warning and not evidence of an attack.
95
+ * - **Pin exists** — the snapshot the user approved can no longer be re-verified,
96
+ * which is indistinguishable from drift and is treated as drift. Otherwise a
97
+ * server could disarm the rug-pull defence at will by adding one fractional
98
+ * number to a schema.
99
+ */
100
+ uncanonical(input, pin, err) {
101
+ if (!pin) {
102
+ return {
103
+ findings: [
104
+ {
105
+ gate: this.name,
106
+ severity: "medium",
107
+ code: "TOOL_DEF_UNCANONICAL",
108
+ message: `Tool definitions for "${displaySafe(input.server.id)}" have no canonical form (${err.message}), ` +
109
+ `so no reproducible pin can be taken — drift detection is unavailable for this server.`,
110
+ },
111
+ ],
112
+ score: 0.5,
113
+ };
114
+ }
115
+ return {
116
+ findings: [
117
+ {
118
+ gate: this.name,
119
+ severity: "high",
120
+ code: "TOOL_DEF_UNCANONICAL",
121
+ message: `Tool definitions for "${displaySafe(input.server.id)}" have no canonical form (${err.message}), so the pinned ` +
122
+ `snapshot ${short(pin.toolsHash)} cannot be re-verified. Treated as drift; re-approval required.`,
123
+ },
124
+ ],
125
+ score: 0,
126
+ fatal: input.policy.pinToolDefs === true,
127
+ };
128
+ }
129
+ /**
130
+ * Persist the current tool-def set as the trusted snapshot for this server.
131
+ * Called by Warden.approve() once a user has accepted the connection.
132
+ *
133
+ * Throws {@link CanonicalizationError} when the set has no canonical form; the
134
+ * caller (`McpHost.connect`) already reports a pin failure as a degraded
135
+ * rug-pull defence rather than failing the connection.
136
+ */
137
+ async pin(server, tools) {
138
+ const pinned = {
139
+ serverId: server.id,
140
+ toolsHash: canonicalToolsHash(tools),
141
+ approvedAt: new Date().toISOString(),
142
+ toolNames: [...tools.map((t) => t.name)].sort(compareCodeUnits),
143
+ identityHash: serverIdentityHash(server),
144
+ };
145
+ await this.store.putPin(pinned);
146
+ }
147
+ }
148
+ /**
149
+ * Marker used where a tool-def hash is *recorded* rather than compared, and the set
150
+ * turned out to have no canonical form. It can never collide with a sha256 hex
151
+ * digest, so anything that later compares it simply reports a mismatch.
152
+ */
153
+ export const UNCANONICAL_TOOLS_HASH = "uncanonical:non-canonical-tool-defs";
154
+ /**
155
+ * sha256 over the canonical tool-def set: tools sorted by name, each reduced to
156
+ * the security-relevant fields (name, description, schema), serialised with
157
+ * {@link canonicalize} — RFC 8785 (JCS) as profiled in `awr/SPEC.md` §4.
158
+ *
159
+ * This digest is quoted in receipts and re-checked elsewhere (`argus verify`, the
160
+ * sealed mandate), so it has to be reproducible by an implementation that is not
161
+ * this one. Two consequences:
162
+ *
163
+ * - **Ordering is by UTF-16 code unit, never `localeCompare`.** `localeCompare`
164
+ * depends on the host locale and ICU version — `["a", "B"]` sorts one way under
165
+ * `en-US` and another under the C locale — so a digest built on it is not even
166
+ * stable across two machines running this same code, let alone across languages.
167
+ * JavaScript's `<`/`>` on strings compare UTF-16 code units, which is exactly
168
+ * RFC 8785 §3.2.3's rule.
169
+ * - **Non-integer numbers are refused** (`AWR-CANON-001`, SPEC §4.3) rather than
170
+ * serialised. Whether `1` is an integer or a double is a language accident that
171
+ * silently changes the bytes, so a schema carrying e.g. `"multipleOf": 0.01` has
172
+ * no canonical form here and this function throws
173
+ * {@link CanonicalizationError}. Callers must handle that — refusing to emit a
174
+ * digest is honest; emitting one nobody else can reproduce is not.
175
+ */
176
+ export function canonicalToolsHash(tools) {
177
+ const canonical = [...tools]
178
+ .sort((a, b) => compareCodeUnits(a.name, b.name))
179
+ .map((t) => ({
180
+ name: t.name,
181
+ description: t.description ?? "",
182
+ inputSchema: t.inputSchema ?? {},
183
+ }));
184
+ return createHash("sha256").update(canonicalize(canonical), "utf8").digest("hex");
185
+ }
186
+ /**
187
+ * Like {@link canonicalToolsHash} but total: returns `undefined` instead of
188
+ * throwing, for call sites that record a hash rather than enforce one.
189
+ */
190
+ export function tryCanonicalToolsHash(tools) {
191
+ try {
192
+ return canonicalToolsHash(tools);
193
+ }
194
+ catch (err) {
195
+ if (err instanceof CanonicalizationError)
196
+ return undefined;
197
+ throw err;
198
+ }
199
+ }
200
+ /**
201
+ * sha256 over the server's launch identity: transport, command, args, url, name.
202
+ *
203
+ * Deliberately NOT the whole `McpServerRef`: `catalog` is provenance rather than
204
+ * identity (the origin gate is what has an opinion about it), and `env` holds
205
+ * secrets that must not be hashed into a value stored on disk next to the pin.
206
+ *
207
+ * Missing fields serialize as empty rather than being omitted, so a server that
208
+ * gains a `url` reads as a change instead of hashing the same as before.
209
+ */
210
+ export function serverIdentityHash(server) {
211
+ const identity = {
212
+ transport: server.transport,
213
+ command: server.command ?? "",
214
+ args: server.args ?? [],
215
+ url: server.url ?? "",
216
+ name: server.name,
217
+ };
218
+ return createHash("sha256").update(canonicalize(identity), "utf8").digest("hex");
219
+ }
220
+ /** RFC 8785 §3.2.3 ordering: arrays of UTF-16 code units as unsigned integers. */
221
+ function compareCodeUnits(a, b) {
222
+ return a < b ? -1 : a > b ? 1 : 0;
223
+ }
224
+ function short(hash) {
225
+ return hash.slice(0, 12);
226
+ }
227
+ //# sourceMappingURL=pinning.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pinning.js","sourceRoot":"","sources":["../src/pinning.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { ToolDef, WardenPolicy } from "./types.js";
2
+ /**
3
+ * Runtime policy layer for tool execution.
4
+ *
5
+ * NOTE: this is the JS-level policy layer, not a true sandbox. It classifies
6
+ * tools, gates sensitive calls behind explicit approval, and watches egress for
7
+ * a tool trying to phone home. OS-level process isolation
8
+ * (seccomp / Landlock on Linux, sandbox-exec on macOS) is the v2 hardening that
9
+ * confines the actual child process; until then these helpers are the boundary
10
+ * the agent loop enforces in-process.
11
+ */
12
+ /**
13
+ * True if a tool name matches any sensitive pattern in the policy. Patterns use
14
+ * `*` as a wildcard and match case-insensitively (e.g. "*delete*", "*transfer*").
15
+ * Sensitive tools are still allowed to be advertised — they just require explicit
16
+ * per-call approval before WARDEN lets them run.
17
+ */
18
+ export declare function isSensitiveTool(toolName: string, policy: WardenPolicy): boolean;
19
+ /** Split a server's advertised tools into sensitive vs. safe by policy. */
20
+ export declare function classifyTools(tools: ToolDef[], policy: WardenPolicy): {
21
+ sensitive: string[];
22
+ safe: string[];
23
+ };
24
+ /**
25
+ * Outbound-request allowlist. Wrap any network egress a tool performs in
26
+ * `check()` — a tool that tries to reach a host outside the allowlist is the
27
+ * classic "phone home" exfiltration tell. Hostnames are matched case-insensitively
28
+ * and a leading "*." entry matches subdomains (e.g. "*.example.com").
29
+ */
30
+ export declare class EgressGuard {
31
+ private readonly allow;
32
+ constructor(allowlist: string[]);
33
+ check(url: string): {
34
+ allowed: boolean;
35
+ reason?: string;
36
+ };
37
+ }
38
+ //# sourceMappingURL=sandbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAExD;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAG/E;AAED,2EAA2E;AAC3E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EAAE,EAChB,MAAM,EAAE,YAAY,GACnB;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAQzC;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;gBAErB,SAAS,EAAE,MAAM,EAAE;IAI/B,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;CAmB1D"}
@@ -0,0 +1,79 @@
1
+ import { wildcardMatch } from "./glob.js";
2
+ /**
3
+ * Runtime policy layer for tool execution.
4
+ *
5
+ * NOTE: this is the JS-level policy layer, not a true sandbox. It classifies
6
+ * tools, gates sensitive calls behind explicit approval, and watches egress for
7
+ * a tool trying to phone home. OS-level process isolation
8
+ * (seccomp / Landlock on Linux, sandbox-exec on macOS) is the v2 hardening that
9
+ * confines the actual child process; until then these helpers are the boundary
10
+ * the agent loop enforces in-process.
11
+ */
12
+ /**
13
+ * True if a tool name matches any sensitive pattern in the policy. Patterns use
14
+ * `*` as a wildcard and match case-insensitively (e.g. "*delete*", "*transfer*").
15
+ * Sensitive tools are still allowed to be advertised — they just require explicit
16
+ * per-call approval before WARDEN lets them run.
17
+ */
18
+ export function isSensitiveTool(toolName, policy) {
19
+ const name = toolName.toLowerCase();
20
+ return policy.sensitiveToolPatterns.some((pat) => globMatch(pat, name));
21
+ }
22
+ /** Split a server's advertised tools into sensitive vs. safe by policy. */
23
+ export function classifyTools(tools, policy) {
24
+ const sensitive = [];
25
+ const safe = [];
26
+ for (const tool of tools) {
27
+ if (isSensitiveTool(tool.name, policy))
28
+ sensitive.push(tool.name);
29
+ else
30
+ safe.push(tool.name);
31
+ }
32
+ return { sensitive, safe };
33
+ }
34
+ /**
35
+ * Outbound-request allowlist. Wrap any network egress a tool performs in
36
+ * `check()` — a tool that tries to reach a host outside the allowlist is the
37
+ * classic "phone home" exfiltration tell. Hostnames are matched case-insensitively
38
+ * and a leading "*." entry matches subdomains (e.g. "*.example.com").
39
+ */
40
+ export class EgressGuard {
41
+ allow;
42
+ constructor(allowlist) {
43
+ this.allow = allowlist.map((h) => h.trim().toLowerCase()).filter(Boolean);
44
+ }
45
+ check(url) {
46
+ let host;
47
+ try {
48
+ host = new URL(url).hostname.toLowerCase();
49
+ }
50
+ catch {
51
+ return { allowed: false, reason: `Unparseable URL: ${url}` };
52
+ }
53
+ if (this.allow.length === 0) {
54
+ return { allowed: false, reason: `Egress blocked (empty allowlist): ${host}` };
55
+ }
56
+ for (const entry of this.allow) {
57
+ if (entry === host)
58
+ return { allowed: true };
59
+ if (entry.startsWith("*.")) {
60
+ const suffix = entry.slice(1); // ".example.com"
61
+ if (host.endsWith(suffix) && host.length > suffix.length)
62
+ return { allowed: true };
63
+ }
64
+ }
65
+ return { allowed: false, reason: `Host not in egress allowlist: ${host}` };
66
+ }
67
+ }
68
+ /**
69
+ * `*`-glob match (case-insensitive). Inputs are matched as whole strings.
70
+ *
71
+ * The pattern comes from the operator's policy, the value from the server being
72
+ * inspected. Compiling `*` into `.*` and running a regex made that pair a denial
73
+ * of service: an operator pattern with a dozen wildcards plus a 200-character
74
+ * tool name took 89 SECONDS to answer "not sensitive". See ./glob.ts.
75
+ */
76
+ function globMatch(pattern, value) {
77
+ return wildcardMatch(pattern.toLowerCase(), value);
78
+ }
79
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAG1C;;;;;;;;;GASG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,MAAoB;IACpE,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACpC,OAAO,MAAM,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;AAC1E,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,aAAa,CAC3B,KAAgB,EAChB,MAAoB;IAEpB,MAAM,SAAS,GAAa,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;YAAE,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;;YAC7D,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IACL,KAAK,CAAW;IAEjC,YAAY,SAAmB;QAC7B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,oBAAoB,GAAG,EAAE,EAAE,CAAC;QAC/D,CAAC;QACD,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,qCAAqC,IAAI,EAAE,EAAE,CAAC;QACjF,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC/B,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YAC7C,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,iBAAiB;gBAChD,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM;oBAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;YACrF,CAAC;QACH,CAAC;QACD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,iCAAiC,IAAI,EAAE,EAAE,CAAC;IAC7E,CAAC;CACF;AAED;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,OAAe,EAAE,KAAa;IAC/C,OAAO,aAAa,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;AACrD,CAAC"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Make untrusted text safe to *display*.
3
+ *
4
+ * Every finding WARDEN emits quotes something the other side chose: a tool name,
5
+ * a server id, a catalog name, the `reason` string out of a signed threat feed.
6
+ * Those strings are then printed to a terminal by the host CLI, rendered in a
7
+ * dashboard, and stored in receipts. Interpolating them raw makes the firewall
8
+ * itself the delivery vehicle for a terminal-control payload:
9
+ *
10
+ * tool name: <ESC>[2K<ESC>[1A + "overwritten ~/.ssh"
11
+ * finding: Tool "<ESC>[2K<ESC>[1Aoverwritten ~/.ssh": Server references ...
12
+ *
13
+ * Printed to a TTY that erases the current line and moves the cursor up, so the
14
+ * BLOCK line WARDEN had just written is overwritten by text the attacker chose.
15
+ * The same trick with zero-width and bidi characters makes a name read as
16
+ * something it is not.
17
+ *
18
+ * So control characters and invisible characters are replaced with a VISIBLE
19
+ * escape rather than stripped. Stripping would hide the very thing worth seeing:
20
+ * a name with an embedded U+202E should look suspicious in the report, not clean.
21
+ * Length is capped, because a finding message is a line of output, not somewhere
22
+ * a hostile server gets to paste a megabyte.
23
+ *
24
+ * This is display hygiene, not a security boundary. `WardenFinding.tool` keeps
25
+ * the RAW advertised name, because that is the key a host uses to filter its own
26
+ * tool list and a sanitized key would match nothing.
27
+ */
28
+ /** Default cap for an interpolated fragment: long enough to be useful in a log line. */
29
+ export declare const DEFAULT_DISPLAY_MAX = 200;
30
+ /**
31
+ * Render `value` as a single-line, control-character-free fragment, escaping what
32
+ * cannot be shown and truncating past `max`.
33
+ *
34
+ * Exported so a host can apply it to the raw names it gets back in
35
+ * `allowedTools` / `blockedTools`, which are deliberately not sanitized.
36
+ */
37
+ export declare function displaySafe(value: unknown, max?: number): string;
38
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAUH,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,GAAE,MAA4B,GAAG,MAAM,CASrF"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Make untrusted text safe to *display*.
3
+ *
4
+ * Every finding WARDEN emits quotes something the other side chose: a tool name,
5
+ * a server id, a catalog name, the `reason` string out of a signed threat feed.
6
+ * Those strings are then printed to a terminal by the host CLI, rendered in a
7
+ * dashboard, and stored in receipts. Interpolating them raw makes the firewall
8
+ * itself the delivery vehicle for a terminal-control payload:
9
+ *
10
+ * tool name: <ESC>[2K<ESC>[1A + "overwritten ~/.ssh"
11
+ * finding: Tool "<ESC>[2K<ESC>[1Aoverwritten ~/.ssh": Server references ...
12
+ *
13
+ * Printed to a TTY that erases the current line and moves the cursor up, so the
14
+ * BLOCK line WARDEN had just written is overwritten by text the attacker chose.
15
+ * The same trick with zero-width and bidi characters makes a name read as
16
+ * something it is not.
17
+ *
18
+ * So control characters and invisible characters are replaced with a VISIBLE
19
+ * escape rather than stripped. Stripping would hide the very thing worth seeing:
20
+ * a name with an embedded U+202E should look suspicious in the report, not clean.
21
+ * Length is capped, because a finding message is a line of output, not somewhere
22
+ * a hostile server gets to paste a megabyte.
23
+ *
24
+ * This is display hygiene, not a security boundary. `WardenFinding.tool` keeps
25
+ * the RAW advertised name, because that is the key a host uses to filter its own
26
+ * tool list and a sanitized key would match nothing.
27
+ */
28
+ /** Zero-width and bidi controls: invisible, therefore always escaped. */
29
+ const INVISIBLE = "\\u200B-\\u200F\\u202A-\\u202E\\u2060\\u2066-\\u2069\\uFEFF";
30
+ /** C0 controls, DEL, and C1 - the terminal-control range. */
31
+ const CONTROL = "\\u0000-\\u001F\\u007F-\\u009F";
32
+ const UNSAFE = new RegExp(`[${CONTROL}${INVISIBLE}]`, "g");
33
+ /** Default cap for an interpolated fragment: long enough to be useful in a log line. */
34
+ export const DEFAULT_DISPLAY_MAX = 200;
35
+ /**
36
+ * Render `value` as a single-line, control-character-free fragment, escaping what
37
+ * cannot be shown and truncating past `max`.
38
+ *
39
+ * Exported so a host can apply it to the raw names it gets back in
40
+ * `allowedTools` / `blockedTools`, which are deliberately not sanitized.
41
+ */
42
+ export function displaySafe(value, max = DEFAULT_DISPLAY_MAX) {
43
+ const text = typeof value === "string" ? value : String(value ?? "");
44
+ const escaped = text.replace(UNSAFE, (ch) => {
45
+ const code = ch.codePointAt(0) ?? 0;
46
+ return `\\u${code.toString(16).toUpperCase().padStart(4, "0")}`;
47
+ });
48
+ if (escaped.length <= max)
49
+ return escaped;
50
+ // Report the real length: "this name is 4 MB long" is itself worth knowing.
51
+ return `${escaped.slice(0, max)}... [truncated, ${text.length} chars]`;
52
+ }
53
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,yEAAyE;AACzE,MAAM,SAAS,GAAG,6DAA6D,CAAC;AAEhF,6DAA6D;AAC7D,MAAM,OAAO,GAAG,gCAAgC,CAAC;AAEjD,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,SAAS,GAAG,EAAE,GAAG,CAAC,CAAC;AAE3D,wFAAwF;AACxF,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAEvC;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,MAAc,mBAAmB;IAC3E,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IACrE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE;QAC1C,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpC,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAClE,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,CAAC,MAAM,IAAI,GAAG;QAAE,OAAO,OAAO,CAAC;IAC1C,4EAA4E;IAC5E,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,mBAAmB,IAAI,CAAC,MAAM,SAAS,CAAC;AACzE,CAAC"}
@@ -0,0 +1,110 @@
1
+ import type { RulesetRef, Severity, WardenGate, WardenGateInput, WardenGateResult, WardenLogger } from "./types.js";
2
+ /**
3
+ * Static tool-definition scanner.
4
+ *
5
+ * The MCP tool *description* and *input schema* are attacker-controlled text the
6
+ * model reads as trusted instructions — this is the tool-poisoning / prompt-
7
+ * injection surface. We scan that text (never the live tool output) for known
8
+ * signatures before a single token of it reaches the model. Cheap, deterministic,
9
+ * and runs first so later gates can reason about its findings.
10
+ *
11
+ * Rules are TIERED, and the tier is what decides blocking — not severity:
12
+ *
13
+ * block — no plausible benign reading in a tool definition. Blocks at the
14
+ * policy threshold like any other finding.
15
+ * advise — legitimately appears in honest tool definitions. Reported, never
16
+ * blocks, never reduces the score, at any `blockAtSeverity`.
17
+ *
18
+ * Ruleset v1 had one tier, so `api_key` in a schema carried the same weight as
19
+ * "ignore all previous instructions". A GitHub-style server whose `create_issue`
20
+ * takes an `api_key` and mentions a personal access token scored 0.40 and had the
21
+ * tool blocked under the default policy — i.e. most real MCP servers were refused,
22
+ * which is how a scanner gets switched off entirely.
23
+ */
24
+ type Tier = "block" | "advise";
25
+ /**
26
+ * Which part of a tool definition a rule is meaningful against.
27
+ *
28
+ * `name` is an IDENTIFIER, not prose, and that difference decides the table
29
+ * below. A rule that keys on a noun - `api_key`, `private_key`, `.env` - matches
30
+ * ordinary identifiers (`sign_with_private_key` is a plausible wallet tool), so
31
+ * running it over names would refuse honest servers on their naming convention:
32
+ * exactly the ruleset v1 mistake, one surface over. A rule that keys on a PHRASE
33
+ * needs whitespace and cannot match `snake_case` at all, and the two hidden-payload
34
+ * rules are about characters that are never legitimate in a name.
35
+ *
36
+ * Names were scanned by nothing at all until ruleset v3, which meant zero-width
37
+ * characters and a base64 blob could sit in the one field that reaches the model
38
+ * first and WARDEN reported nothing.
39
+ */
40
+ type Surface = "name" | "description" | "inputSchema";
41
+ /**
42
+ * Ruleset version. Bump on ANY change to the table above — a scan result is only
43
+ * comparable to another scan made under the same version and digest.
44
+ *
45
+ * 1 — single tier; credential parameter names blocked real servers.
46
+ * 2 — block/advise tiers; exfil "send … to" anchored on an external destination;
47
+ * TOOL_DEF_SECRET_HARVEST added so demoting the credential nouns does not
48
+ * open a hole; advisory findings no longer affect the score.
49
+ * 3 — rules gained `surfaces`, and the tool NAME became a scanned surface. Until
50
+ * v3 the name was scanned by nothing, so zero-width characters, a base64 blob
51
+ * or an injection phrase in the one field that reaches the model first went
52
+ * entirely unreported. Noun-keyed rules stay off the name on purpose: they
53
+ * match ordinary identifiers, and refusing `sign_with_private_key` would be
54
+ * the v1 calibration error committed on a new surface.
55
+ * 4 — calibrated against 1 108 live public MCP servers (docs/mcp-survey.md). v3
56
+ * blocked 50 of them and only 4 held up on review, so v4 adds context
57
+ * GUARDS — polarity, quotation, URI form, payload length, entropy, script
58
+ * adjacency — and demotes four blocking rules the survey showed were
59
+ * selecting for honest servers: the bare `exfiltrat*` noun, `system prompt`,
60
+ * `do not tell the user`, and (in severity only) the credential nouns. A
61
+ * rule's guards are part of this table and therefore of the digest.
62
+ */
63
+ export declare const STATIC_SCAN_RULESET_VERSION = "4";
64
+ export interface StaticScanRule {
65
+ code: string;
66
+ severity: Severity;
67
+ tier: Tier;
68
+ /** Which surfaces the rule is run against, so a re-run scans the same fields. */
69
+ surfaces: Surface[];
70
+ /** The regex source, so a third party can re-run the exact rule. */
71
+ source: string;
72
+ flags: string;
73
+ /**
74
+ * Which context guards this rule is subject to. Published because the regex
75
+ * alone no longer describes the rule: the same pattern with and without
76
+ * `polarity` reports different findings on the same text.
77
+ */
78
+ guards: string[];
79
+ }
80
+ export interface StaticScanRuleset extends RulesetRef {
81
+ rules: StaticScanRule[];
82
+ }
83
+ /**
84
+ * The rule table plus its digest, so a scan result stays checkable after the
85
+ * rules change.
86
+ *
87
+ * Sorted, so the digest depends on the rules and not on the order they happen to
88
+ * be declared in — and sorted by CODE-UNIT comparison, not `localeCompare`: the
89
+ * digest is a cross-machine identifier, and a locale-dependent collation would
90
+ * make the same rule table digest differently on a differently-configured host,
91
+ * which is exactly the divergence the digest exists to detect. The preimage is
92
+ * the RFC 8785 canonical form (see ./jcs.ts) so this file does not invent a
93
+ * second serialization of its own.
94
+ */
95
+ export declare function staticScanRuleset(): StaticScanRuleset;
96
+ /** Just the identity of the rule table, for embedding in a verdict. */
97
+ export declare function staticScanRulesetRef(): RulesetRef;
98
+ export declare class StaticScanGate implements WardenGate {
99
+ readonly name = "static-scan";
100
+ private readonly log;
101
+ /**
102
+ * @param log where dropped matches are reported. A guard silently discarding a
103
+ * finding is the one behaviour in this gate that cannot be seen in the
104
+ * verdict, so it is the one that most needs a debug line.
105
+ */
106
+ constructor(log?: WardenLogger);
107
+ evaluate(input: WardenGateInput): Promise<WardenGateResult>;
108
+ }
109
+ export {};
110
+ //# sourceMappingURL=static-scan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-scan.d.ts","sourceRoot":"","sources":["../src/static-scan.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EAER,UAAU,EACV,eAAe,EACf,gBAAgB,EAChB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,KAAK,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE/B;;;;;;;;;;;;;;GAcG;AACH,KAAK,OAAO,GAAG,MAAM,GAAG,aAAa,GAAG,aAAa,CAAC;AAuZtD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAmB/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,iFAAiF;IACjF,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CAerD;AAOD,uEAAuE;AACvE,wBAAgB,oBAAoB,IAAI,UAAU,CAGjD;AAED,qBAAa,cAAe,YAAW,UAAU;IAC/C,QAAQ,CAAC,IAAI,iBAAiB;IAE9B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC;;;;OAIG;gBACS,GAAG,GAAE,YAA6B;IAIxC,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAoDlE"}