@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.
package/dist/jcs.d.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * RFC 8785 (JSON Canonicalization Scheme) as profiled by AWR/2 (`awr/SPEC.md` §4).
3
+ *
4
+ * Two places in WARDEN turn a JSON value into bytes that a *different*
5
+ * implementation has to reproduce exactly: the signed threat feed (a publisher
6
+ * signs, ARGUS verifies) and the tool-def pin (a sha256 quoted in receipts and
7
+ * re-checked by `argus verify`). `JSON.stringify` cannot do that job — its output
8
+ * depends on the key order the wire happened to use, so one logical document
9
+ * serialises to different bytes on the two sides and the signature fails for no
10
+ * security reason at all.
11
+ *
12
+ * The profile implemented here, rule by rule:
13
+ *
14
+ * 1. **Property names sort as arrays of UTF-16 code units** (RFC 8785 §3.2.3).
15
+ * JavaScript's default `Array.prototype.sort()` on strings compares exactly
16
+ * that, so this is the one rule the platform gives away for free.
17
+ * `String.prototype.localeCompare` does *not*: it depends on the host locale
18
+ * and ICU version, which makes it unusable as a digest input.
19
+ * 2. **No Unicode normalization, ever** (SPEC §4.1 item 2). NFC would collide two
20
+ * property names that RFC 8785 keeps distinct.
21
+ * 3. **Two-character escapes plus lowercase `\uXXXX` for the remaining C0
22
+ * controls** (§3.2.2.2) — which is precisely what `JSON.stringify` emits for a
23
+ * string, so string bodies are delegated to it rather than re-implemented.
24
+ * 4. **Lone surrogates terminate with an error** instead of being substituted
25
+ * (§4.1 item 4). `JSON.stringify` would quietly escape them into
26
+ * valid-looking bytes, so they are rejected before it is called.
27
+ * 5. **Integers only, within ±(2^53−1)**; every non-integer number is refused
28
+ * rather than arbitrated (§4.3), because whether `1` parses as an integer or
29
+ * as a double is a language accident that silently changes the signed bytes.
30
+ *
31
+ * Scope: this module is *only* the byte-level canonicalization. It knows nothing
32
+ * about AWR documents, Data Integrity proofs, `did:key` or profiles.
33
+ */
34
+ /** Reason codes from the SPEC §11.2 `AWR-CANON-*` registry that this module raises. */
35
+ export type CanonicalizationCode =
36
+ /** Non-integer JSON number present. */
37
+ "AWR-CANON-001"
38
+ /** Integer outside ±(2^53−1). */
39
+ | "AWR-CANON-002"
40
+ /** Invalid Unicode (lone surrogate) in string data. */
41
+ | "AWR-CANON-003"
42
+ /** Duplicate object property name. */
43
+ | "AWR-CANON-004"
44
+ /** Input is not well-formed JSON / not representable in JSON. */
45
+ | "AWR-CANON-005";
46
+ /**
47
+ * A value could not be canonicalized (or parsed) under the AWR profile.
48
+ *
49
+ * Callers are expected to *handle* this rather than let it escape: refusing to
50
+ * produce bytes is the honest outcome, but it must not crash a connection check.
51
+ */
52
+ export declare class CanonicalizationError extends Error {
53
+ readonly code: CanonicalizationCode;
54
+ constructor(code: CanonicalizationCode, detail: string);
55
+ }
56
+ /** Largest magnitude of a JSON integer permitted in canonical bytes (2^53−1). */
57
+ export declare const MAX_SAFE_JSON_INTEGER = 9007199254740991;
58
+ /**
59
+ * Return the RFC 8785 canonical serialization of `value` as a UTF-8-ready string.
60
+ *
61
+ * Throws {@link CanonicalizationError} rather than emitting bytes it cannot
62
+ * defend: a non-integer number, an integer beyond 2^53−1, a lone surrogate, an
63
+ * `undefined` property (which `JSON.stringify` would silently drop), a
64
+ * non-plain object, or a cycle.
65
+ */
66
+ export declare function canonicalize(value: unknown): string;
67
+ /**
68
+ * Parse JSON with the profile's parser requirements, for input whose bytes were
69
+ * signed by someone else.
70
+ *
71
+ * `JSON.parse` alone is not enough on a verification path:
72
+ *
73
+ * - **Duplicate property names** (§4.1 item 5, `AWR-CANON-004`) are resolved
74
+ * last-wins and silently, which would let the parser — not the signer — decide
75
+ * which of two records was the signed one.
76
+ * - **Non-integer number literals** (§4.3, `AWR-CANON-001`) are rejected
77
+ * *lexically*, not by value: `2340.0` and `2340` parse to the same JavaScript
78
+ * number, and a publisher written in a language that distinguishes them would
79
+ * have signed different bytes.
80
+ *
81
+ * Both checks run over the received text after `JSON.parse` has confirmed it is
82
+ * well-formed, so the scanner below only has to find tokens, never validate
83
+ * grammar.
84
+ */
85
+ export declare function parseJsonStrict(text: string): unknown;
86
+ //# sourceMappingURL=jcs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jcs.d.ts","sourceRoot":"","sources":["../src/jcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,uFAAuF;AACvF,MAAM,MAAM,oBAAoB;AAC9B,uCAAuC;AACrC,eAAe;AACjB,iCAAiC;GAC/B,eAAe;AACjB,uDAAuD;GACrD,eAAe;AACjB,sCAAsC;GACpC,eAAe;AACjB,iEAAiE;GAC/D,eAAe,CAAC;AAEpB;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,oBAAoB;gBAA1B,IAAI,EAAE,oBAAoB,EAAE,MAAM,EAAE,MAAM;CAIhE;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,mBAAmB,CAAC;AAStD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAInD;AA8HD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CASrD"}
package/dist/jcs.js ADDED
@@ -0,0 +1,296 @@
1
+ /**
2
+ * RFC 8785 (JSON Canonicalization Scheme) as profiled by AWR/2 (`awr/SPEC.md` §4).
3
+ *
4
+ * Two places in WARDEN turn a JSON value into bytes that a *different*
5
+ * implementation has to reproduce exactly: the signed threat feed (a publisher
6
+ * signs, ARGUS verifies) and the tool-def pin (a sha256 quoted in receipts and
7
+ * re-checked by `argus verify`). `JSON.stringify` cannot do that job — its output
8
+ * depends on the key order the wire happened to use, so one logical document
9
+ * serialises to different bytes on the two sides and the signature fails for no
10
+ * security reason at all.
11
+ *
12
+ * The profile implemented here, rule by rule:
13
+ *
14
+ * 1. **Property names sort as arrays of UTF-16 code units** (RFC 8785 §3.2.3).
15
+ * JavaScript's default `Array.prototype.sort()` on strings compares exactly
16
+ * that, so this is the one rule the platform gives away for free.
17
+ * `String.prototype.localeCompare` does *not*: it depends on the host locale
18
+ * and ICU version, which makes it unusable as a digest input.
19
+ * 2. **No Unicode normalization, ever** (SPEC §4.1 item 2). NFC would collide two
20
+ * property names that RFC 8785 keeps distinct.
21
+ * 3. **Two-character escapes plus lowercase `\uXXXX` for the remaining C0
22
+ * controls** (§3.2.2.2) — which is precisely what `JSON.stringify` emits for a
23
+ * string, so string bodies are delegated to it rather than re-implemented.
24
+ * 4. **Lone surrogates terminate with an error** instead of being substituted
25
+ * (§4.1 item 4). `JSON.stringify` would quietly escape them into
26
+ * valid-looking bytes, so they are rejected before it is called.
27
+ * 5. **Integers only, within ±(2^53−1)**; every non-integer number is refused
28
+ * rather than arbitrated (§4.3), because whether `1` parses as an integer or
29
+ * as a double is a language accident that silently changes the signed bytes.
30
+ *
31
+ * Scope: this module is *only* the byte-level canonicalization. It knows nothing
32
+ * about AWR documents, Data Integrity proofs, `did:key` or profiles.
33
+ */
34
+ /**
35
+ * A value could not be canonicalized (or parsed) under the AWR profile.
36
+ *
37
+ * Callers are expected to *handle* this rather than let it escape: refusing to
38
+ * produce bytes is the honest outcome, but it must not crash a connection check.
39
+ */
40
+ export class CanonicalizationError extends Error {
41
+ code;
42
+ constructor(code, detail) {
43
+ super(`${code}: ${detail}`);
44
+ this.code = code;
45
+ this.name = "CanonicalizationError";
46
+ }
47
+ }
48
+ /** Largest magnitude of a JSON integer permitted in canonical bytes (2^53−1). */
49
+ export const MAX_SAFE_JSON_INTEGER = 9007199254740991;
50
+ /**
51
+ * Nesting bound. Canonicalization runs over attacker-influenced input (a remote
52
+ * feed, a third-party tool schema), and an unbounded recursive walk is a
53
+ * denial-of-service (SPEC §13.4).
54
+ */
55
+ const MAX_DEPTH = 64;
56
+ /**
57
+ * Return the RFC 8785 canonical serialization of `value` as a UTF-8-ready string.
58
+ *
59
+ * Throws {@link CanonicalizationError} rather than emitting bytes it cannot
60
+ * defend: a non-integer number, an integer beyond 2^53−1, a lone surrogate, an
61
+ * `undefined` property (which `JSON.stringify` would silently drop), a
62
+ * non-plain object, or a cycle.
63
+ */
64
+ export function canonicalize(value) {
65
+ const out = [];
66
+ serialize(value, out, "$", 0, new Set());
67
+ return out.join("");
68
+ }
69
+ function serialize(value, out, path, depth, seen) {
70
+ if (depth > MAX_DEPTH) {
71
+ throw new CanonicalizationError("AWR-CANON-005", `nesting deeper than ${MAX_DEPTH} at ${path}`);
72
+ }
73
+ if (value === null) {
74
+ out.push("null");
75
+ return;
76
+ }
77
+ switch (typeof value) {
78
+ case "boolean":
79
+ out.push(value ? "true" : "false");
80
+ return;
81
+ case "number":
82
+ out.push(serializeNumber(value, path));
83
+ return;
84
+ case "string":
85
+ out.push(serializeString(value, path));
86
+ return;
87
+ case "object":
88
+ break;
89
+ default:
90
+ // undefined, function, symbol, bigint: JSON has no representation, and
91
+ // guessing one (JSON.stringify drops or throws depending on position) is
92
+ // exactly the silent divergence this module exists to prevent.
93
+ throw new CanonicalizationError("AWR-CANON-005", `${typeof value} at ${path} is not representable in JSON`);
94
+ }
95
+ const obj = value;
96
+ if (seen.has(obj))
97
+ throw new CanonicalizationError("AWR-CANON-005", `circular reference at ${path}`);
98
+ seen.add(obj);
99
+ try {
100
+ if (Array.isArray(obj)) {
101
+ out.push("[");
102
+ for (let i = 0; i < obj.length; i++) {
103
+ if (i > 0)
104
+ out.push(",");
105
+ serialize(obj[i], out, `${path}[${i}]`, depth + 1, seen);
106
+ }
107
+ out.push("]");
108
+ return;
109
+ }
110
+ // Only plain records canonicalize. A Date, Map, RegExp or class instance has
111
+ // no JSON form of its own; `JSON.stringify` would call `toJSON()` or emit
112
+ // `{}`, inventing bytes the other implementation cannot reproduce.
113
+ const proto = Object.getPrototypeOf(obj);
114
+ if (proto !== Object.prototype && proto !== null) {
115
+ throw new CanonicalizationError("AWR-CANON-005", `${obj.constructor?.name ?? "object"} at ${path} is not a plain JSON object`);
116
+ }
117
+ const record = obj;
118
+ out.push("{");
119
+ let first = true;
120
+ // §3.2.3: sort as arrays of UTF-16 code units — the default string sort.
121
+ for (const key of Object.keys(record).sort()) {
122
+ const member = record[key];
123
+ if (member === undefined) {
124
+ throw new CanonicalizationError("AWR-CANON-005", `property "${key}" at ${path} is undefined — JSON has no such value ` +
125
+ `(JSON.stringify would drop it silently, changing the signed bytes)`);
126
+ }
127
+ if (!first)
128
+ out.push(",");
129
+ first = false;
130
+ out.push(serializeString(key, `property name "${key}" at ${path}`), ":");
131
+ serialize(member, out, `${path}.${key}`, depth + 1, seen);
132
+ }
133
+ out.push("}");
134
+ }
135
+ finally {
136
+ seen.delete(obj);
137
+ }
138
+ }
139
+ /** §4.3: integers only, `[-(2^53-1), 2^53-1]`. Everything else is refused. */
140
+ function serializeNumber(n, path) {
141
+ if (!Number.isFinite(n)) {
142
+ throw new CanonicalizationError("AWR-CANON-001", `${String(n)} at ${path} is not a JSON number`);
143
+ }
144
+ if (!Number.isInteger(n)) {
145
+ throw new CanonicalizationError("AWR-CANON-001", `non-integer JSON number ${n} at ${path}`);
146
+ }
147
+ if (!Number.isSafeInteger(n)) {
148
+ throw new CanonicalizationError("AWR-CANON-002", `integer ${n} at ${path} is outside ±(2^53−1)`);
149
+ }
150
+ // Safe integers never render in exponential form, and `String(-0)` is "0".
151
+ return String(n);
152
+ }
153
+ /**
154
+ * §3.2.2.2 string serialization, delegated to `JSON.stringify` — which emits the
155
+ * same two-character escapes and the same lowercase `\u00xx` for the remaining C0
156
+ * controls, and leaves every other character literal. The one divergence is lone
157
+ * surrogates, which it escapes and RFC 8785 forbids, so those are rejected first.
158
+ */
159
+ function serializeString(text, path) {
160
+ assertWellFormed(text, path);
161
+ return JSON.stringify(text);
162
+ }
163
+ /** §4.1 item 4 / `AWR-CANON-003`: unpaired surrogates are an error, not a substitution. */
164
+ function assertWellFormed(text, path) {
165
+ for (let i = 0; i < text.length; i++) {
166
+ const unit = text.charCodeAt(i);
167
+ if (unit >= 0xd800 && unit <= 0xdbff) {
168
+ const low = text.charCodeAt(i + 1);
169
+ if (!(low >= 0xdc00 && low <= 0xdfff)) {
170
+ throw new CanonicalizationError("AWR-CANON-003", `lone high surrogate U+${unit.toString(16).toUpperCase()} at index ${i} in ${path}`);
171
+ }
172
+ i++;
173
+ }
174
+ else if (unit >= 0xdc00 && unit <= 0xdfff) {
175
+ throw new CanonicalizationError("AWR-CANON-003", `lone low surrogate U+${unit.toString(16).toUpperCase()} at index ${i} in ${path}`);
176
+ }
177
+ }
178
+ }
179
+ /**
180
+ * Parse JSON with the profile's parser requirements, for input whose bytes were
181
+ * signed by someone else.
182
+ *
183
+ * `JSON.parse` alone is not enough on a verification path:
184
+ *
185
+ * - **Duplicate property names** (§4.1 item 5, `AWR-CANON-004`) are resolved
186
+ * last-wins and silently, which would let the parser — not the signer — decide
187
+ * which of two records was the signed one.
188
+ * - **Non-integer number literals** (§4.3, `AWR-CANON-001`) are rejected
189
+ * *lexically*, not by value: `2340.0` and `2340` parse to the same JavaScript
190
+ * number, and a publisher written in a language that distinguishes them would
191
+ * have signed different bytes.
192
+ *
193
+ * Both checks run over the received text after `JSON.parse` has confirmed it is
194
+ * well-formed, so the scanner below only has to find tokens, never validate
195
+ * grammar.
196
+ */
197
+ export function parseJsonStrict(text) {
198
+ let value;
199
+ try {
200
+ value = JSON.parse(text);
201
+ }
202
+ catch (err) {
203
+ throw new CanonicalizationError("AWR-CANON-005", `not well-formed JSON: ${err.message}`);
204
+ }
205
+ auditTokens(text);
206
+ return value;
207
+ }
208
+ function auditTokens(text) {
209
+ const tokens = tokenize(text);
210
+ for (const token of tokens) {
211
+ if (token.kind === "number")
212
+ assertIntegerLiteral(token.literal);
213
+ }
214
+ // A string immediately followed by `:` inside an object is a property name;
215
+ // in well-formed JSON nothing else can occupy that position.
216
+ const stack = [];
217
+ for (let i = 0; i < tokens.length; i++) {
218
+ const token = tokens[i];
219
+ if (token.kind === "{")
220
+ stack.push(new Set());
221
+ else if (token.kind === "[")
222
+ stack.push(null);
223
+ else if (token.kind === "}" || token.kind === "]")
224
+ stack.pop();
225
+ else if (token.kind === "string") {
226
+ const names = stack[stack.length - 1];
227
+ if (names && tokens[i + 1]?.kind === ":") {
228
+ if (names.has(token.value)) {
229
+ throw new CanonicalizationError("AWR-CANON-004", `duplicate object property name "${token.value}"`);
230
+ }
231
+ names.add(token.value);
232
+ }
233
+ }
234
+ }
235
+ }
236
+ function tokenize(text) {
237
+ const tokens = [];
238
+ let i = 0;
239
+ while (i < text.length) {
240
+ const ch = text[i];
241
+ if (ch === '"') {
242
+ const start = i;
243
+ i++;
244
+ while (i < text.length) {
245
+ const c = text[i];
246
+ if (c === "\\") {
247
+ i += 2;
248
+ continue;
249
+ }
250
+ i++;
251
+ if (c === '"')
252
+ break;
253
+ }
254
+ // The slice is a valid JSON string literal (JSON.parse already accepted the
255
+ // whole document), so decoding it cannot fail — and decoding is required:
256
+ // "a" and "a" are the same property name.
257
+ tokens.push({ kind: "string", value: JSON.parse(text.slice(start, i)) });
258
+ continue;
259
+ }
260
+ if (ch === "{" || ch === "}" || ch === "[" || ch === "]" || ch === ":" || ch === ",") {
261
+ tokens.push({ kind: ch });
262
+ i++;
263
+ continue;
264
+ }
265
+ if (ch === "-" || (ch >= "0" && ch <= "9")) {
266
+ const start = i;
267
+ i++;
268
+ while (i < text.length && /[0-9eE+.\-]/.test(text[i]))
269
+ i++;
270
+ tokens.push({ kind: "number", literal: text.slice(start, i) });
271
+ continue;
272
+ }
273
+ // Whitespace and the letters of true/false/null carry nothing we check.
274
+ i++;
275
+ }
276
+ return tokens;
277
+ }
278
+ function assertIntegerLiteral(literal) {
279
+ if (/[.eE]/.test(literal)) {
280
+ throw new CanonicalizationError("AWR-CANON-001", `non-integer JSON number literal ${literal}`);
281
+ }
282
+ // BigInt, not Number: a 20-digit literal loses precision before it can be
283
+ // compared, and would slip through as a "safe" integer.
284
+ let asInt;
285
+ try {
286
+ asInt = BigInt(literal);
287
+ }
288
+ catch {
289
+ throw new CanonicalizationError("AWR-CANON-005", `malformed number literal ${literal}`);
290
+ }
291
+ const limit = BigInt(MAX_SAFE_JSON_INTEGER);
292
+ if (asInt > limit || asInt < -limit) {
293
+ throw new CanonicalizationError("AWR-CANON-002", `integer literal ${literal} is outside ±(2^53−1)`);
294
+ }
295
+ }
296
+ //# sourceMappingURL=jcs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jcs.js","sourceRoot":"","sources":["../src/jcs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { WardenLogger } from "./types.js";
2
+ /**
3
+ * A logger that writes nothing — the default when a host supplies none.
4
+ *
5
+ * WARDEN reports every decision through the logger, so a host that drops it
6
+ * loses the audit trail but never the enforcement: verdicts are returned as
7
+ * data from `vet()`, not inferred from log output.
8
+ */
9
+ export declare function silentLogger(): WardenLogger;
10
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,YAAY,CAU3C"}
package/dist/logger.js ADDED
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A logger that writes nothing — the default when a host supplies none.
3
+ *
4
+ * WARDEN reports every decision through the logger, so a host that drops it
5
+ * loses the audit trail but never the enforcement: verdicts are returned as
6
+ * data from `vet()`, not inferred from log output.
7
+ */
8
+ export function silentLogger() {
9
+ const noop = () => { };
10
+ const self = {
11
+ debug: noop,
12
+ info: noop,
13
+ warn: noop,
14
+ error: noop,
15
+ child: () => self,
16
+ };
17
+ return self;
18
+ }
19
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,IAAI,GAAG,GAAS,EAAE,GAAE,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAiB;QACzB,KAAK,EAAE,IAAI;QACX,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,GAAG,EAAE,CAAC,IAAI;KAClB,CAAC;IACF,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { WardenGate, WardenGateInput, WardenGateResult } from "./types.js";
2
+ /**
3
+ * Origin gate — enforces `policy.allowUnknownServers` against where a server
4
+ * declaration came from.
5
+ *
6
+ * Every server ARGUS connects to is one of two things: declared by the operator
7
+ * under `mcp.servers`, or discovered from a remote catalog listed under
8
+ * `mcp.catalogs` — in which case `McpServerRef.catalog` names that catalog
9
+ * (see CatalogConnector.normalize). Only the second kind is "unknown": nothing
10
+ * local ever vouched for it.
11
+ *
12
+ * This gate replaces the reputation gate that used to occupy this slot in the
13
+ * chain. That gate asked LUMEN for a PageRank score without ever supplying trust
14
+ * edges, so the oracle returned its neutral default without any request being
15
+ * made: the scored branch was unreachable in production, the composite score was
16
+ * permanently multiplied by a constant, and the user was told the oracle was
17
+ * unreachable when nothing had been tried. Catalog provenance needs no network,
18
+ * is always available, and cannot deadlock — an operator's own servers are known
19
+ * by definition, so fail-closed still leaves a way to connect.
20
+ */
21
+ export declare class OriginGate implements WardenGate {
22
+ readonly name = "origin";
23
+ evaluate(input: WardenGateInput): Promise<WardenGateResult>;
24
+ }
25
+ //# sourceMappingURL=origin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,UAAU,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,UAAW,YAAW,UAAU;IAC3C,QAAQ,CAAC,IAAI,YAAY;IAGnB,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAqBlE"}
package/dist/origin.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Origin gate — enforces `policy.allowUnknownServers` against where a server
3
+ * declaration came from.
4
+ *
5
+ * Every server ARGUS connects to is one of two things: declared by the operator
6
+ * under `mcp.servers`, or discovered from a remote catalog listed under
7
+ * `mcp.catalogs` — in which case `McpServerRef.catalog` names that catalog
8
+ * (see CatalogConnector.normalize). Only the second kind is "unknown": nothing
9
+ * local ever vouched for it.
10
+ *
11
+ * This gate replaces the reputation gate that used to occupy this slot in the
12
+ * chain. That gate asked LUMEN for a PageRank score without ever supplying trust
13
+ * edges, so the oracle returned its neutral default without any request being
14
+ * made: the scored branch was unreachable in production, the composite score was
15
+ * permanently multiplied by a constant, and the user was told the oracle was
16
+ * unreachable when nothing had been tried. Catalog provenance needs no network,
17
+ * is always available, and cannot deadlock — an operator's own servers are known
18
+ * by definition, so fail-closed still leaves a way to connect.
19
+ */
20
+ export class OriginGate {
21
+ name = "origin";
22
+ // eslint-disable-next-line @typescript-eslint/require-await -- gate interface is async
23
+ async evaluate(input) {
24
+ const catalog = input.server.catalog;
25
+ if (!catalog) {
26
+ // Operator-declared under mcp.servers: known, nothing to report.
27
+ return { findings: [], score: 1 };
28
+ }
29
+ const strict = input.policy.allowUnknownServers !== true;
30
+ const finding = {
31
+ gate: this.name,
32
+ severity: strict ? "high" : "info",
33
+ code: "SERVER_UNDECLARED",
34
+ message: strict
35
+ ? `Server "${input.server.id}" was discovered from catalog "${catalog}" and is not declared under mcp.servers; ` +
36
+ `policy forbids undeclared servers (allowUnknownServers=false) — blocking. Add it to mcp.servers to allow it.`
37
+ : `Server "${input.server.id}" was discovered from catalog "${catalog}", not declared under mcp.servers.`,
38
+ };
39
+ // Catalog provenance on its own is not a defect, so the permissive path does
40
+ // not tax the composite score; it only says where the server came from.
41
+ return { findings: [finding], score: strict ? 0 : 1, fatal: strict };
42
+ }
43
+ }
44
+ //# sourceMappingURL=origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"origin.js","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,UAAU;IACZ,IAAI,GAAG,QAAQ,CAAC;IAEzB,uFAAuF;IACvF,KAAK,CAAC,QAAQ,CAAC,KAAsB;QACnC,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC;QACrC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,iEAAiE;YACjE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACpC,CAAC;QAED,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC,mBAAmB,KAAK,IAAI,CAAC;QACzD,MAAM,OAAO,GAAkB;YAC7B,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM;YAClC,IAAI,EAAE,mBAAmB;YACzB,OAAO,EAAE,MAAM;gBACb,CAAC,CAAC,WAAW,KAAK,CAAC,MAAM,CAAC,EAAE,kCAAkC,OAAO,2CAA2C;oBAC9G,8GAA8G;gBAChH,CAAC,CAAC,WAAW,KAAK,CAAC,MAAM,CAAC,EAAE,kCAAkC,OAAO,oCAAoC;SAC5G,CAAC;QACF,6EAA6E;QAC7E,wEAAwE;QACxE,OAAO,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACvE,CAAC;CACF"}
@@ -0,0 +1,73 @@
1
+ import type { McpServerRef, PinStore, ToolDef, WardenGate, WardenGateInput, WardenGateResult } from "./types.js";
2
+ /**
3
+ * Tool-definition pinning + drift detection ("rug-pull" defence).
4
+ *
5
+ * A server can advertise benign tools at approval time and silently swap in a
6
+ * poisoned definition later. We hash the canonical tool-def set on approval and
7
+ * compare on every subsequent connection: a changed hash means the contract the
8
+ * user approved no longer holds, so (under policy.pinToolDefs) we block and force
9
+ * re-approval. First-contact servers are flagged UNPINNED so the chain knows the
10
+ * pin is established only when the user approves.
11
+ */
12
+ export declare class PinningGate implements WardenGate {
13
+ private readonly store;
14
+ readonly name = "pinning";
15
+ constructor(store: PinStore);
16
+ evaluate(input: WardenGateInput): Promise<WardenGateResult>;
17
+ /**
18
+ * The tool-def set has no canonical form (see {@link canonicalToolsHash}), so no
19
+ * hash can be produced for it. What that means depends on whether a pin exists:
20
+ *
21
+ * - **No pin yet** — nothing is being contradicted; the pin simply cannot be
22
+ * established, which is a `medium` warning and not evidence of an attack.
23
+ * - **Pin exists** — the snapshot the user approved can no longer be re-verified,
24
+ * which is indistinguishable from drift and is treated as drift. Otherwise a
25
+ * server could disarm the rug-pull defence at will by adding one fractional
26
+ * number to a schema.
27
+ */
28
+ private uncanonical;
29
+ /**
30
+ * Persist the current tool-def set as the trusted snapshot for this server.
31
+ * Called by Warden.approve() once a user has accepted the connection.
32
+ *
33
+ * Throws {@link CanonicalizationError} when the set has no canonical form; the
34
+ * caller (`McpHost.connect`) already reports a pin failure as a degraded
35
+ * rug-pull defence rather than failing the connection.
36
+ */
37
+ pin(server: McpServerRef, tools: ToolDef[]): Promise<void>;
38
+ }
39
+ /**
40
+ * Marker used where a tool-def hash is *recorded* rather than compared, and the set
41
+ * turned out to have no canonical form. It can never collide with a sha256 hex
42
+ * digest, so anything that later compares it simply reports a mismatch.
43
+ */
44
+ export declare const UNCANONICAL_TOOLS_HASH = "uncanonical:non-canonical-tool-defs";
45
+ /**
46
+ * sha256 over the canonical tool-def set: tools sorted by name, each reduced to
47
+ * the security-relevant fields (name, description, schema), serialised with
48
+ * {@link canonicalize} — RFC 8785 (JCS) as profiled in `awr/SPEC.md` §4.
49
+ *
50
+ * This digest is quoted in receipts and re-checked elsewhere (`argus verify`, the
51
+ * sealed mandate), so it has to be reproducible by an implementation that is not
52
+ * this one. Two consequences:
53
+ *
54
+ * - **Ordering is by UTF-16 code unit, never `localeCompare`.** `localeCompare`
55
+ * depends on the host locale and ICU version — `["a", "B"]` sorts one way under
56
+ * `en-US` and another under the C locale — so a digest built on it is not even
57
+ * stable across two machines running this same code, let alone across languages.
58
+ * JavaScript's `<`/`>` on strings compare UTF-16 code units, which is exactly
59
+ * RFC 8785 §3.2.3's rule.
60
+ * - **Non-integer numbers are refused** (`AWR-CANON-001`, SPEC §4.3) rather than
61
+ * serialised. Whether `1` is an integer or a double is a language accident that
62
+ * silently changes the bytes, so a schema carrying e.g. `"multipleOf": 0.01` has
63
+ * no canonical form here and this function throws
64
+ * {@link CanonicalizationError}. Callers must handle that — refusing to emit a
65
+ * digest is honest; emitting one nobody else can reproduce is not.
66
+ */
67
+ export declare function canonicalToolsHash(tools: ToolDef[]): string;
68
+ /**
69
+ * Like {@link canonicalToolsHash} but total: returns `undefined` instead of
70
+ * throwing, for call sites that record a hash rather than enforce one.
71
+ */
72
+ export declare function tryCanonicalToolsHash(tools: ToolDef[]): string | undefined;
73
+ //# sourceMappingURL=pinning.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pinning.d.ts","sourceRoot":"","sources":["../src/pinning.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EAER,OAAO,EAEP,UAAU,EACV,eAAe,EACf,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;GASG;AACH,qBAAa,WAAY,YAAW,UAAU;IAGhC,OAAO,CAAC,QAAQ,CAAC,KAAK;IAFlC,QAAQ,CAAC,IAAI,aAAa;gBAEG,KAAK,EAAE,QAAQ;IAEtC,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA+CjE;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW;IAgCnB;;;;;;;OAOG;IACG,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CASjE;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,wCAAwC,CAAC;AAE5E;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,CAS3D;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,GAAG,SAAS,CAO1E"}