@aimarket/warden 0.3.0 → 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.
package/dist/pinning.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { canonicalize, CanonicalizationError } from "./jcs.js";
3
+ import { displaySafe } from "./sanitize.js";
3
4
  /**
4
5
  * Tool-definition pinning + drift detection ("rug-pull" defence).
5
6
  *
@@ -27,6 +28,7 @@ export class PinningGate {
27
28
  throw err;
28
29
  return this.uncanonical(input, pin, err);
29
30
  }
31
+ const identityDrift = this.identityDrift(input, pin);
30
32
  if (!pin) {
31
33
  const finding = {
32
34
  gate: this.name,
@@ -43,22 +45,47 @@ export class PinningGate {
43
45
  // Neutral-to-good: unpinned isn't unsafe, it's just unestablished.
44
46
  return { findings: [finding], score: 0.9 };
45
47
  }
48
+ const findings = [];
46
49
  if (pin.toolsHash !== hash) {
47
- const finding = {
50
+ findings.push({
48
51
  gate: this.name,
49
52
  severity: "high",
50
53
  code: "TOOL_DEF_DRIFT",
51
- message: `Tool definitions for "${input.server.id}" changed since approval ` +
54
+ message: `Tool definitions for "${displaySafe(input.server.id)}" changed since approval ` +
52
55
  `(pinned ${short(pin.toolsHash)} → now ${short(hash)}). Possible rug-pull; re-approval required.`,
53
- };
54
- return {
55
- findings: [finding],
56
- score: 0,
57
- fatal: input.policy.pinToolDefs === true,
58
- };
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 };
59
65
  }
60
66
  return { findings: [], score: 1 };
61
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
+ }
62
89
  /**
63
90
  * The tool-def set has no canonical form (see {@link canonicalToolsHash}), so no
64
91
  * hash can be produced for it. What that means depends on whether a pin exists:
@@ -78,7 +105,7 @@ export class PinningGate {
78
105
  gate: this.name,
79
106
  severity: "medium",
80
107
  code: "TOOL_DEF_UNCANONICAL",
81
- message: `Tool definitions for "${input.server.id}" have no canonical form (${err.message}), ` +
108
+ message: `Tool definitions for "${displaySafe(input.server.id)}" have no canonical form (${err.message}), ` +
82
109
  `so no reproducible pin can be taken — drift detection is unavailable for this server.`,
83
110
  },
84
111
  ],
@@ -91,7 +118,7 @@ export class PinningGate {
91
118
  gate: this.name,
92
119
  severity: "high",
93
120
  code: "TOOL_DEF_UNCANONICAL",
94
- message: `Tool definitions for "${input.server.id}" have no canonical form (${err.message}), so the pinned ` +
121
+ message: `Tool definitions for "${displaySafe(input.server.id)}" have no canonical form (${err.message}), so the pinned ` +
95
122
  `snapshot ${short(pin.toolsHash)} cannot be re-verified. Treated as drift; re-approval required.`,
96
123
  },
97
124
  ],
@@ -112,7 +139,8 @@ export class PinningGate {
112
139
  serverId: server.id,
113
140
  toolsHash: canonicalToolsHash(tools),
114
141
  approvedAt: new Date().toISOString(),
115
- toolNames: [...tools.map((t) => t.name)].sort(),
142
+ toolNames: [...tools.map((t) => t.name)].sort(compareCodeUnits),
143
+ identityHash: serverIdentityHash(server),
116
144
  };
117
145
  await this.store.putPin(pinned);
118
146
  }
@@ -169,6 +197,26 @@ export function tryCanonicalToolsHash(tools) {
169
197
  throw err;
170
198
  }
171
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
+ }
172
220
  /** RFC 8785 §3.2.3 ordering: arrays of UTF-16 code units as unsigned integers. */
