@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.
- package/CHANGELOG.md +206 -0
- package/LICENSE +21 -0
- package/README-es.md +190 -0
- package/README-fr.md +190 -0
- package/README-ru.md +189 -0
- package/README-zh.md +173 -0
- package/README.md +179 -6
- package/dist/glob.d.ts +81 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +180 -0
- package/dist/glob.js.map +1 -0
- package/dist/index.d.ts +106 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/jcs.d.ts +86 -0
- package/dist/jcs.d.ts.map +1 -0
- package/dist/jcs.js +296 -0
- package/dist/jcs.js.map +1 -0
- package/dist/logger.d.ts +10 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +19 -0
- package/dist/logger.js.map +1 -0
- package/dist/origin.d.ts +25 -0
- package/dist/origin.d.ts.map +1 -0
- package/dist/origin.js +49 -0
- package/dist/origin.js.map +1 -0
- package/dist/pinning.d.ts +91 -0
- package/dist/pinning.d.ts.map +1 -0
- package/dist/pinning.js +227 -0
- package/dist/pinning.js.map +1 -0
- package/dist/sandbox.d.ts +38 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +79 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/sanitize.d.ts +38 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +53 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/static-scan.d.ts +110 -0
- package/dist/static-scan.d.ts.map +1 -0
- package/dist/static-scan.js +539 -0
- package/dist/static-scan.js.map +1 -0
- package/dist/threat-feed.d.ts +138 -0
- package/dist/threat-feed.d.ts.map +1 -0
- package/dist/threat-feed.js +546 -0
- package/dist/threat-feed.js.map +1 -0
- package/dist/types.d.ts +173 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/package.json +67 -9
- package/index.js +0 -6
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
|
package/dist/jcs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jcs.js","sourceRoot":"","sources":["../src/jcs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAeH;;;;;GAKG;AACH,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACzB;IAArB,YAAqB,IAA0B,EAAE,MAAc;QAC7D,KAAK,CAAC,GAAG,IAAI,KAAK,MAAM,EAAE,CAAC,CAAC;QADT,SAAI,GAAJ,IAAI,CAAsB;QAE7C,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,qBAAqB,GAAG,gBAAgB,CAAC;AAEtD;;;;GAIG;AACH,MAAM,SAAS,GAAG,EAAE,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,SAAS,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,GAAG,EAAU,CAAC,CAAC;IACjD,OAAO,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACtB,CAAC;AAED,SAAS,SAAS,CAAC,KAAc,EAAE,GAAa,EAAE,IAAY,EAAE,KAAa,EAAE,IAAiB;IAC9F,IAAI,KAAK,GAAG,SAAS,EAAE,CAAC;QACtB,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,uBAAuB,SAAS,OAAO,IAAI,EAAE,CAAC,CAAC;IAClG,CAAC;IACD,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QACnB,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACjB,OAAO;IACT,CAAC;IACD,QAAQ,OAAO,KAAK,EAAE,CAAC;QACrB,KAAK,SAAS;YACZ,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YACnC,OAAO;QACT,KAAK,QAAQ;YACX,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;YACvC,OAAO;QACT,KAAK,QAAQ;YACX,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;YACvC,OAAO;QACT,KAAK,QAAQ;YACX,MAAM;QACR;YACE,uEAAuE;YACvE,yEAAyE;YACzE,+DAA+D;YAC/D,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,GAAG,OAAO,KAAK,OAAO,IAAI,+BAA+B,CAAC,CAAC;IAChH,CAAC;IAED,MAAM,GAAG,GAAG,KAAe,CAAC;IAC5B,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,yBAAyB,IAAI,EAAE,CAAC,CAAC;IACrG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACd,IAAI,CAAC;QACH,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACpC,IAAI,CAAC,GAAG,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBACzB,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;YAC3D,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACd,OAAO;QACT,CAAC;QAED,6EAA6E;QAC7E,0EAA0E;QAC1E,mEAAmE;QACnE,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,KAAK,KAAK,MAAM,CAAC,SAAS,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACjD,MAAM,IAAI,qBAAqB,CAC7B,eAAe,EACf,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,IAAI,QAAQ,OAAO,IAAI,6BAA6B,CAC7E,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,GAA8B,CAAC;QAC9C,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACd,IAAI,KAAK,GAAG,IAAI,CAAC;QACjB,yEAAyE;QACzE,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC7C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAC3B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,IAAI,qBAAqB,CAC7B,eAAe,EACf,aAAa,GAAG,QAAQ,IAAI,yCAAyC;oBACnE,oEAAoE,CACvE,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,KAAK;gBAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC1B,KAAK,GAAG,KAAK,CAAC;YACd,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,GAAG,EAAE,kBAAkB,GAAG,QAAQ,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;YACzE,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,IAAI,GAAG,EAAE,EAAE,KAAK,GAAG,CAAC,EAAE,IAAI,CAAC,CAAC;QAC5D,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChB,CAAC;YAAS,CAAC;QACT,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,SAAS,eAAe,CAAC,CAAS,EAAE,IAAY;IAC9C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,uBAAuB,CAAC,CAAC;IACnG,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;QACzB,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,2BAA2B,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,WAAW,CAAC,OAAO,IAAI,uBAAuB,CAAC,CAAC;IACnG,CAAC;IACD,2EAA2E;IAC3E,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,IAAY,EAAE,IAAY;IACjD,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC;AAED,2FAA2F;AAC3F,SAAS,gBAAgB,CAAC,IAAY,EAAE,IAAY;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YACrC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACnC,IAAI,CAAC,CAAC,GAAG,IAAI,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,EAAE,CAAC;gBACtC,MAAM,IAAI,qBAAqB,CAC7B,eAAe,EACf,yBAAyB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,OAAO,IAAI,EAAE,CACpF,CAAC;YACJ,CAAC;YACD,CAAC,EAAE,CAAC;QACN,CAAC;aAAM,IAAI,IAAI,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,EAAE,CAAC;YAC5C,MAAM,IAAI,qBAAqB,CAC7B,eAAe,EACf,wBAAwB,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,aAAa,CAAC,OAAO,IAAI,EAAE,CACnF,CAAC;QACJ,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,yBAA0B,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACtG,CAAC;IACD,WAAW,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,KAAK,CAAC;AACf,CAAC;AAOD,SAAS,WAAW,CAAC,IAAY;IAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAE9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;YAAE,oBAAoB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACnE,CAAC;IAED,4EAA4E;IAC5E,6DAA6D;IAC7D,MAAM,KAAK,GAA8B,EAAE,CAAC;IAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QACzB,IAAI,KAAK,CAAC,IAAI,KAAK,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,EAAU,CAAC,CAAC;aACjD,IAAI,KAAK,CAAC,IAAI,KAAK,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;aACzC,IAAI,KAAK,CAAC,IAAI,KAAK,GAAG,IAAI,KAAK,CAAC,IAAI,KAAK,GAAG;YAAE,KAAK,CAAC,GAAG,EAAE,CAAC;aAC1D,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACtC,IAAI,KAAK,IAAI,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC;gBACzC,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC3B,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,mCAAmC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;gBACtG,CAAC;gBACD,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,MAAM,GAAY,EAAE,CAAC;IAC3B,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QACvB,MAAM,EAAE,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACpB,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,CAAC,CAAC;YAChB,CAAC,EAAE,CAAC;YACJ,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACvB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;gBACnB,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;oBACf,CAAC,IAAI,CAAC,CAAC;oBACP,SAAS;gBACX,CAAC;gBACD,CAAC,EAAE,CAAC;gBACJ,IAAI,CAAC,KAAK,GAAG;oBAAE,MAAM;YACvB,CAAC;YACD,4EAA4E;YAC5E,0EAA0E;YAC1E,0CAA0C;YAC1C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAW,EAAE,CAAC,CAAC;YACnF,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,GAAG,EAAE,CAAC;YACrF,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAC1B,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,EAAE,IAAI,GAAG,CAAC,EAAE,CAAC;YAC3C,MAAM,KAAK,GAAG,CAAC,CAAC;YAChB,CAAC,EAAE,CAAC;YACJ,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAE,CAAC;gBAAE,CAAC,EAAE,CAAC;YAC5D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/D,SAAS;QACX,CAAC;QACD,wEAAwE;QACxE,CAAC,EAAE,CAAC;IACN,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,oBAAoB,CAAC,OAAe;IAC3C,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,mCAAmC,OAAO,EAAE,CAAC,CAAC;IACjG,CAAC;IACD,0EAA0E;IAC1E,wDAAwD;IACxD,IAAI,KAAa,CAAC;IAClB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,4BAA4B,OAAO,EAAE,CAAC,CAAC;IAC1F,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAC;IAC5C,IAAI,KAAK,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,KAAK,EAAE,CAAC;QACpC,MAAM,IAAI,qBAAqB,CAAC,eAAe,EAAE,mBAAmB,OAAO,uBAAuB,CAAC,CAAC;IACtG,CAAC;AACH,CAAC"}
|
package/dist/logger.d.ts
ADDED
|
@@ -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"}
|
package/dist/origin.d.ts
ADDED
|
@@ -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":"AACA,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;CAyBlE"}
|
package/dist/origin.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { displaySafe } from "./sanitize.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 class OriginGate {
|
|
22
|
+
name = "origin";
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/require-await -- gate interface is async
|
|
24
|
+
async evaluate(input) {
|
|
25
|
+
const catalog = input.server.catalog;
|
|
26
|
+
if (!catalog) {
|
|
27
|
+
// Operator-declared under mcp.servers: known, nothing to report.
|
|
28
|
+
return { findings: [], score: 1 };
|
|
29
|
+
}
|
|
30
|
+
const strict = input.policy.allowUnknownServers !== true;
|
|
31
|
+
// Both the id and the catalog name can come from a remote catalog entry, and
|
|
32
|
+
// this message is printed to the operator's terminal.
|
|
33
|
+
const id = displaySafe(input.server.id);
|
|
34
|
+
const from = displaySafe(catalog);
|
|
35
|
+
const finding = {
|
|
36
|
+
gate: this.name,
|
|
37
|
+
severity: strict ? "high" : "info",
|
|
38
|
+
code: "SERVER_UNDECLARED",
|
|
39
|
+
message: strict
|
|
40
|
+
? `Server "${id}" was discovered from catalog "${from}" and is not declared under mcp.servers; ` +
|
|
41
|
+
`policy forbids undeclared servers (allowUnknownServers=false) — blocking. Add it to mcp.servers to allow it.`
|
|
42
|
+
: `Server "${id}" was discovered from catalog "${from}", not declared under mcp.servers.`,
|
|
43
|
+
};
|
|
44
|
+
// Catalog provenance on its own is not a defect, so the permissive path does
|
|
45
|
+
// not tax the composite score; it only says where the server came from.
|
|
46
|
+
return { findings: [finding], score: strict ? 0 : 1, fatal: strict };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=origin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAQ5C;;;;;;;;;;;;;;;;;;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,6EAA6E;QAC7E,sDAAsD;QACtD,MAAM,EAAE,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACxC,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAClC,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,EAAE,kCAAkC,IAAI,2CAA2C;oBAC9F,8GAA8G;gBAChH,CAAC,CAAC,WAAW,EAAE,kCAAkC,IAAI,oCAAoC;SAC5F,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,91 @@
|
|
|
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
|
+
* Has the program behind this server changed since approval?
|
|
19
|
+
*
|
|
20
|
+
* Silent when the pin predates {@link PinnedServer.identityHash}: an absent
|
|
21
|
+
* value is "not recorded", never "changed". The next `approve()` records it.
|
|
22
|
+
*/
|
|
23
|
+
private identityDrift;
|
|
24
|
+
/**
|
|
25
|
+
* The tool-def set has no canonical form (see {@link canonicalToolsHash}), so no
|
|
26
|
+
* hash can be produced for it. What that means depends on whether a pin exists:
|
|
27
|
+
*
|
|
28
|
+
* - **No pin yet** — nothing is being contradicted; the pin simply cannot be
|
|
29
|
+
* established, which is a `medium` warning and not evidence of an attack.
|
|
30
|
+
* - **Pin exists** — the snapshot the user approved can no longer be re-verified,
|
|
31
|
+
* which is indistinguishable from drift and is treated as drift. Otherwise a
|
|
32
|
+
* server could disarm the rug-pull defence at will by adding one fractional
|
|
33
|
+
* number to a schema.
|
|
34
|
+
*/
|
|
35
|
+
private uncanonical;
|
|
36
|
+
/**
|
|
37
|
+
* Persist the current tool-def set as the trusted snapshot for this server.
|
|
38
|
+
* Called by Warden.approve() once a user has accepted the connection.
|
|
39
|
+
*
|
|
40
|
+
* Throws {@link CanonicalizationError} when the set has no canonical form; the
|
|
41
|
+
* caller (`McpHost.connect`) already reports a pin failure as a degraded
|
|
42
|
+
* rug-pull defence rather than failing the connection.
|
|
43
|
+
*/
|
|
44
|
+
pin(server: McpServerRef, tools: ToolDef[]): Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Marker used where a tool-def hash is *recorded* rather than compared, and the set
|
|
48
|
+
* turned out to have no canonical form. It can never collide with a sha256 hex
|
|
49
|
+
* digest, so anything that later compares it simply reports a mismatch.
|
|
50
|
+
*/
|
|
51
|
+
export declare const UNCANONICAL_TOOLS_HASH = "uncanonical:non-canonical-tool-defs";
|
|
52
|
+
/**
|
|
53
|
+
* sha256 over the canonical tool-def set: tools sorted by name, each reduced to
|
|
54
|
+
* the security-relevant fields (name, description, schema), serialised with
|
|
55
|
+
* {@link canonicalize} — RFC 8785 (JCS) as profiled in `awr/SPEC.md` §4.
|
|
56
|
+
*
|
|
57
|
+
* This digest is quoted in receipts and re-checked elsewhere (`argus verify`, the
|
|
58
|
+
* sealed mandate), so it has to be reproducible by an implementation that is not
|
|
59
|
+
* this one. Two consequences:
|
|
60
|
+
*
|
|
61
|
+
* - **Ordering is by UTF-16 code unit, never `localeCompare`.** `localeCompare`
|
|
62
|
+
* depends on the host locale and ICU version — `["a", "B"]` sorts one way under
|
|
63
|
+
* `en-US` and another under the C locale — so a digest built on it is not even
|
|
64
|
+
* stable across two machines running this same code, let alone across languages.
|
|
65
|
+
* JavaScript's `<`/`>` on strings compare UTF-16 code units, which is exactly
|
|
66
|
+
* RFC 8785 §3.2.3's rule.
|
|
67
|
+
* - **Non-integer numbers are refused** (`AWR-CANON-001`, SPEC §4.3) rather than
|
|
68
|
+
* serialised. Whether `1` is an integer or a double is a language accident that
|
|
69
|
+
* silently changes the bytes, so a schema carrying e.g. `"multipleOf": 0.01` has
|
|
70
|
+
* no canonical form here and this function throws
|
|
71
|
+
* {@link CanonicalizationError}. Callers must handle that — refusing to emit a
|
|
72
|
+
* digest is honest; emitting one nobody else can reproduce is not.
|
|
73
|
+
*/
|
|
74
|
+
export declare function canonicalToolsHash(tools: ToolDef[]): string;
|
|
75
|
+
/**
|
|
76
|
+
* Like {@link canonicalToolsHash} but total: returns `undefined` instead of
|
|
77
|
+
* throwing, for call sites that record a hash rather than enforce one.
|
|
78
|
+
*/
|
|
79
|
+
export declare function tryCanonicalToolsHash(tools: ToolDef[]): string | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* sha256 over the server's launch identity: transport, command, args, url, name.
|
|
82
|
+
*
|
|
83
|
+
* Deliberately NOT the whole `McpServerRef`: `catalog` is provenance rather than
|
|
84
|
+
* identity (the origin gate is what has an opinion about it), and `env` holds
|
|
85
|
+
* secrets that must not be hashed into a value stored on disk next to the pin.
|
|
86
|
+
*
|
|
87
|
+
* Missing fields serialize as empty rather than being omitted, so a server that
|
|
88
|
+
* gains a `url` reads as a change instead of hashing the same as before.
|
|
89
|
+
*/
|
|
90
|
+
export declare function serverIdentityHash(server: McpServerRef): string;
|
|
91
|
+
//# sourceMappingURL=pinning.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pinning.d.ts","sourceRoot":"","sources":["../src/pinning.ts"],"names":[],"mappings":"AAGA,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;IAqDjE;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAerB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,WAAW;IAgCnB;;;;;;;OAOG;IACG,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAUjE;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;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAS/D"}
|