@aimarket/warden 0.0.1 → 0.3.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.
@@ -0,0 +1,179 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalize, CanonicalizationError } from "./jcs.js";
3
+ /**
4
+ * Tool-definition pinning + drift detection ("rug-pull" defence).
5
+ *
6
+ * A server can advertise benign tools at approval time and silently swap in a
7
+ * poisoned definition later. We hash the canonical tool-def set on approval and
8
+ * compare on every subsequent connection: a changed hash means the contract the
9
+ * user approved no longer holds, so (under policy.pinToolDefs) we block and force
10
+ * re-approval. First-contact servers are flagged UNPINNED so the chain knows the
11
+ * pin is established only when the user approves.
12
+ */
13
+ export class PinningGate {
14
+ store;
15
+ name = "pinning";
16
+ constructor(store) {
17
+ this.store = store;
18
+ }
19
+ async evaluate(input) {
20
+ const pin = await this.store.getPin(input.server.id);
21
+ let hash;
22
+ try {
23
+ hash = canonicalToolsHash(input.tools);
24
+ }
25
+ catch (err) {
26
+ if (!(err instanceof CanonicalizationError))
27
+ throw err;
28
+ return this.uncanonical(input, pin, err);
29
+ }
30
+ if (!pin) {
31
+ const finding = {
32
+ gate: this.name,
33
+ severity: "info",
34
+ code: "TOOL_DEF_UNPINNED",
35
+ message: `Server "${input.server.id}" has no pinned tool-def snapshot yet; it will be pinned on approval.`,
36
+ // Report-only by necessity, not by preference: a server at first contact
37
+ // cannot be anything but unpinned, and Warden.approve() runs only after
38
+ // vet() passes. Letting this finding block — which it did at
39
+ // blockAtSeverity "info" — makes first contact impossible for every
40
+ // server, so no pin can ever be created.
41
+ advisory: true,
42
+ };
43
+ // Neutral-to-good: unpinned isn't unsafe, it's just unestablished.
44
+ return { findings: [finding], score: 0.9 };
45
+ }
46
+ if (pin.toolsHash !== hash) {
47
+ const finding = {
48
+ gate: this.name,
49
+ severity: "high",
50
+ code: "TOOL_DEF_DRIFT",
51
+ message: `Tool definitions for "${input.server.id}" changed since approval ` +
52
+ `(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
+ };
59
+ }
60
+ return { findings: [], score: 1 };
61
+ }
62
+ /**
63
+ * The tool-def set has no canonical form (see {@link canonicalToolsHash}), so no
64
+ * hash can be produced for it. What that means depends on whether a pin exists:
65
+ *
66
+ * - **No pin yet** — nothing is being contradicted; the pin simply cannot be
67
+ * established, which is a `medium` warning and not evidence of an attack.
68
+ * - **Pin exists** — the snapshot the user approved can no longer be re-verified,
69
+ * which is indistinguishable from drift and is treated as drift. Otherwise a
70
+ * server could disarm the rug-pull defence at will by adding one fractional
71
+ * number to a schema.
72
+ */
73
+ uncanonical(input, pin, err) {
74
+ if (!pin) {
75
+ return {
76
+ findings: [
77
+ {
78
+ gate: this.name,
79
+ severity: "medium",
80
+ code: "TOOL_DEF_UNCANONICAL",
81
+ message: `Tool definitions for "${input.server.id}" have no canonical form (${err.message}), ` +
82
+ `so no reproducible pin can be taken — drift detection is unavailable for this server.`,
83
+ },
84
+ ],
85
+ score: 0.5,
86
+ };
87
+ }
88
+ return {
89
+ findings: [
90
+ {
91
+ gate: this.name,
92
+ severity: "high",
93
+ code: "TOOL_DEF_UNCANONICAL",
94
+ message: `Tool definitions for "${input.server.id}" have no canonical form (${err.message}), so the pinned ` +
95
+ `snapshot ${short(pin.toolsHash)} cannot be re-verified. Treated as drift; re-approval required.`,
96
+ },
97
+ ],
98
+ score: 0,
99
+ fatal: input.policy.pinToolDefs === true,
100
+ };
101
+ }
102
+ /**
103
+ * Persist the current tool-def set as the trusted snapshot for this server.
104
+ * Called by Warden.approve() once a user has accepted the connection.
105
+ *
106
+ * Throws {@link CanonicalizationError} when the set has no canonical form; the
107
+ * caller (`McpHost.connect`) already reports a pin failure as a degraded
108
+ * rug-pull defence rather than failing the connection.
109
+ */
110
+ async pin(server, tools) {
111
+ const pinned = {
112
+ serverId: server.id,
113
+ toolsHash: canonicalToolsHash(tools),
114
+ approvedAt: new Date().toISOString(),
115
+ toolNames: [...tools.map((t) => t.name)].sort(),
116
+ };
117
+ await this.store.putPin(pinned);
118
+ }
119
+ }
120
+ /**
121
+ * Marker used where a tool-def hash is *recorded* rather than compared, and the set
122
+ * turned out to have no canonical form. It can never collide with a sha256 hex
123
+ * digest, so anything that later compares it simply reports a mismatch.
124
+ */
125
+ export const UNCANONICAL_TOOLS_HASH = "uncanonical:non-canonical-tool-defs";
126
+ /**
127
+ * sha256 over the canonical tool-def set: tools sorted by name, each reduced to
128
+ * the security-relevant fields (name, description, schema), serialised with
129
+ * {@link canonicalize} — RFC 8785 (JCS) as profiled in `awr/SPEC.md` §4.
130
+ *
131
+ * This digest is quoted in receipts and re-checked elsewhere (`argus verify`, the
132
+ * sealed mandate), so it has to be reproducible by an implementation that is not
133
+ * this one. Two consequences:
134
+ *
135
+ * - **Ordering is by UTF-16 code unit, never `localeCompare`.** `localeCompare`
136
+ * depends on the host locale and ICU version — `["a", "B"]` sorts one way under
137
+ * `en-US` and another under the C locale — so a digest built on it is not even
138
+ * stable across two machines running this same code, let alone across languages.
139
+ * JavaScript's `<`/`>` on strings compare UTF-16 code units, which is exactly
140
+ * RFC 8785 §3.2.3's rule.
141
+ * - **Non-integer numbers are refused** (`AWR-CANON-001`, SPEC §4.3) rather than
142
+ * serialised. Whether `1` is an integer or a double is a language accident that
143
+ * silently changes the bytes, so a schema carrying e.g. `"multipleOf": 0.01` has
144
+ * no canonical form here and this function throws
145
+ * {@link CanonicalizationError}. Callers must handle that — refusing to emit a
146
+ * digest is honest; emitting one nobody else can reproduce is not.
147
+ */
148
+ export function canonicalToolsHash(tools) {
149
+ const canonical = [...tools]
150
+ .sort((a, b) => compareCodeUnits(a.name, b.name))
151
+ .map((t) => ({
152
+ name: t.name,
153
+ description: t.description ?? "",
154
+ inputSchema: t.inputSchema ?? {},
155
+ }));
156
+ return createHash("sha256").update(canonicalize(canonical), "utf8").digest("hex");
157
+ }
158
+ /**
159
+ * Like {@link canonicalToolsHash} but total: returns `undefined` instead of
160
+ * throwing, for call sites that record a hash rather than enforce one.
161
+ */
162
+ export function tryCanonicalToolsHash(tools) {
163
+ try {
164
+ return canonicalToolsHash(tools);
165
+ }
166
+ catch (err) {
167
+ if (err instanceof CanonicalizationError)
168
+ return undefined;
169
+ throw err;
170
+ }
171
+ }
172
+ /** RFC 8785 §3.2.3 ordering: arrays of UTF-16 code units as unsigned integers. */
173
+ function compareCodeUnits(a, b) {
174
+ return a < b ? -1 : a > b ? 1 : 0;
175
+ }
176
+ function short(hash) {
177
+ return hash.slice(0, 12);
178
+ }
179
+ //# 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":"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"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Runtime policy layer for tool execution.
3
+ *
4
+ * NOTE: this is the JS-level policy layer, not a true sandbox. It classifies
5
+ * tools, gates sensitive calls behind explicit approval, and watches egress for
6
+ * a tool trying to phone home. OS-level process isolation
7
+ * (seccomp / Landlock on Linux, sandbox-exec on macOS) is the v2 hardening that
8
+ * confines the actual child process; until then these helpers are the boundary
9
+ * the agent loop enforces in-process.
10
+ */
11
+ /**
12
+ * True if a tool name matches any sensitive pattern in the policy. Patterns use
13
+ * `*` as a wildcard and match case-insensitively (e.g. "*delete*", "*transfer*").
14
+ * Sensitive tools are still allowed to be advertised — they just require explicit
15
+ * per-call approval before WARDEN lets them run.
16
+ */
17
+ export function isSensitiveTool(toolName, policy) {
18
+ const name = toolName.toLowerCase();
19
+ return policy.sensitiveToolPatterns.some((pat) => globMatch(pat, name));
20
+ }
21
+ /** Split a server's advertised tools into sensitive vs. safe by policy. */
22
+ export function classifyTools(tools, policy) {
23
+ const sensitive = [];
24
+ const safe = [];
25
+ for (const tool of tools) {
26
+ if (isSensitiveTool(tool.name, policy))
27
+ sensitive.push(tool.name);
28
+ else
29
+ safe.push(tool.name);
30
+ }
31
+ return { sensitive, safe };
32
+ }
33
+ /**
34
+ * Outbound-request allowlist. Wrap any network egress a tool performs in
35
+ * `check()` — a tool that tries to reach a host outside the allowlist is the
36
+ * classic "phone home" exfiltration tell. Hostnames are matched case-insensitively
37
+ * and a leading "*." entry matches subdomains (e.g. "*.example.com").
38
+ */
39
+ export class EgressGuard {
40
+ allow;
41
+ constructor(allowlist) {
42
+ this.allow = allowlist.map((h) => h.trim().toLowerCase()).filter(Boolean);
43
+ }
44
+ check(url) {
45
+ let host;
46
+ try {
47
+ host = new URL(url).hostname.toLowerCase();
48
+ }
49
+ catch {
50
+ return { allowed: false, reason: `Unparseable URL: ${url}` };
51
+ }
52
+ if (this.allow.length === 0) {
53
+ return { allowed: false, reason: `Egress blocked (empty allowlist): ${host}` };
54
+ }
55
+ for (const entry of this.allow) {
56
+ if (entry === host)
57
+ return { allowed: true };
58
+ if (entry.startsWith("*.")) {
59
+ const suffix = entry.slice(1); // ".example.com"
60
+ if (host.endsWith(suffix) && host.length > suffix.length)
61
+ return { allowed: true };
62
+ }
63
+ }
64
+ return { allowed: false, reason: `Host not in egress allowlist: ${host}` };
65
+ }
66
+ }
67
+ /** `*`-glob match (case-insensitive). Inputs are matched as whole strings. */
68
+ 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);
75
+ }
76
+ //# sourceMappingURL=sandbox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sandbox.js","sourceRoot":"","sources":["../src/sandbox.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ import type { RulesetRef, Severity, WardenGate, WardenGateInput, WardenGateResult } 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
+ * Ruleset version. Bump on ANY change to the table above — a scan result is only
27
+ * comparable to another scan made under the same version and digest.
28
+ *
29
+ * 1 — single tier; credential parameter names blocked real servers.
30
+ * 2 — block/advise tiers; exfil "send … to" anchored on an external destination;
31
+ * TOOL_DEF_SECRET_HARVEST added so demoting the credential nouns does not
32
+ * open a hole; advisory findings no longer affect the score.
33
+ */
34
+ export declare const STATIC_SCAN_RULESET_VERSION = "2";
35
+ export interface StaticScanRule {
36
+ code: string;
37
+ severity: Severity;
38
+ tier: Tier;
39
+ /** The regex source, so a third party can re-run the exact rule. */
40
+ source: string;
41
+ flags: string;
42
+ }
43
+ export interface StaticScanRuleset extends RulesetRef {
44
+ rules: StaticScanRule[];
45
+ }
46
+ /**
47
+ * The rule table plus its digest, so a scan result stays checkable after the
48
+ * rules change.
49
+ *
50
+ * Sorted, so the digest depends on the rules and not on the order they happen to
51
+ * be declared in — and sorted by CODE-UNIT comparison, not `localeCompare`: the
52
+ * digest is a cross-machine identifier, and a locale-dependent collation would
53
+ * make the same rule table digest differently on a differently-configured host,
54
+ * which is exactly the divergence the digest exists to detect. The preimage is
55
+ * the RFC 8785 canonical form (see ./jcs.ts) so this file does not invent a
56
+ * second serialization of its own.
57
+ */
58
+ export declare function staticScanRuleset(): StaticScanRuleset;
59
+ /** Just the identity of the rule table, for embedding in a verdict. */
60
+ export declare function staticScanRulesetRef(): RulesetRef;
61
+ export declare class StaticScanGate implements WardenGate {
62
+ readonly name = "static-scan";
63
+ evaluate(input: WardenGateInput): Promise<WardenGateResult>;
64
+ }
65
+ export {};
66
+ //# 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":"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"}
@@ -0,0 +1,215 @@
1
+ import { createHash } from "node:crypto";
2
+ import { canonicalize } from "./jcs.js";
3
+ // Imperative instructions aimed at the model — the classic injection tells.
4
+ const INJECTION_PATTERNS = [
5
+ { re: /\bignore\s+(?:all\s+|the\s+)?(?:previous|prior|above|preceding)\b/i, code: "TOOL_DEF_INJECTION", severity: "critical", tier: "block" },
6
+ { re: /\bdisregard\s+(?:all\s+|the\s+|any\s+)?(?:previous|prior|above|instructions?|rules?)\b/i, code: "TOOL_DEF_INJECTION", severity: "critical", tier: "block" },
7
+ { re: /\bdo\s+not\s+(?:tell|inform|mention|reveal\s+to|notify)\s+(?:the\s+)?user\b/i, code: "TOOL_DEF_INJECTION", severity: "critical", tier: "block" },
8
+ { re: /\bwithout\s+(?:telling|informing|asking)\s+(?:the\s+)?user\b/i, code: "TOOL_DEF_INJECTION", severity: "high", tier: "block" },
9
+ { re: /<\s*\/?\s*(?:system|instructions?|important)\s*>/i, code: "TOOL_DEF_INJECTION", severity: "high", tier: "block" },
10
+ // A tool DEFINITION that talks about the model's own instructions is arguing
11
+ // with the system prompt, not describing a capability. Honest servers describe
12
+ // user-supplied prompts instead. This is the blocking rule most likely to need
13
+ // a policy override — a prompt-management server may legitimately trip it.
14
+ { re: /\b(?:system\s+prompt|developer\s+(?:message|prompt))\b/i, code: "TOOL_DEF_INJECTION", severity: "high", tier: "block" },
15
+ // Ordinary English. Kept as a signal because it co-occurs with real poisoning,
16
+ // demoted because on its own it is noise: "You must supply a subject."
17
+ { re: /\byou\s+must\b/i, code: "TOOL_DEF_IMPERATIVE", severity: "low", tier: "advise" },
18
+ { re: /\binstead\s+of\b/i, code: "TOOL_DEF_IMPERATIVE", severity: "info", tier: "advise" },
19
+ ];
20
+ // Data-exfiltration phrasing.
21
+ const EXFIL_PATTERNS = [
22
+ { re: /\bexfiltrat\w*/i, code: "TOOL_DEF_EXFIL", severity: "critical", tier: "block" },
23
+ // Anchored on an EXTERNAL destination. Unanchored, this matched "Send the
24
+ // message to the recipient" in any honest email or webhook tool.
25
+ {
26
+ re: /\b(?:send|post|forward|relay)\s+(?:it|them|this|the\s+\w+|all|results?|output|data|contents?)\s+to\s+(?:https?:\/\/|\b[\w-]+(?:\.[\w-]+)*\.[a-z]{2,}\b)/i,
27
+ code: "TOOL_DEF_EXFIL",
28
+ severity: "high",
29
+ tier: "block",
30
+ },
31
+ { re: /\b(?:post|send|put)\s+to\s+https?:\/\//i, code: "TOOL_DEF_EXFIL", severity: "critical", tier: "block" },
32
+ { re: /\bupload\b[\s\S]{0,40}\bto\s+(?:https?:\/\/|[\w.-]+\.[a-z]{2,})/i, code: "TOOL_DEF_EXFIL", severity: "high", tier: "block" },
33
+ ];
34
+ // Secrets. Naming a credential PARAMETER is what normal tools do; demanding the
35
+ // material that is never a parameter is not.
36
+ //
37
+ // The discriminator is the VERB, not the noun. "Requires a personal access token
38
+ // with repo scope" describes an input. "read the user's api_key from the .env
39
+ // file" instructs the model to go and get one. Tiering on the noun alone is what
40
+ // blocked every real server, and dropping the noun rules entirely would have let
41
+ // a harvest instruction through whenever it omitted an injection phrase.
42
+ const SECRET_PATTERNS = [
43
+ {
44
+ re: /\b(?:read|extract|retrieve|fetch|obtain|dump|reveal|collect|harvest|grab|copy|print)\s[\s\S]{0,30}?\b(?:api[_\s-]?key|access[_\s-]?token|bearer\s+token|credential|password|passwd|secret|\.env\b|environment\s+variable)/i,
45
+ code: "TOOL_DEF_SECRET_HARVEST",
46
+ severity: "critical",
47
+ tier: "block",
48
+ },
49
+ { re: /\bprivate[_\s-]?key\b/i, code: "TOOL_DEF_SECRET_REQUEST", severity: "critical", tier: "block" },
50
+ { re: /\bseed[_\s-]?phrase\b|\bmnemonic\b/i, code: "TOOL_DEF_SECRET_REQUEST", severity: "critical", tier: "block" },
51
+ { re: /~\/\.ssh|\bid_rsa\b|\.ssh\/[\w.-]+/i, code: "TOOL_DEF_SECRET_REQUEST", severity: "critical", tier: "block" },
52
+ // Advisory: these are ordinary parameter names and ordinary setup prose.
53
+ { re: /\bapi[_\s-]?key\b/i, code: "TOOL_DEF_CREDENTIAL_PARAM", severity: "low", tier: "advise" },
54
+ { re: /\bcredentials?\b|\baccess[_\s-]?token\b|\bbearer\s+token\b/i, code: "TOOL_DEF_CREDENTIAL_PARAM", severity: "low", tier: "advise" },
55
+ { re: /\bsecret(?:s)?\b/i, code: "TOOL_DEF_CREDENTIAL_PARAM", severity: "low", tier: "advise" },
56
+ { re: /\bpassword\b|\bpasswd\b/i, code: "TOOL_DEF_CREDENTIAL_PARAM", severity: "medium", tier: "advise" },
57
+ { re: /(?:^|[^.\w])\.env\b|\benvironment\s+variables?\b/i, code: "TOOL_DEF_ENV_REFERENCE", severity: "medium", tier: "advise" },
58
+ ];
59
+ // Dangerous URL schemes embedded in text.
60
+ const URL_SCHEME_PATTERNS = [
61
+ { re: /\bdata:[\w/+.-]+;base64,/i, code: "TOOL_DEF_DATA_URL", severity: "high", tier: "block" },
62
+ { re: /\bjavascript:/i, code: "TOOL_DEF_DATA_URL", severity: "high", tier: "block" },
63
+ ];
64
+ // Hidden payloads. Part of the rule table (and therefore the digest) rather than
65
+ // special cases in the scan loop.
66
+ const PAYLOAD_PATTERNS = [
67
+ {
68
+ // Standard (RFC 4648 §4) AND URL-safe (§5) base64, padded or not — JWTs and
69
+ // web payloads commonly omit padding.
70
+ re: /[A-Za-z0-9+/_-]{120,}={0,2}/,
71
+ code: "TOOL_DEF_BASE64_BLOB",
72
+ severity: "high",
73
+ tier: "block",
74
+ note: "contains a long base64-encoded blob — possible hidden payload",
75
+ },
76
+ {
77
+ // U+200B–200F, U+202A–202E, U+2060, U+FEFF. Built from a \u-escaped string so
78
+ // the source stays reviewable (the characters are, by definition, invisible).
79
+ re: new RegExp("[\\u200B-\\u200F\\u202A-\\u202E\\u2060\\uFEFF]"),
80
+ code: "TOOL_DEF_HIDDEN_UNICODE",
81
+ severity: "high",
82
+ tier: "block",
83
+ note: "contains zero-width or bidi control characters hiding text from review",
84
+ },
85
+ ];
86
+ const RULES = [
87
+ ...INJECTION_PATTERNS,
88
+ ...EXFIL_PATTERNS,
89
+ ...SECRET_PATTERNS,
90
+ ...URL_SCHEME_PATTERNS,
91
+ ...PAYLOAD_PATTERNS,
92
+ ];
93
+ /**
94
+ * Ruleset version. Bump on ANY change to the table above — a scan result is only
95
+ * comparable to another scan made under the same version and digest.
96
+ *
97
+ * 1 — single tier; credential parameter names blocked real servers.
98
+ * 2 — block/advise tiers; exfil "send … to" anchored on an external destination;
99
+ * TOOL_DEF_SECRET_HARVEST added so demoting the credential nouns does not
100
+ * open a hole; advisory findings no longer affect the score.
101
+ */
102
+ export const STATIC_SCAN_RULESET_VERSION = "2";
103
+ const SEVERITY_RANK = {
104
+ info: 0,
105
+ low: 1,
106
+ medium: 2,
107
+ high: 3,
108
+ critical: 4,
109
+ };
110
+ /** Penalty applied to the gate score per worst BLOCKING severity found. */
111
+ const SEVERITY_PENALTY = {
112
+ info: 0,
113
+ low: 0.1,
114
+ medium: 0.3,
115
+ high: 0.6,
116
+ critical: 1,
117
+ };
118
+ /**
119
+ * The rule table plus its digest, so a scan result stays checkable after the
120
+ * rules change.
121
+ *
122
+ * Sorted, so the digest depends on the rules and not on the order they happen to
123
+ * be declared in — and sorted by CODE-UNIT comparison, not `localeCompare`: the
124
+ * digest is a cross-machine identifier, and a locale-dependent collation would
125
+ * make the same rule table digest differently on a differently-configured host,
126
+ * which is exactly the divergence the digest exists to detect. The preimage is
127
+ * the RFC 8785 canonical form (see ./jcs.ts) so this file does not invent a
128
+ * second serialization of its own.
129
+ */
130
+ export function staticScanRuleset() {
131
+ const rules = RULES.map((r) => ({
132
+ code: r.code,
133
+ severity: r.severity,
134
+ tier: r.tier,
135
+ source: r.re.source,
136
+ flags: r.re.flags,
137
+ })).sort((a, b) => cmp(a.code, b.code) || cmp(a.source, b.source) || cmp(a.flags, b.flags));
138
+ const preimage = canonicalize({ version: STATIC_SCAN_RULESET_VERSION, rules });
139
+ const digest = `sha256-${createHash("sha256").update(preimage, "utf8").digest("base64")}`;
140
+ return { version: STATIC_SCAN_RULESET_VERSION, digest, rules };
141
+ }
142
+ /** Code-unit comparison. See staticScanRuleset for why not localeCompare. */
143
+ function cmp(a, b) {
144
+ return a < b ? -1 : a > b ? 1 : 0;
145
+ }
146
+ /** Just the identity of the rule table, for embedding in a verdict. */
147
+ export function staticScanRulesetRef() {
148
+ const { version, digest } = staticScanRuleset();
149
+ return { version, digest };
150
+ }
151
+ export class StaticScanGate {
152
+ name = "static-scan";
153
+ async evaluate(input) {
154
+ const findings = [];
155
+ for (const tool of input.tools) {
156
+ const schemaText = safeStringifySchema(tool.inputSchema);
157
+ // Description is prose; schema text is field names + descriptions + enums.
158
+ const haystacks = [
159
+ { text: tool.description ?? "", where: "description" },
160
+ { text: schemaText, where: "input schema" },
161
+ ];
162
+ for (const { text, where } of haystacks) {
163
+ for (const rule of RULES) {
164
+ if (!rule.re.test(text))
165
+ continue;
166
+ const finding = {
167
+ gate: this.name,
168
+ severity: rule.severity,
169
+ code: rule.code,
170
+ message: rule.note
171
+ ? `Tool "${tool.name}" ${where} ${rule.note}.`
172
+ : `Tool "${tool.name}" ${where} matches ${rule.code} signature (${describe(rule.re)}).`,
173
+ tool: tool.name,
174
+ };
175
+ if (rule.tier === "advise")
176
+ finding.advisory = true;
177
+ findings.push(finding);
178
+ }
179
+ }
180
+ }
181
+ return { findings, score: scoreFor(findings) };
182
+ }
183
+ }
184
+ /**
185
+ * 1 minus the penalty for the worst BLOCKING severity found; clamped to [0,1].
186
+ *
187
+ * Advisory findings are excluded on purpose. The composite is presented to users
188
+ * as a safety score, and a credential parameter name is not a safety defect —
189
+ * letting it drag the number down made a clean server look like a compromised one.
190
+ */
191
+ function scoreFor(findings) {
192
+ let worst = "info";
193
+ for (const f of findings) {
194
+ if (f.advisory)
195
+ continue;
196
+ if (SEVERITY_RANK[f.severity] > SEVERITY_RANK[worst])
197
+ worst = f.severity;
198
+ }
199
+ const score = 1 - SEVERITY_PENALTY[worst];
200
+ return Math.max(0, Math.min(1, score));
201
+ }
202
+ /** Deterministic, total stringify of a JSON schema for scanning. */
203
+ function safeStringifySchema(schema) {
204
+ try {
205
+ return JSON.stringify(schema) ?? "";
206
+ }
207
+ catch {
208
+ return String(schema ?? "");
209
+ }
210
+ }
211
+ /** Short human label for a signature regex, for the finding message. */
212
+ function describe(re) {
213
+ return re.source.length > 48 ? `${re.source.slice(0, 45)}…` : re.source;
214
+ }
215
+ //# sourceMappingURL=static-scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"static-scan.js","sourceRoot":"","sources":["../src/static-scan.ts"],"names":[],"mappings":"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"}