173
221
  function compareCodeUnits(a, b) {
174
222
  return a < b ? -1 : a > b ? 1 : 0;
@@ -1 +1 @@
1
- {"version":3,"file":"pinning.js","sourceRoot":"","sources":["../src/pinning.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pinning.js","sourceRoot":"","sources":["../src/pinning.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAAA,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"}
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"}
package/dist/sandbox.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { wildcardMatch } from "./glob.js";
1
2
  /**
2
3
  * Runtime policy layer for tool execution.
3
4
  *
@@ -64,13 +65,15 @@ export class EgressGuard {
64
65
  return { allowed: false, reason: `Host not in egress allowlist: ${host}` };
65
66
  }
66
67
  }
67
- /** `*`-glob match (case-insensitive). Inputs are matched as whole strings. */
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
+ */
68
76
  function globMatch(pattern, value) {
69
- const re = pattern
70
- .toLowerCase()
71
- .split("*")
72
- .map((seg) => seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
73
- .join(".*");
74
- return new RegExp(`^${re}$`, "i").test(value);
77
+ return wildcardMatch(pattern.toLowerCase(), value);
75
78
  }
76
79
  //# sourceMappingURL=sandbox.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"AAEA;;;;;;;;;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,8EAA8E;AAC9E,SAAS,SAAS,CAAC,OAAe,EAAE,KAAa;IAC/C,MAAM,EAAE,GAAG,OAAO;SACf,WAAW,EAAE;SACb,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;SACxD,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,OAAO,IAAI,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAChD,CAAC"}
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"}
@@ -1,4 +1,4 @@
1
- import type { RulesetRef, Severity, WardenGate, WardenGateInput, WardenGateResult } from "./types.js";
1
+ import type { RulesetRef, Severity, WardenGate, WardenGateInput, WardenGateResult, WardenLogger } from "./types.js";
2
2
  /**
3
3
  * Static tool-definition scanner.
4
4
  *
@@ -22,6 +22,22 @@ import type { RulesetRef, Severity, WardenGate, WardenGateInput, WardenGateResul
22
22
  * which is how a scanner gets switched off entirely.
23
23
  */
24
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";
25
41
  /**
26
42
  * Ruleset version. Bump on ANY change to the table above — a scan result is only
27
43
  * comparable to another scan made under the same version and digest.
@@ -30,15 +46,36 @@ type Tier = "block" | "advise";
30
46
  * 2 — block/advise tiers; exfil "send … to" anchored on an external destination;
31
47
  * TOOL_DEF_SECRET_HARVEST added so demoting the credential nouns does not
32
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.
33
62
  */
34
- export declare const STATIC_SCAN_RULESET_VERSION = "2";
63
+ export declare const STATIC_SCAN_RULESET_VERSION = "4";
35
64
  export interface StaticScanRule {
36
65
  code: string;
37
66
  severity: Severity;
38
67
  tier: Tier;
68
+ /** Which surfaces the rule is run against, so a re-run scans the same fields. */
69
+ surfaces: Surface[];
39
70
  /** The regex source, so a third party can re-run the exact rule. */
40
71
  source: string;
41
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[];
42
79
  }
43
80
  export interface StaticScanRuleset extends RulesetRef {
44
81
  rules: StaticScanRule[];
@@ -60,6 +97,13 @@ export declare function staticScanRuleset(): StaticScanRuleset;
60
97
  export declare function staticScanRulesetRef(): RulesetRef;
61
98
  export declare class StaticScanGate implements WardenGate {
62
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);
63
107
  evaluate(input: WardenGateInput): Promise<WardenGateResult>;
64
108
  }
65
109
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"static-scan.d.ts","sourceRoot":"","sources":["../src/static-scan.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EACV,QAAQ,EAER,UAAU,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,KAAK,IAAI,GAAG,OAAO,GAAG,QAAQ,CAAC;AA2G/B;;;;;;;;GAQG;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,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAkB,SAAQ,UAAU;IACnD,KAAK,EAAE,cAAc,EAAE,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,IAAI,iBAAiB,CAarD;AAOD,uEAAuE;AACvE,wBAAgB,oBAAoB,IAAI,UAAU,CAGjD;AAED,qBAAa,cAAe,YAAW,UAAU;IAC/C,QAAQ,CAAC,IAAI,iBAAiB;IAExB,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CA+BlE"}
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"}