@vantageos/mcp-doctor 0.5.0 → 0.6.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/doctor.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/doctor.ts
4
4
  import { readFileSync as readFileSync13, realpathSync } from "fs";
5
- import { dirname as dirname12, join as join20, resolve as resolve2 } from "path";
5
+ import { dirname as dirname13, join as join20, resolve as resolve3 } from "path";
6
6
  import { fileURLToPath } from "url";
7
7
 
8
8
  // src/scan.ts
@@ -129,9 +129,14 @@ function loadTree(root, backendRoot) {
129
129
  var COM = 1;
130
130
  var CODE = 2;
131
131
  var maskCache = /* @__PURE__ */ new WeakMap();
132
+ var maskEnd = /* @__PURE__ */ new WeakMap();
132
133
  function buildCodeMask(file) {
133
134
  const mask = [];
134
135
  let mode = "code";
136
+ const jsxFile = /\.[tj]sx$/.test(file.path);
137
+ let jsxDepth = 0;
138
+ let jstrQuote = "";
139
+ const jsxExprStack = [];
135
140
  const tplStack = [];
136
141
  let braceDepth = 0;
137
142
  let prevSig = "";
@@ -193,6 +198,75 @@ function buildCodeMask(file) {
193
198
  } else i++;
194
199
  continue;
195
200
  }
201
+ if (mode === "jstr") {
202
+ if (c === jstrQuote) mode = "jtag";
203
+ i++;
204
+ continue;
205
+ }
206
+ if (mode === "jtag") {
207
+ if (c === "/" && n === ">") {
208
+ row[i] = CODE;
209
+ row[i + 1] = CODE;
210
+ i += 2;
211
+ mode = jsxDepth > 0 ? "jtext" : "code";
212
+ if (mode === "code") prevSig = "x";
213
+ } else if (c === ">") {
214
+ row[i] = CODE;
215
+ i++;
216
+ jsxDepth++;
217
+ mode = "jtext";
218
+ } else if (c === '"' || c === "'") {
219
+ jstrQuote = c;
220
+ mode = "jstr";
221
+ i++;
222
+ } else if (c === "{") {
223
+ row[i] = CODE;
224
+ jsxExprStack.push({ depth: braceDepth, back: "jtag", savedJsxDepth: jsxDepth });
225
+ jsxDepth = 0;
226
+ braceDepth++;
227
+ i++;
228
+ mode = "code";
229
+ prevSig = "{";
230
+ } else {
231
+ if (c !== " " && c !== " ") row[i] = CODE;
232
+ i++;
233
+ }
234
+ continue;
235
+ }
236
+ if (mode === "jtext") {
237
+ if (c === "{") {
238
+ row[i] = CODE;
239
+ jsxExprStack.push({ depth: braceDepth, back: "jtext", savedJsxDepth: jsxDepth });
240
+ jsxDepth = 0;
241
+ braceDepth++;
242
+ i++;
243
+ mode = "code";
244
+ prevSig = "{";
245
+ } else if (c === "<" && n === "/") {
246
+ row[i] = CODE;
247
+ row[i + 1] = CODE;
248
+ i += 2;
249
+ mode = "jclose";
250
+ } else if (c === "<" && n !== void 0 && /[A-Za-z_$>]/.test(n)) {
251
+ row[i] = CODE;
252
+ i++;
253
+ mode = "jtag";
254
+ } else i++;
255
+ continue;
256
+ }
257
+ if (mode === "jclose") {
258
+ if (c === ">") {
259
+ row[i] = CODE;
260
+ i++;
261
+ jsxDepth = Math.max(0, jsxDepth - 1);
262
+ mode = jsxDepth > 0 ? "jtext" : "code";
263
+ if (mode === "code") prevSig = "x";
264
+ } else {
265
+ if (c !== " " && c !== " ") row[i] = CODE;
266
+ i++;
267
+ }
268
+ continue;
269
+ }
196
270
  if (mode === "sq" || mode === "dq") {
197
271
  if (c === "\\") i += 2;
198
272
  else if (mode === "sq" && c === "'" || mode === "dq" && c === '"') {
@@ -260,6 +334,11 @@ function buildCodeMask(file) {
260
334
  i = j;
261
335
  continue;
262
336
  }
337
+ if (jsxFile && c === "<" && n !== void 0 && /[A-Za-z_$>]/.test(n) && (prevSig === "" || "(,=:?&|{};![".includes(prevSig) || wordBefore === "return" || prevSig === ">" && /=>\s*$/.test(s.slice(0, i)) || prevSig === "}") && !/^<[A-Za-z_$][\w$]*\s+extends\b/.test(s.slice(i)) && !/^<[A-Za-z_$][\w$]*\s*,/.test(s.slice(i))) {
338
+ i++;
339
+ mode = "jtag";
340
+ continue;
341
+ }
263
342
  if (c === '"' || c === "'" || c === "`") {
264
343
  mode = c === '"' ? "dq" : c === "'" ? "sq" : "tpl";
265
344
  i++;
@@ -279,6 +358,14 @@ function buildCodeMask(file) {
279
358
  i++;
280
359
  continue;
281
360
  }
361
+ const jx = jsxExprStack[jsxExprStack.length - 1];
362
+ if (jx && jx.depth === braceDepth) {
363
+ jsxExprStack.pop();
364
+ jsxDepth = jx.savedJsxDepth;
365
+ mode = jx.back;
366
+ i++;
367
+ continue;
368
+ }
282
369
  }
283
370
  if (c !== " " && c !== " ") prevSig = c;
284
371
  i++;
@@ -286,6 +373,7 @@ function buildCodeMask(file) {
286
373
  if (mode === "sq" || mode === "dq" || mode === "re" || mode === "recls") mode = "code";
287
374
  mask.push(row);
288
375
  }
376
+ if (mode !== "code") maskEnd.set(file, mode);
289
377
  return mask;
290
378
  }
291
379
  function maskOf(file) {
@@ -296,6 +384,12 @@ function maskOf(file) {
296
384
  }
297
385
  return mask;
298
386
  }
387
+ function unterminatedContext(file) {
388
+ maskOf(file);
389
+ const m = maskEnd.get(file);
390
+ if (!m) return null;
391
+ return m.startsWith("j") ? "an unterminated JSX element" : m === "block" ? "an unterminated block comment" : m === "tpl" ? "an unterminated template literal" : `an unterminated ${m} context`;
392
+ }
299
393
  function isCodeAt(file, n, col) {
300
394
  const mask = maskOf(file);
301
395
  return mask[n - 1]?.[col] === CODE;
@@ -752,6 +846,34 @@ var MATCHERS = [
752
846
  re: /\bcreateHash\s*\(|\bcreateHmac\s*\(|\bsubtle\s*\.\s*(digest|sign|verify)\s*\(/,
753
847
  accept: (m, ctx) => !digestsStaticInput(m, ctx),
754
848
  sample: 'const h = await crypto.subtle.digest("SHA-256", data);'
849
+ },
850
+ // ── Minting / signing: shown, not counted (category "mints-token") ──────────
851
+ {
852
+ id: "mint-sign-jwt",
853
+ category: "mints-token",
854
+ re: /\bnew\s+SignJWT\s*\(/,
855
+ sample: "return new SignJWT({ sub }).setProtectedHeader({ alg }).sign(key);"
856
+ },
857
+ {
858
+ id: "mint-jwt-sign",
859
+ category: "mints-token",
860
+ re: /\b(jwt|jsonwebtoken)\.sign\s*\(/,
861
+ sample: "const token = jwt.sign(claims, privateKey);"
862
+ },
863
+ {
864
+ // A key that can only be a private one by construction.
865
+ id: "mint-private-key-import",
866
+ category: "mints-token",
867
+ re: /\b(importPKCS8|createPrivateKey)\s*\(/,
868
+ sample: "const key = await importPKCS8(pem, 'RS256');"
869
+ },
870
+ {
871
+ // importJWK is neutral (it also loads PUBLIC verification keys); it is a minting
872
+ // site only when its argument is named as a private/secret key.
873
+ id: "mint-private-key-import",
874
+ category: "mints-token",
875
+ re: /\bimportJWK\s*\(\s*[\w$.]*(?:priv|secret)[\w$.]*/i,
876
+ sample: 'const key = await importJWK(privateJwk, "RS256");'
755
877
  }
756
878
  ];
757
879
  function biteSample(mt, text) {
@@ -782,6 +904,12 @@ function localIdentityPositiveControls() {
782
904
  out["negative:switch-on-unrelated-field"] = !bites('switch (row.kind) { case "a": return 1; }');
783
905
  out["negative:literal-array-unrelated"] = !bites("return [kind].includes(row.status);");
784
906
  out["negative:localecompare-unrelated"] = !bites("return a.name.localeCompare(b.name) === 0;");
907
+ out["negative:public-jwk-import-is-not-minting"] = !bites(
908
+ 'const key = await importJWK(publicJwk, "RS256");'
909
+ );
910
+ out["negative:verify-is-not-minting"] = !MATCHERS.filter(
911
+ (m) => m.category === "mints-token"
912
+ ).some((mt) => biteSample(mt, "const { payload } = await jwtVerify(token, keys);"));
785
913
  out["negative:object-is-nullish"] = !bites("return Object.is(row.orgId, undefined);");
786
914
  return out;
787
915
  }
@@ -861,19 +989,55 @@ function unreadableReason(file) {
861
989
  if (file.lines.some((l) => l.raw.includes("\0") || l.raw.includes("\uFFFD"))) {
862
990
  return "file is not valid UTF-8 text";
863
991
  }
992
+ const open = unterminatedContext(file);
993
+ if (open) return `the file ends inside ${open} \u2014 the tail cannot be read as code`;
864
994
  const pairs = [
865
995
  ["{", "}"],
866
996
  ["(", ")"],
867
997
  ["[", "]"]
868
998
  ];
869
999
  const off = [];
870
- for (const [open, close] of pairs) {
1000
+ for (const [open2, close] of pairs) {
871
1001
  let net = 0;
872
- for (const l of file.lines) net += countInCode(file, l, open) - countInCode(file, l, close);
873
- if (net !== 0) off.push(`${open}${close} ${net > 0 ? "+" : ""}${net}`);
1002
+ for (const l of file.lines) net += countInCode(file, l, open2) - countInCode(file, l, close);
1003
+ if (net !== 0) off.push(`${open2}${close} ${net > 0 ? "+" : ""}${net}`);
874
1004
  }
875
1005
  return off.length > 0 ? `brackets never balance (${off.join(", ")}) \u2014 the file cannot be read as code` : null;
876
1006
  }
1007
+ function signingChainLines(file) {
1008
+ const inChain = /* @__PURE__ */ new Set();
1009
+ const lines = file.lines;
1010
+ for (let i = 0; i < lines.length; i++) {
1011
+ const l = lines[i];
1012
+ if (l.comment) continue;
1013
+ const start = l.raw.match(/\bnew\s+SignJWT\s*\(|\b(?:jwt|jsonwebtoken)\.sign\s*\(/);
1014
+ if (!start || start.index === void 0 || !isCodeAt(file, l.n, start.index)) continue;
1015
+ let end = -1;
1016
+ if (/SignJWT/.test(start[0])) {
1017
+ for (let j = i; j < Math.min(lines.length, i + 60); j++) {
1018
+ if (!lines[j].comment && /\.sign\s*\(/.test(lines[j].raw)) {
1019
+ end = j;
1020
+ break;
1021
+ }
1022
+ }
1023
+ }
1024
+ if (end < 0) {
1025
+ let depth = 0;
1026
+ let opened = false;
1027
+ for (let j = i; j < Math.min(lines.length, i + 60); j++) {
1028
+ const raw = j === i ? lines[j].raw.slice(start.index) : lines[j].raw;
1029
+ depth += (raw.match(/\(/g) ?? []).length - (raw.match(/\)/g) ?? []).length;
1030
+ if (/\(/.test(raw)) opened = true;
1031
+ if (opened && depth <= 0) {
1032
+ end = j;
1033
+ break;
1034
+ }
1035
+ }
1036
+ }
1037
+ for (let j = i; j <= (end < 0 ? i : end); j++) inChain.add(lines[j].n);
1038
+ }
1039
+ return inChain;
1040
+ }
877
1041
  function findLocalIdentitySites(tree) {
878
1042
  const pkg = tree.packageJson;
879
1043
  const selfExempt = !pkg?.unreadable && pkg?.json.name === CLOUD_IDENTITY_PACKAGE;
@@ -882,6 +1046,7 @@ function findLocalIdentitySites(tree) {
882
1046
  violations: [],
883
1047
  exempt: [],
884
1048
  delegatedCalls: 0,
1049
+ minting: [],
885
1050
  unreadable: []
886
1051
  };
887
1052
  for (const file of tree.files) {
@@ -890,6 +1055,7 @@ function findLocalIdentitySites(tree) {
890
1055
  const bindings = cloudIdentityBindings(file);
891
1056
  const statics = staticConstants(file);
892
1057
  const catalog = [...MATCHERS, ...aliasMatchers(file)];
1058
+ const signingChain = signingChainLines(file);
893
1059
  scan.delegatedCalls += countDelegatedCalls(file, bindings);
894
1060
  for (const line of file.lines) {
895
1061
  if (line.comment) continue;
@@ -909,6 +1075,16 @@ function findLocalIdentitySites(tree) {
909
1075
  }
910
1076
  if (!hit) continue;
911
1077
  const decl = enclosingDecl(file, line.n);
1078
+ if (hit.category === "mints-token" || signingChain.has(line.n)) {
1079
+ scan.minting.push({
1080
+ file: file.rel,
1081
+ line: line.n,
1082
+ primitive: hit.category === "mints-token" ? hit.id : `signing-payload (${hit.id})`,
1083
+ category: "mints-token",
1084
+ enclosing: decl
1085
+ });
1086
+ continue;
1087
+ }
912
1088
  const site = {
913
1089
  file: file.rel,
914
1090
  line: line.n,
@@ -932,6 +1108,10 @@ function exemptSuffix(exempt) {
932
1108
  const rows = exempt.map((e) => `${describeSite(e)} \u2014 "${e.justification}"`);
933
1109
  return `; EXEMPT (${exempt.length}): ${rows.join("; ")}`;
934
1110
  }
1111
+ function mintingSuffix(minting) {
1112
+ if (minting.length === 0) return "";
1113
+ return `; MINTING/SIGNING (${minting.length}, shown, not counted): ${minting.map(describeSite).join("; ")}`;
1114
+ }
935
1115
  function makeLocalIdentity(opts = {}) {
936
1116
  const ceiling = opts.ceiling ?? 0;
937
1117
  if (!Number.isInteger(ceiling) || ceiling < 0) {
@@ -967,7 +1147,7 @@ function makeLocalIdentity(opts = {}) {
967
1147
  id,
968
1148
  anchor: `${v.file}:${v.line}`,
969
1149
  state: "non-conforming",
970
- verdict: `${s.violations.length} local identity primitive(s), ceiling ${ceiling} (positive-control OK): ${listed}${exemptSuffix(s.exempt)} -> delegate to ${CLOUD_IDENTITY_PACKAGE} (validateMasterBearer, verifyClerkSessionToken, isMasterScope, ...) or justify the site with "// ${EXEMPT_MARKER} <reason>"`
1150
+ verdict: `${s.violations.length} local identity primitive(s), ceiling ${ceiling} (positive-control OK): ${listed}${exemptSuffix(s.exempt)}${mintingSuffix(s.minting)} -> delegate to ${CLOUD_IDENTITY_PACKAGE} (validateMasterBearer, verifyClerkSessionToken, isMasterScope, ...) or justify the site with "// ${EXEMPT_MARKER} <reason>"`
971
1151
  };
972
1152
  } else if (s.violations.length > 0) {
973
1153
  const v = s.violations[0];
@@ -975,14 +1155,14 @@ function makeLocalIdentity(opts = {}) {
975
1155
  id,
976
1156
  anchor: `${v.file}:${v.line}`,
977
1157
  state: "present",
978
- verdict: `${s.violations.length} local identity primitive(s) at or under the ceiling ${ceiling} \u2014 a ratchet, not a clean bill: ${listed}${exemptSuffix(s.exempt)}`
1158
+ verdict: `${s.violations.length} local identity primitive(s) at or under the ceiling ${ceiling} \u2014 a ratchet, not a clean bill: ${listed}${exemptSuffix(s.exempt)}${mintingSuffix(s.minting)}`
979
1159
  };
980
1160
  } else {
981
1161
  decided = {
982
1162
  id,
983
1163
  anchor: s.exempt[0] ? `${s.exempt[0].file}:${s.exempt[0].line}` : "-",
984
1164
  state: "present",
985
- verdict: `0 local identity primitives; ${delegated} (positive-control OK)${exemptSuffix(s.exempt)}`
1165
+ verdict: `0 local identity primitives; ${delegated} (positive-control OK)${exemptSuffix(s.exempt)}${mintingSuffix(s.minting)}`
986
1166
  };
987
1167
  }
988
1168
  return settle(id, decided, s.unreadable);
@@ -1169,6 +1349,237 @@ function resolveNameBindings(tree) {
1169
1349
  }
1170
1350
  return bindings;
1171
1351
  }
1352
+ var READ_STRING_LITERAL_RE = /^(?:"((?:[^"\\\n]|\\.)*)"|'((?:[^'\\\n]|\\.)*)'|`((?:[^`\\$]|\\.|\$(?!\{))*)`)/;
1353
+ function readDescriptionValue(text) {
1354
+ let rest = text.replace(/^\s+/, "");
1355
+ let acc = "";
1356
+ let first = true;
1357
+ for (; ; ) {
1358
+ const m = rest.match(READ_STRING_LITERAL_RE);
1359
+ if (!m) {
1360
+ const expr = rest.split("\n")[0].trim().slice(0, 60);
1361
+ return { kind: "non-literal", expr: first ? expr : `\u2026+ ${expr}` };
1362
+ }
1363
+ first = false;
1364
+ acc += m[1] ?? m[2] ?? m[3] ?? "";
1365
+ rest = rest.slice(m[0].length).replace(/^\s+/, "");
1366
+ if (rest.startsWith("+")) {
1367
+ rest = rest.slice(1).replace(/^\s+/, "");
1368
+ continue;
1369
+ }
1370
+ return { kind: "literal", text: acc };
1371
+ }
1372
+ }
1373
+ function constExpression(tree, name) {
1374
+ const decl = new RegExp(`\\bconst\\s+${name.replace(/\$/g, "\\$")}\\s*(?::[^=\\n]+)?=(?!=)`);
1375
+ let found = null;
1376
+ let count = 0;
1377
+ for (const file of tree.files) {
1378
+ const code = file.lines.map((l) => l.comment ? "" : l.raw);
1379
+ for (let i = 0; i < code.length; i++) {
1380
+ const m = code[i].match(decl);
1381
+ if (!m) continue;
1382
+ count++;
1383
+ let text = code[i].slice((m.index ?? 0) + m[0].length);
1384
+ let depth = 0;
1385
+ let j = i;
1386
+ for (; ; ) {
1387
+ for (const ch of text) {
1388
+ if ("([{".includes(ch)) depth++;
1389
+ else if (")]}".includes(ch)) depth--;
1390
+ }
1391
+ if (depth <= 0 && /;\s*$/.test(text)) break;
1392
+ if (++j >= code.length || j > i + 60) break;
1393
+ text += `
1394
+ ${code[j]}`;
1395
+ }
1396
+ found = text.replace(/;\s*$/, "").trim();
1397
+ }
1398
+ }
1399
+ return count === 1 ? found : null;
1400
+ }
1401
+ var wrapperScans = /* @__PURE__ */ new WeakMap();
1402
+ var wrapperOpeners = /* @__PURE__ */ new WeakMap();
1403
+ var FUNCTION_DECL_RE = /^\s*(?:export\s+)?(?:default\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*(?:<[^>(]*>)?\s*\(|^\s*(?:export\s+)?const\s+([A-Za-z_$][\w$]*)\s*(?::[^=]+)?=\s*(?:async\s*)?\(/;
1404
+ function splitArgs(text, open, max = Number.POSITIVE_INFINITY) {
1405
+ const args = [];
1406
+ let depth = 0;
1407
+ let start = open + 1;
1408
+ for (let i = open; i < text.length; i++) {
1409
+ const c = text[i];
1410
+ if (c === '"' || c === "'" || c === "`") {
1411
+ for (i++; i < text.length && text[i] !== c; i++) if (text[i] === "\\") i++;
1412
+ continue;
1413
+ }
1414
+ if (c === "/" && text[i + 1] === "/") {
1415
+ while (i < text.length && text[i] !== "\n") i++;
1416
+ continue;
1417
+ }
1418
+ if (c === "/" && text[i + 1] === "*") {
1419
+ const e = text.indexOf("*/", i + 2);
1420
+ if (e < 0) return null;
1421
+ i = e + 1;
1422
+ continue;
1423
+ }
1424
+ if (c === "(" || c === "[" || c === "{") depth++;
1425
+ else if (c === ")" || c === "]" || c === "}") {
1426
+ depth--;
1427
+ if (depth === 0) {
1428
+ args.push(text.slice(start, i).trim());
1429
+ return { args, end: i };
1430
+ }
1431
+ } else if (c === "," && depth === 1) {
1432
+ args.push(text.slice(start, i).trim());
1433
+ start = i + 1;
1434
+ if (args.length >= max) return { args, end: i };
1435
+ }
1436
+ }
1437
+ return null;
1438
+ }
1439
+ function scanWrappers(tree) {
1440
+ const cached = wrapperScans.get(tree);
1441
+ if (cached) return cached;
1442
+ const scan = {
1443
+ sites: [],
1444
+ unresolved: [],
1445
+ internal: /* @__PURE__ */ new Set(),
1446
+ descriptions: /* @__PURE__ */ new Map()
1447
+ };
1448
+ wrapperScans.set(tree, scan);
1449
+ const wrappers = [];
1450
+ for (const file of tree.files) {
1451
+ const text = file.lines.map((l) => l.comment ? " ".repeat(l.raw.length) : l.raw).join("\n");
1452
+ const lineStart = [];
1453
+ let off = 0;
1454
+ for (const l of file.lines) {
1455
+ lineStart.push(off);
1456
+ off += l.raw.length + 1;
1457
+ }
1458
+ const lineOf = (idx) => {
1459
+ let lo = 0;
1460
+ let hi = lineStart.length - 1;
1461
+ while (lo < hi) {
1462
+ const mid = lo + hi + 1 >> 1;
1463
+ if (lineStart[mid] <= idx) lo = mid;
1464
+ else hi = mid - 1;
1465
+ }
1466
+ return lo + 1;
1467
+ };
1468
+ const aliases = /* @__PURE__ */ new Set(["registerTool", "tool"]);
1469
+ for (const m of text.matchAll(
1470
+ /\bconst\s+([A-Za-z_$][\w$]*)\s*=[^;\n]*\.(?:registerTool|tool)\b/g
1471
+ )) {
1472
+ aliases.add(m[1]);
1473
+ }
1474
+ const callRe = new RegExp(
1475
+ `(?:\\b(?:${[...aliases].join("|")})|\\.(?:registerTool|tool))\\s*\\(\\s*([A-Za-z_$][\\w$]*)\\s*,`,
1476
+ "g"
1477
+ );
1478
+ file.lines.forEach((line, i) => {
1479
+ if (line.comment) return;
1480
+ const d = line.raw.match(FUNCTION_DECL_RE);
1481
+ if (!d) return;
1482
+ const name = d[1] ?? d[2];
1483
+ if (name === "registerTool" || name === "tool") return;
1484
+ const open = lineStart[i] + (d.index ?? 0) + d[0].length - 1;
1485
+ const params = splitArgs(text, open);
1486
+ if (!params) return;
1487
+ const paramNames = params.args.map(
1488
+ (a) => a.replace(/^\.\.\./, "").match(/^([A-Za-z_$][\w$]*)/)?.[1] ?? null
1489
+ );
1490
+ const bodyOpen = text.indexOf("{", params.end);
1491
+ if (bodyOpen < 0) return;
1492
+ let depth = 0;
1493
+ let bodyEnd = -1;
1494
+ for (let k = bodyOpen; k < text.length; k++) {
1495
+ if (text[k] === "{") depth++;
1496
+ else if (text[k] === "}" && --depth === 0) {
1497
+ bodyEnd = k;
1498
+ break;
1499
+ }
1500
+ }
1501
+ if (bodyEnd < 0) return;
1502
+ const body = text.slice(bodyOpen, bodyEnd);
1503
+ const idxs = /* @__PURE__ */ new Set();
1504
+ let descIdx = -1;
1505
+ for (const c of body.matchAll(callRe)) {
1506
+ const k = paramNames.indexOf(c[1]);
1507
+ if (k < 0) continue;
1508
+ idxs.add(k);
1509
+ scan.internal.add(`${file.rel}:${lineOf(bodyOpen + (c.index ?? 0))}`);
1510
+ const after = body.slice((c.index ?? 0) + c[0].length, (c.index ?? 0) + c[0].length + 3e3);
1511
+ const dm = after.match(/(?:^|[\s{,])description\b\s*(?::\s*([A-Za-z_$][\w$]*))?\s*[,}]/);
1512
+ if (dm) {
1513
+ const ref = dm[1] ?? "description";
1514
+ descIdx = paramNames.indexOf(ref);
1515
+ }
1516
+ }
1517
+ if (idxs.size !== 1) return;
1518
+ wrappers.push({
1519
+ name,
1520
+ nameIdx: [...idxs][0],
1521
+ descIdx,
1522
+ file,
1523
+ declLine: line.n,
1524
+ endLine: lineOf(bodyEnd)
1525
+ });
1526
+ });
1527
+ }
1528
+ for (const w of wrappers) {
1529
+ const callRe = new RegExp(`(?<![\\w$.])${w.name.replace(/\$/g, "\\$")}\\s*\\(`, "g");
1530
+ for (const file of tree.files) {
1531
+ const text = file.lines.map((l) => l.comment ? " ".repeat(l.raw.length) : l.raw).join("\n");
1532
+ const lineStart = [];
1533
+ let off = 0;
1534
+ for (const l of file.lines) {
1535
+ lineStart.push(off);
1536
+ off += l.raw.length + 1;
1537
+ }
1538
+ file.lines.forEach((line, i) => {
1539
+ if (line.comment) return;
1540
+ if (file === w.file && line.n >= w.declLine && line.n <= w.endLine) return;
1541
+ if (/^\s*(?:export\s+)?(?:async\s+)?function\b|^\s*import\b|^\s*\}?\s*from\b/.test(line.raw))
1542
+ return;
1543
+ for (const m of line.raw.matchAll(callRe)) {
1544
+ if (!isCodeAtCached(file, line.n, m.index ?? 0)) continue;
1545
+ const open = lineStart[i] + (m.index ?? 0) + m[0].length - 1;
1546
+ const parsed = splitArgs(text, open, Math.max(w.nameIdx, w.descIdx) + 1);
1547
+ const anchor = `${file.rel}:${line.n}`;
1548
+ if (!parsed || parsed.args.length <= w.nameIdx) {
1549
+ scan.unresolved.push(anchor);
1550
+ continue;
1551
+ }
1552
+ let nameArg = parsed.args[w.nameIdx];
1553
+ if (/^[A-Za-z_$][\w$]*$/.test(nameArg))
1554
+ nameArg = constExpression(tree, nameArg) ?? nameArg;
1555
+ const nameVal = readDescriptionValue(nameArg);
1556
+ if (nameVal.kind !== "literal" || nameVal.text === "") {
1557
+ scan.unresolved.push(anchor);
1558
+ continue;
1559
+ }
1560
+ const toolName = nameVal.text;
1561
+ scan.sites.push({ name: toolName, anchor });
1562
+ let set = wrapperOpeners.get(file);
1563
+ if (!set) {
1564
+ set = /* @__PURE__ */ new Set();
1565
+ wrapperOpeners.set(file, set);
1566
+ }
1567
+ set.add(line.n);
1568
+ if (w.descIdx >= 0 && parsed.args.length > w.descIdx) {
1569
+ scan.descriptions.set(anchor, parsed.args[w.descIdx]);
1570
+ }
1571
+ }
1572
+ });
1573
+ }
1574
+ }
1575
+ return scan;
1576
+ }
1577
+ function isCodeAtCached(file, n, col) {
1578
+ return isCodeAt(file, n, col);
1579
+ }
1580
+ function wrapperDescriptionArg(tree, anchor) {
1581
+ return scanWrappers(tree).descriptions.get(anchor);
1582
+ }
1172
1583
  function findRegisteredTools(tree) {
1173
1584
  const bindings = resolveNameBindings(tree);
1174
1585
  const out = [];
@@ -1227,6 +1638,7 @@ function findRegisteredTools(tree) {
1227
1638
  if (hasSchema) push(m[2], `${file.rel}:${line.n}`);
1228
1639
  }
1229
1640
  }
1641
+ for (const w of scanWrappers(tree).sites) push(w.name, w.anchor);
1230
1642
  return out;
1231
1643
  }
1232
1644
  var DESCRIPTOR_KEY_RE = new RegExp(`^(\\s*)name:\\s*["'\`](${NAME_LITERAL})["'\`]\\s*,?\\s*$`);
@@ -1297,7 +1709,9 @@ function findUnresolvedToolSites(tree) {
1297
1709
  }
1298
1710
  if (awaitingName) out.push(openerAnchor);
1299
1711
  }
1300
- return out;
1712
+ const wrappers = scanWrappers(tree);
1713
+ const kept = out.filter((a) => !wrappers.internal.has(a));
1714
+ return [...kept, ...wrappers.unresolved];
1301
1715
  }
1302
1716
  function findListReadBuilders(tree) {
1303
1717
  const out = [];
@@ -1350,6 +1764,45 @@ function sdkFirstAnchor(tree) {
1350
1764
  }
1351
1765
  return null;
1352
1766
  }
1767
+ var ANNOTATIONS_NONLITERAL_RE = /\bannotations\s*:\s*([^\s{][^\n]*?)\s*,?\s*$/;
1768
+ var SIMPLE_IDENT_RE = /^([A-Za-z_$][A-Za-z0-9_$]*)(?:\s+(?:as\s+const|satisfies\s+[\w.<>\[\]]+))?$/;
1769
+ function resolveConstObjectLines(tree, home, ident, seen = /* @__PURE__ */ new Set()) {
1770
+ if (seen.has(ident)) return null;
1771
+ seen.add(ident);
1772
+ const openRe = new RegExp(
1773
+ `\\bconst\\s+${ident.replace(/\$/g, "\\$")}\\s*(?::[^=]+)?=\\s*(?:Object\\.freeze\\(\\s*)?\\{`
1774
+ );
1775
+ const found = [];
1776
+ for (const file of tree.files) {
1777
+ for (let i = 0; i < file.lines.length; i++) {
1778
+ const l = file.lines[i];
1779
+ if (!l.comment && openRe.test(l.raw)) found.push({ file, index: i });
1780
+ }
1781
+ }
1782
+ const local = found.filter((f) => f.file === home);
1783
+ const pick = local.length === 1 ? local[0] : local.length === 0 && found.length === 1 ? found[0] : null;
1784
+ if (!pick) return null;
1785
+ const out = [];
1786
+ let depth = 0;
1787
+ const started = true;
1788
+ for (let i = pick.index; i < pick.file.lines.length; i++) {
1789
+ const l = pick.file.lines[i];
1790
+ if (l.comment) continue;
1791
+ const text = i === pick.index ? l.raw.slice(l.raw.search(openRe)) : l.raw;
1792
+ out.push(text);
1793
+ depth += (text.match(/\{/g) ?? []).length - (text.match(/\}/g) ?? []).length;
1794
+ const spread = text.match(/\.\.\.\s*([A-Za-z_$][A-Za-z0-9_$]*)\s*[,}]?\s*$/);
1795
+ if (spread) {
1796
+ const inner = resolveConstObjectLines(tree, pick.file, spread[1], seen);
1797
+ if (!inner) return null;
1798
+ out.push(...inner);
1799
+ } else if (/\.\.\./.test(text)) {
1800
+ return null;
1801
+ }
1802
+ if (started && depth <= 0) return out;
1803
+ }
1804
+ return null;
1805
+ }
1353
1806
  var VERB_FIELD_RE = /\bverb\s*:\s*["'`]([A-Za-z][A-Za-z-]*)["'`]/;
1354
1807
  var TABLE_FIELD_RE = /\btable\s*:\s*["'`]([A-Za-z0-9_.-]+)["'`]/;
1355
1808
  var MECHANISM_FIELD_RE = /\b(?:searchMechanism|mechanism)\s*:\s*["'`]([A-Za-z0-9_-]+(?:\([^)]*\))?)["'`]/;
@@ -1437,7 +1890,7 @@ function callOpenerLine(file, siteLine) {
1437
1890
  for (let n = siteLine; n >= Math.max(1, siteLine - CALL_OPENER_LOOKBACK); n--) {
1438
1891
  const line = file.lines[n - 1];
1439
1892
  if (!line || line.comment) continue;
1440
- if (TOOL_INLINE_LITERAL_RE.test(line.raw) || TOOL_INLINE_IDENT_RE.test(line.raw) || OPENER_RE.test(line.raw)) {
1893
+ if (TOOL_INLINE_LITERAL_RE.test(line.raw) || TOOL_INLINE_IDENT_RE.test(line.raw) || OPENER_RE.test(line.raw) || wrapperOpeners.get(file)?.has(line.n)) {
1441
1894
  return line.n;
1442
1895
  }
1443
1896
  }
@@ -1458,6 +1911,34 @@ function ownCallEnd(file, siteLine) {
1458
1911
  }
1459
1912
  return file.lines.length;
1460
1913
  }
1914
+ var HINT_KEYS = ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"];
1915
+ function effectiveHints(tree, file, objectText, seen = /* @__PURE__ */ new Set()) {
1916
+ const state = {
1917
+ readOnlyHint: false,
1918
+ destructiveHint: false,
1919
+ idempotentHint: false,
1920
+ openWorldHint: false
1921
+ };
1922
+ const open = objectText.indexOf("{");
1923
+ const parsed = open < 0 ? null : splitArgs(objectText, open);
1924
+ if (!parsed) return { unresolved: objectText.trim().slice(0, 60) };
1925
+ for (const member of parsed.args) {
1926
+ if (member.startsWith("...")) {
1927
+ const id = member.slice(3).trim().match(SIMPLE_IDENT_RE);
1928
+ const lines = id && !seen.has(id[1]) ? resolveConstObjectLines(tree, file, id[1]) : null;
1929
+ if (!lines) return { unresolved: member };
1930
+ const inner = effectiveHints(tree, file, lines.join("\n"), /* @__PURE__ */ new Set([...seen, id?.[1] ?? ""]));
1931
+ if ("unresolved" in inner) return inner;
1932
+ for (const k of HINT_KEYS) if (inner[k]) state[k] = true;
1933
+ continue;
1934
+ }
1935
+ const kv = member.match(/^["'`]?([A-Za-z_$][\w$]*)["'`]?\s*:\s*([\s\S]*)$/);
1936
+ if (kv && HINT_KEYS.includes(kv[1])) {
1937
+ state[kv[1]] = /^(?:true|false)$/.test(kv[2].trim());
1938
+ }
1939
+ }
1940
+ return state;
1941
+ }
1461
1942
  function findToolAnnotations(tree) {
1462
1943
  const allSites = findRegisteredTools(tree);
1463
1944
  const out = [];
@@ -1482,10 +1963,31 @@ function findToolAnnotations(tree) {
1482
1963
  let paginationCursorDeclared = false;
1483
1964
  let paginationDefault;
1484
1965
  let paginationMax;
1966
+ let annotationsUnresolved;
1967
+ const windowRaws = [];
1485
1968
  for (const line of file.lines) {
1486
1969
  if (line.n <= windowStart || line.n > windowEnd) continue;
1487
1970
  if (line.comment) continue;
1488
- const raw = line.raw;
1971
+ windowRaws.push(line.raw);
1972
+ }
1973
+ const ownStart = callOpenerLine(file, site.line);
1974
+ const ownRaws = [];
1975
+ let ownExpr;
1976
+ for (const line of file.lines) {
1977
+ if (line.n < ownStart || line.n > windowEnd || line.comment) continue;
1978
+ ownRaws.push(line.raw);
1979
+ const nl = line.raw.match(ANNOTATIONS_NONLITERAL_RE);
1980
+ if (nl && ownExpr === void 0) ownExpr = nl[1];
1981
+ }
1982
+ if (ownExpr !== void 0) {
1983
+ const id = ownExpr.match(SIMPLE_IDENT_RE);
1984
+ const body = id ? resolveConstObjectLines(tree, file, id[1]) : null;
1985
+ windowRaws.length = 0;
1986
+ windowRaws.push(...ownRaws);
1987
+ if (body) windowRaws.push(...body);
1988
+ else annotationsUnresolved = ownExpr;
1989
+ }
1990
+ for (const raw of windowRaws) {
1489
1991
  if (!verb) {
1490
1992
  const m = raw.match(VERB_FIELD_RE);
1491
1993
  if (m) verb = m[1];
@@ -1530,6 +2032,22 @@ function findToolAnnotations(tree) {
1530
2032
  if (m) paginationMax = Number.parseInt(m[1], 10);
1531
2033
  }
1532
2034
  }
2035
+ const ownText = ownRaws.join("\n");
2036
+ const inlineAnn = ownText.match(/\bannotations\s*:\s*\{/);
2037
+ if (inlineAnn && inlineAnn.index !== void 0) {
2038
+ const objText = ownText.slice(inlineAnn.index + inlineAnn[0].length - 1);
2039
+ const parsedObj = splitArgs(objText, 0);
2040
+ if (parsedObj?.args.some((m) => m.startsWith("..."))) {
2041
+ const eff = effectiveHints(tree, file, objText);
2042
+ if ("unresolved" in eff) annotationsUnresolved = eff.unresolved;
2043
+ else {
2044
+ readOnlyHintDeclared = eff.readOnlyHint;
2045
+ destructiveHintDeclared = eff.destructiveHint;
2046
+ idempotentHintDeclared = eff.idempotentHint;
2047
+ openWorldHintDeclared = eff.openWorldHint;
2048
+ }
2049
+ }
2050
+ }
1533
2051
  out.push({
1534
2052
  name: site.name,
1535
2053
  anchor: site.anchor,
@@ -1546,7 +2064,8 @@ function findToolAnnotations(tree) {
1546
2064
  tokensEstimeContexte,
1547
2065
  paginationCursorDeclared,
1548
2066
  paginationDefault,
1549
- paginationMax
2067
+ paginationMax,
2068
+ annotationsUnresolved
1550
2069
  });
1551
2070
  }
1552
2071
  }
@@ -1570,6 +2089,18 @@ function countKeywordInCode(tree, re) {
1570
2089
  }
1571
2090
  return { count, firstFile, firstLine };
1572
2091
  }
2092
+ function opaqueAnnotationsAbstain(id, rule, tools) {
2093
+ const opaque = tools.find((t) => t.annotationsUnresolved);
2094
+ if (!opaque) return null;
2095
+ const reason = `tool "${opaque.name}" at ${opaque.anchor} passes \`annotations: ${opaque.annotationsUnresolved}\`, which is not an inline object and does not resolve to a \`const\` object literal in this tree \u2014 the fields it declares are unreadable statically (${rule})`;
2096
+ return {
2097
+ id,
2098
+ anchor: opaque.anchor,
2099
+ state: "could-not-judge",
2100
+ reason,
2101
+ verdict: `could not judge: ${reason}`
2102
+ };
2103
+ }
1573
2104
 
1574
2105
  // src/detectors/c8-authority-declared.ts
1575
2106
  var TIER_SET = /* @__PURE__ */ new Set(["public", "org-member", "org-admin", "fleet-internal", "master"]);
@@ -1622,6 +2153,8 @@ var c8AuthorityDeclared = {
1622
2153
  verdict: "no tool registrations found; authority-declared check n/a"
1623
2154
  };
1624
2155
  }
2156
+ const opaque = opaqueAnnotationsAbstain(this.id, "C-8", tools);
2157
+ if (opaque) return opaque;
1625
2158
  for (const t of tools) {
1626
2159
  if (isFolded(t)) {
1627
2160
  return {
@@ -1725,6 +2258,8 @@ var c9VerbDeclared = {
1725
2258
  verdict: "no tool registrations found; verb-declared check n/a"
1726
2259
  };
1727
2260
  }
2261
+ const opaque = opaqueAnnotationsAbstain(this.id, "C-9", tools);
2262
+ if (opaque) return opaque;
1728
2263
  for (const t of tools) {
1729
2264
  if (!t.verb) {
1730
2265
  return {
@@ -1820,6 +2355,8 @@ var c10MechanismDeclared = {
1820
2355
  verdict: "no list/search tool found; mechanism-declared check n/a"
1821
2356
  };
1822
2357
  }
2358
+ const opaque = opaqueAnnotationsAbstain(this.id, "C-10", applicable);
2359
+ if (opaque) return opaque;
1823
2360
  for (const t of applicable) {
1824
2361
  if (!t.searchMechanism) {
1825
2362
  return {
@@ -1894,6 +2431,8 @@ var c11Pagination = {
1894
2431
  verdict: "no list/read/get/search tool registrations found; pagination declaration check n/a"
1895
2432
  };
1896
2433
  }
2434
+ const opaque = opaqueAnnotationsAbstain(this.id, "C-11", tools);
2435
+ if (opaque) return opaque;
1897
2436
  for (const t of tools) {
1898
2437
  const missing = [];
1899
2438
  if (!t.paginationCursorDeclared) missing.push("paginationCursor");
@@ -2117,6 +2656,8 @@ var c13ContextCeiling = {
2117
2656
  verdict: "no tool registrations found; context-ceiling check n/a"
2118
2657
  };
2119
2658
  }
2659
+ const opaque = opaqueAnnotationsAbstain(this.id, "C-13", tools);
2660
+ if (opaque) return opaque;
2120
2661
  const ceiling = findCeiling(tree);
2121
2662
  if (!ceiling) {
2122
2663
  return {
@@ -2350,7 +2891,7 @@ var c15AnnotationsUntrusted = {
2350
2891
  };
2351
2892
  }
2352
2893
  for (const t of tools) {
2353
- if (!allHintsDeclared(t)) {
2894
+ if (!allHintsDeclared(t) && !t.annotationsUnresolved) {
2354
2895
  const missing = missingHints(t).join(", ");
2355
2896
  return {
2356
2897
  id: this.id,
@@ -2360,6 +2901,17 @@ var c15AnnotationsUntrusted = {
2360
2901
  };
2361
2902
  }
2362
2903
  }
2904
+ const opaque = tools.find((t) => t.annotationsUnresolved);
2905
+ if (opaque) {
2906
+ const reason = `tool "${opaque.name}" at ${opaque.anchor} passes \`annotations: ${opaque.annotationsUnresolved}\`, which is not an inline object and does not resolve to a \`const\` object literal in this tree \u2014 its four behavioral hints are unreadable statically (C-15)`;
2907
+ return {
2908
+ id: this.id,
2909
+ anchor: opaque.anchor,
2910
+ state: "could-not-judge",
2911
+ reason,
2912
+ verdict: `could not judge: ${reason}`
2913
+ };
2914
+ }
2363
2915
  return {
2364
2916
  id: this.id,
2365
2917
  anchor: tools[0].anchor,
@@ -2448,21 +3000,92 @@ var c16UntrustedOutput = {
2448
3000
 
2449
3001
  // src/detectors/c17-supply-chain.ts
2450
3002
  import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync as readdirSync2 } from "fs";
2451
- import { join as join3 } from "path";
3003
+ import { dirname as dirname2, join as join3, relative as relative2, resolve } from "path";
2452
3004
  var PIN4 = "a74a042b53c71a095ab88b3073bd47728eeda814";
2453
3005
  var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"];
2454
3006
  var WORKFLOW_DIR = join3(".github", "workflows");
2455
- var PROVENANCE_RE2 = /--provenance\b|\bprovenance\s*:\s*true\b|id-token:\s*write/i;
3007
+ var PROVENANCE_RE2 = /\b(?:npm|pnpm|yarn|bun)\s+publish\b[^\n]*?--provenance\b/;
3008
+ function workflowStatements(text) {
3009
+ const out = [];
3010
+ const lines = text.split("\n").map((l) => l.replace(/(^|\s)#.*$/, "$1"));
3011
+ for (let i = 0; i < lines.length; i++) {
3012
+ let stmt = lines[i];
3013
+ const start = i + 1;
3014
+ while (/\\\s*$/.test(stmt) && i + 1 < lines.length) {
3015
+ stmt = `${stmt.replace(/\\\s*$/, " ")}${lines[++i].trim()}`;
3016
+ }
3017
+ out.push([start, stmt]);
3018
+ }
3019
+ return out;
3020
+ }
3021
+ var PROVENANCE_RECORD_FILES = ["PROVENANCE", "PROVENANCE.md", "CHANGELOG.md", "README.md"];
3022
+ var RECORD_LINE_RE = /^[\s>*_\-+]*Provenance\s*:\s*(.*)$/i;
3023
+ var SHASUM_HEX_RE = /\bshasum\b[^\n]*?\b([0-9a-f]{40})\b/i;
3024
+ function judgeProvenanceRecord(line, name, version) {
3025
+ const body = line.match(RECORD_LINE_RE)?.[1] ?? "";
3026
+ const esc2 = (x) => x.replace(/[.+*?^$()[\]{}|\\/]/g, "\\$&");
3027
+ const named = new RegExp(`(?:^|[\\s(])${esc2(name)}@v?${esc2(version)}(?![\\w.-]*\\d)`);
3028
+ if (!named.test(body)) {
3029
+ return { ok: false, why: `it does not name ${name}@${version}, the artifact being published` };
3030
+ }
3031
+ if (!SHASUM_HEX_RE.test(body)) {
3032
+ return { ok: false, why: "it carries no 40-hex `shasum` (the width of dist.shasum)" };
3033
+ }
3034
+ return { ok: true, why: "" };
3035
+ }
3036
+ function findProvenanceRecord(root, name, version) {
3037
+ let firstBad = null;
3038
+ for (const recordFile of PROVENANCE_RECORD_FILES) {
3039
+ const path = join3(root, recordFile);
3040
+ if (!existsSync3(path)) continue;
3041
+ let text;
3042
+ try {
3043
+ text = readFileSync4(path, "utf8");
3044
+ } catch {
3045
+ continue;
3046
+ }
3047
+ const lines = text.split("\n");
3048
+ for (let i = 0; i < lines.length; i++) {
3049
+ if (!RECORD_LINE_RE.test(lines[i])) continue;
3050
+ const site = `${recordFile}:${i + 1}`;
3051
+ if (!version || !name) {
3052
+ firstBad ??= {
3053
+ site,
3054
+ ok: false,
3055
+ why: "package.json declares no name and version to match it against"
3056
+ };
3057
+ continue;
3058
+ }
3059
+ const j = judgeProvenanceRecord(lines[i], name, version);
3060
+ if (j.ok) return { site, ok: true, why: "" };
3061
+ firstBad ??= { site, ok: false, why: j.why };
3062
+ }
3063
+ }
3064
+ return firstBad;
3065
+ }
3066
+ var RECORD_SAMPLE = "Provenance: p@1.2.3 npm-pack shasum 4b825dc642cb6eb9a060e54bf8d69288fbee4904 reproduced by r, read back from the registry 2026-10-10";
2456
3067
  function c17PositiveControls() {
2457
3068
  const sample = "npm publish --provenance --access public";
2458
3069
  const bare = "npm publish --access public";
3070
+ const claimed = (t) => workflowStatements(t).some(([, l]) => PROVENANCE_RE2.test(l));
2459
3071
  return {
2460
3072
  provenanceMatcher: {
2461
3073
  pattern: PROVENANCE_RE2.source,
2462
3074
  sample,
2463
3075
  matched: PROVENANCE_RE2.test(sample),
2464
- rejectsBare: !PROVENANCE_RE2.test(bare)
2465
- }
3076
+ // Bare publish, a comment naming the flag, the OIDC permission alone, and a continuation
3077
+ // line are the four shapes the matcher must tell apart.
3078
+ rejectsBare: !PROVENANCE_RE2.test(bare) && !claimed("# run: npm publish --provenance") && !claimed(" run: npm publish --access public # --provenance later") && !claimed("permissions:\n id-token: write") && claimed("run: npm publish \\\n --provenance --access public")
3079
+ },
3080
+ recordAcceptsComplete: judgeProvenanceRecord(RECORD_SAMPLE, "p", "1.2.3").ok,
3081
+ recordRejectsOtherVersion: !judgeProvenanceRecord(RECORD_SAMPLE, "p", "1.2.4").ok,
3082
+ recordRejectsOtherPackage: !judgeProvenanceRecord(RECORD_SAMPLE, "q", "1.2.3").ok,
3083
+ recordRejectsBareVersion: !judgeProvenanceRecord(
3084
+ "Provenance: version 1.2.3 npm-pack shasum 4b825dc642cb6eb9a060e54bf8d69288fbee4904",
3085
+ "p",
3086
+ "1.2.3"
3087
+ ).ok,
3088
+ recordRejectsNoShasum: !judgeProvenanceRecord("Provenance: p@1.2.3 trust us", "p", "1.2.3").ok
2466
3089
  };
2467
3090
  }
2468
3091
  function findLockfile(root) {
@@ -2479,30 +3102,45 @@ function findProvenanceClaim(tree, root) {
2479
3102
  return { site: `${pkg.path}:publishConfig.provenance`, text: "provenance: true" };
2480
3103
  }
2481
3104
  }
2482
- const workflowsDir = join3(root, WORKFLOW_DIR);
2483
- if (!existsSync3(workflowsDir)) return null;
2484
- let entries = [];
2485
- try {
2486
- entries = readdirSync2(workflowsDir);
2487
- } catch {
2488
- return null;
2489
- }
2490
- for (const name of entries) {
2491
- if (!/\.ya?ml$/.test(name)) continue;
2492
- const path = join3(workflowsDir, name);
2493
- let text;
3105
+ for (const dir of workflowDirs(root)) {
3106
+ const workflowsDir = join3(dir, WORKFLOW_DIR);
3107
+ if (!existsSync3(workflowsDir)) continue;
3108
+ let entries = [];
2494
3109
  try {
2495
- text = readFileSync4(path, "utf8");
3110
+ entries = readdirSync2(workflowsDir).sort();
2496
3111
  } catch {
2497
3112
  continue;
2498
3113
  }
2499
- if (PROVENANCE_RE2.test(text)) {
2500
- const line = text.split("\n").findIndex((l) => PROVENANCE_RE2.test(l)) + 1;
2501
- return { site: `${WORKFLOW_DIR}/${name}:${line}`, text };
3114
+ for (const name of entries) {
3115
+ if (!/\.ya?ml$/.test(name)) continue;
3116
+ const path = join3(workflowsDir, name);
3117
+ let text;
3118
+ try {
3119
+ text = readFileSync4(path, "utf8");
3120
+ } catch {
3121
+ continue;
3122
+ }
3123
+ const hit = workflowStatements(text).find(([, l]) => PROVENANCE_RE2.test(l));
3124
+ if (hit) {
3125
+ const line = hit[0];
3126
+ const rel = relative2(root, workflowsDir) || WORKFLOW_DIR;
3127
+ return { site: `${rel}/${name}:${line}`, text };
3128
+ }
2502
3129
  }
2503
3130
  }
2504
3131
  return null;
2505
3132
  }
3133
+ function workflowDirs(root) {
3134
+ const chain = [];
3135
+ let dir = resolve(root);
3136
+ for (; ; ) {
3137
+ chain.push(dir);
3138
+ if (existsSync3(join3(dir, ".git"))) return chain;
3139
+ const parent = dirname2(dir);
3140
+ if (parent === dir) return [resolve(root)];
3141
+ dir = parent;
3142
+ }
3143
+ }
2506
3144
  var c17SupplyChainInner = {
2507
3145
  id: "C17 supply-chain",
2508
3146
  rule: "C-17",
@@ -2514,6 +3152,11 @@ var c17SupplyChainInner = {
2514
3152
  `C17 provenance-matcher control FAILED: accept(\`${controls.provenanceMatcher.sample}\`)=${controls.provenanceMatcher.matched} rejectsBare=${controls.provenanceMatcher.rejectsBare} \u2014 matcher broken, verdict untrustworthy`
2515
3153
  );
2516
3154
  }
3155
+ if (!controls.recordAcceptsComplete || !controls.recordRejectsOtherVersion || !controls.recordRejectsNoShasum || !controls.recordRejectsOtherPackage || !controls.recordRejectsBareVersion) {
3156
+ throw new Error(
3157
+ `C17 provenance-record control FAILED: ${JSON.stringify(controls)} \u2014 record parser broken, verdict untrustworthy`
3158
+ );
3159
+ }
2517
3160
  const lockfile = findLockfile(tree.root);
2518
3161
  if (!lockfile) {
2519
3162
  return {
@@ -2524,12 +3167,31 @@ var c17SupplyChainInner = {
2524
3167
  };
2525
3168
  }
2526
3169
  const claim = findProvenanceClaim(tree, tree.root);
3170
+ const pkgVersion = typeof tree.packageJson?.json.version === "string" ? tree.packageJson.json.version : void 0;
3171
+ const pkgName = typeof tree.packageJson?.json.name === "string" ? tree.packageJson.json.name : void 0;
3172
+ const record = claim ? null : findProvenanceRecord(tree.root, pkgName, pkgVersion);
3173
+ if (!claim && record?.ok) {
3174
+ return {
3175
+ id: this.id,
3176
+ anchor: record.site,
3177
+ state: "present",
3178
+ verdict: `lockfile ${lockfile} pinned + reproduced-shasum record at ${record.site} (non-CI path, C-17; PROPOSED EVIDENCE FORM \u2014 the reviewer rules on it). Whether that shasum equals the registry's dist.shasum is the gate check at publish time \u2014 this static run asserts the record is complete and names version ${pkgVersion}, not that the gate has resolved it.`
3179
+ };
3180
+ }
3181
+ if (!claim && record && !record.ok) {
3182
+ return {
3183
+ id: this.id,
3184
+ anchor: record.site,
3185
+ state: "non-conforming",
3186
+ verdict: `lockfile ${lockfile} present and a \`Provenance:\` line at ${record.site}, but ${record.why} -> a record covers only the version it names and must carry the reproduced 40-hex shasum (C-17, non-CI path; proposed evidence form)`
3187
+ };
3188
+ }
2527
3189
  if (!claim) {
2528
3190
  return {
2529
3191
  id: this.id,
2530
3192
  anchor: `${lockfile}:1`,
2531
3193
  state: "non-conforming",
2532
- verdict: `lockfile ${lockfile} present, but no provenance attestation claim found in package.json publishConfig or ${WORKFLOW_DIR}/*.yml -> add \`npm publish --provenance\` or \`publishConfig.provenance: true\` (C-17b)`
3194
+ verdict: `lockfile ${lockfile} present, but no provenance attestation claim found in package.json publishConfig or ${WORKFLOW_DIR}/*.yml -> add \`npm publish --provenance\` or \`publishConfig.provenance: true\`, or, when no CI publishes, a \`Provenance: <pkg>@<version> npm-pack shasum <40-hex> ...\` record in PROVENANCE / CHANGELOG.md / README.md (C-17b; record form proposed)`
2533
3195
  };
2534
3196
  }
2535
3197
  return {
@@ -2553,6 +3215,28 @@ function manifestPath(root) {
2553
3215
  if (existsSync4(extPath)) return { kind: "extension", path: extPath };
2554
3216
  return { kind: "server", path: join4(root, "mcp.json") };
2555
3217
  }
3218
+ var SERVER_MANIFEST_NAMES = ["mcp.json", "mcp-server.json"];
3219
+ function isObject(v) {
3220
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3221
+ }
3222
+ function classifyRootJson(json) {
3223
+ if (!json) return "unparseable";
3224
+ if (isObject(json.mcpServers)) return "plugin-config";
3225
+ if (isObject(json.capabilities) && isObject(json.transport) && typeof json.version === "string") {
3226
+ return "server-manifest";
3227
+ }
3228
+ return "partial";
3229
+ }
3230
+ function rootCandidates(root) {
3231
+ const out = [];
3232
+ for (const name of SERVER_MANIFEST_NAMES) {
3233
+ const path = join4(root, name);
3234
+ if (!existsSync4(path)) continue;
3235
+ const json = readJson(path);
3236
+ out.push({ name, path, role: classifyRootJson(json), json });
3237
+ }
3238
+ return out;
3239
+ }
2556
3240
  function readJson(path) {
2557
3241
  try {
2558
3242
  return JSON.parse(readFileSync5(path, "utf8"));
@@ -2566,7 +3250,10 @@ function c18PositiveControls() {
2566
3250
  versionParity: {
2567
3251
  matched: equalCheck("0.1.0", "0.1.0"),
2568
3252
  rejectsMismatch: !equalCheck("0.1.0", "0.2.0")
2569
- }
3253
+ },
3254
+ contentIdentifiesPluginConfig: classifyRootJson({ mcpServers: { a: {} } }) === "plugin-config",
3255
+ contentIdentifiesServerManifest: classifyRootJson({ version: "1", capabilities: {}, transport: {} }) === "server-manifest",
3256
+ contentRejectsPartial: classifyRootJson({ version: "1", name: "x" }) === "partial"
2570
3257
  };
2571
3258
  }
2572
3259
  var c18StructureManifestInner = {
@@ -2575,6 +3262,11 @@ var c18StructureManifestInner = {
2575
3262
  pin: PIN5,
2576
3263
  run(tree) {
2577
3264
  const controls = c18PositiveControls();
3265
+ if (!controls.contentIdentifiesPluginConfig || !controls.contentIdentifiesServerManifest || !controls.contentRejectsPartial) {
3266
+ throw new Error(
3267
+ "C18 manifest-by-content control FAILED: mcpServers must read as plugin config, capabilities+transport+version as server manifest, a bare name+version as partial \u2014 verdict untrustworthy"
3268
+ );
3269
+ }
2578
3270
  if (!controls.versionParity.matched || !controls.versionParity.rejectsMismatch) {
2579
3271
  throw new Error(
2580
3272
  "C18 version-parity control FAILED: equal-strings must match and unequal-strings must be rejected \u2014 verdict untrustworthy"
@@ -2601,25 +3293,64 @@ var c18StructureManifestInner = {
2601
3293
  verdict: `could not judge: ${reason}`
2602
3294
  };
2603
3295
  }
2604
- const { kind, path } = manifestPath(tree.root);
2605
- const manifestName = kind === "extension" ? "extension.json" : "mcp.json";
2606
- if (!existsSync4(path)) {
2607
- return {
2608
- id: this.id,
2609
- anchor: tree.root,
2610
- state: "non-conforming",
2611
- verdict: `no ${manifestName} found at the tree root -> the ${kind === "extension" ? "extension" : "server/App"} manifest is required (C-18)`
2612
- };
2613
- }
2614
- const manifest = readJson(path);
2615
- if (!manifest) {
2616
- return {
2617
- id: this.id,
2618
- anchor: path,
2619
- state: "non-conforming",
2620
- verdict: `${manifestName} present but could not be parsed as JSON -> fix the manifest (C-18)`
2621
- };
3296
+ const ext = manifestPath(tree.root);
3297
+ const kind = ext.kind;
3298
+ let path = ext.path;
3299
+ let manifest = null;
3300
+ let manifestName = kind === "extension" ? "extension.json" : "mcp.json";
3301
+ if (kind === "server") {
3302
+ const cands = rootCandidates(tree.root);
3303
+ const bad = cands.find((c) => c.role === "unparseable");
3304
+ if (bad) {
3305
+ return {
3306
+ id: this.id,
3307
+ anchor: bad.path,
3308
+ state: "non-conforming",
3309
+ verdict: `${bad.name} present but could not be parsed as JSON -> fix the manifest (C-18)`
3310
+ };
3311
+ }
3312
+ const manifests = cands.filter((c) => c.role === "server-manifest");
3313
+ if (manifests.length > 1) {
3314
+ return {
3315
+ id: this.id,
3316
+ anchor: manifests[0].path,
3317
+ state: "non-conforming",
3318
+ verdict: `two root files each carry capabilities + transport + version (${manifests.map((c) => c.name).join(" and ")}) -> a repository has ONE server manifest; keep it in mcp-server.json when mcp.json is the OpenAI plugin config, otherwise in mcp.json (C-18)`
3319
+ };
3320
+ }
3321
+ const chosen = manifests[0] ?? cands.find((c) => c.role === "partial" && c.name === "mcp-server.json") ?? cands.find((c) => c.role === "partial");
3322
+ if (!chosen) {
3323
+ const plugin = cands.find((c) => c.role === "plugin-config");
3324
+ return {
3325
+ id: this.id,
3326
+ anchor: plugin ? plugin.path : tree.root,
3327
+ state: "non-conforming",
3328
+ verdict: plugin ? `no server manifest found: ${plugin.name} carries \`mcpServers\` (the OpenAI plugin config, not the server manifest) and no root file carries capabilities + transport + version -> add mcp-server.json with the server manifest (C-18)` : "no mcp.json or mcp-server.json found at the tree root -> the server/App manifest is required (C-18)"
3329
+ };
3330
+ }
3331
+ path = chosen.path;
3332
+ manifest = chosen.json;
3333
+ manifestName = chosen.name;
3334
+ } else {
3335
+ if (!existsSync4(path)) {
3336
+ return {
3337
+ id: this.id,
3338
+ anchor: tree.root,
3339
+ state: "non-conforming",
3340
+ verdict: "no extension.json found at the tree root -> the extension manifest is required (C-18)"
3341
+ };
3342
+ }
3343
+ manifest = readJson(path);
3344
+ if (!manifest) {
3345
+ return {
3346
+ id: this.id,
3347
+ anchor: path,
3348
+ state: "non-conforming",
3349
+ verdict: `${manifestName} present but could not be parsed as JSON -> fix the manifest (C-18)`
3350
+ };
3351
+ }
2622
3352
  }
3353
+ if (!manifest) throw new Error("C18: manifest selection produced no manifest \u2014 unreachable");
2623
3354
  if (typeof manifest.name !== "string" || manifest.name.length === 0) {
2624
3355
  return {
2625
3356
  id: this.id,
@@ -2653,7 +3384,7 @@ var c18StructureManifestInner = {
2653
3384
  id: this.id,
2654
3385
  anchor: path,
2655
3386
  state: "non-conforming",
2656
- verdict: `mcp.json is missing required field(s): ${missing.join(", ")} not declared -> declare capabilities/transport (C-18)`
3387
+ verdict: `${manifestName} is missing required field(s): ${missing.join(", ")} not declared -> declare capabilities/transport (C-18)`
2657
3388
  };
2658
3389
  }
2659
3390
  }
@@ -2689,30 +3420,52 @@ var c18StructureManifest = guardUnreadablePackageJson(
2689
3420
  // src/detectors/c19-served-descriptions.ts
2690
3421
  var PIN6 = "a74a042b53c71a095ab88b3073bd47728eeda814";
2691
3422
  var TOOL_DESC_INLINE_RE = /\b(?:s\.tool|\.tool|registerTool)\s*\(\s*(?:["'`][^"'`]+["'`]|[A-Za-z_$][\w$]*)\s*,\s*["'`]([^"'`]+)["'`]/;
2692
- var DESCRIPTOR_DESC_RE = /\bdescription\s*:\s*["'`]([^"'`]+)["'`]/;
2693
3423
  var BARE_STRING_RE = /^\s*["'`]([^"'`]+)["'`]\s*,?\s*$/;
2694
3424
  var BACKWARD_LOOKBACK = 10;
2695
3425
  var FORWARD_LOOKAHEAD = 6;
3426
+ var DESCRIPTION_KEY_RE = /\bdescription\s*:/;
2696
3427
  function findInlineDescription(tree, siteAnchor, toolName) {
3428
+ const forwarded = wrapperDescriptionArg(tree, siteAnchor);
3429
+ if (forwarded !== void 0) {
3430
+ const resolved = /^[A-Za-z_$][\w$]*$/.test(forwarded) ? constExpression(tree, forwarded) : null;
3431
+ const v = readDescriptionValue(resolved ?? forwarded);
3432
+ if (v.kind === "literal" && v.text.length > 0) return v.text;
3433
+ if (v.kind === "non-literal") return v;
3434
+ }
2697
3435
  const [rel, lineStr] = siteAnchor.split(":");
2698
3436
  const file = tree.files.find((f) => f.rel === rel);
2699
3437
  if (!file) return void 0;
2700
3438
  const siteLine = Number.parseInt(lineStr, 10);
2701
- const start = Math.max(1, siteLine - BACKWARD_LOOKBACK);
3439
+ let floor = 1;
3440
+ for (const other of findRegisteredTools(tree)) {
3441
+ const [orel, oline] = other.anchor.split(":");
3442
+ const n = Number.parseInt(oline, 10);
3443
+ if (orel === rel && n < siteLine) floor = Math.max(floor, ownCallEnd(file, n) + 1);
3444
+ }
3445
+ const start = Math.max(floor, siteLine - BACKWARD_LOOKBACK);
2702
3446
  const end = Math.min(siteLine + FORWARD_LOOKAHEAD, file.lines.length + 1);
3447
+ let nonLiteral;
2703
3448
  for (let n = start; n < end; n++) {
2704
3449
  const line = file.lines[n - 1];
2705
3450
  if (!line || line.comment) continue;
2706
3451
  const inline = line.raw.match(TOOL_DESC_INLINE_RE);
2707
3452
  if (inline) return inline[1];
2708
- const desc = line.raw.match(DESCRIPTOR_DESC_RE);
2709
- if (desc) return desc[1];
3453
+ const key = line.raw.match(DESCRIPTION_KEY_RE);
3454
+ if (key && key.index !== void 0) {
3455
+ const after = line.raw.slice(key.index + key[0].length);
3456
+ const tail = file.lines.slice(n, n + 8).filter((l) => !l.comment).map((l) => l.raw).join("\n");
3457
+ const v = readDescriptionValue(`${after}
3458
+ ${tail}`);
3459
+ if (v.kind === "literal" && v.text.length > 0) return v.text;
3460
+ if (v.kind === "non-literal" && !nonLiteral) nonLiteral = v;
3461
+ continue;
3462
+ }
2710
3463
  if (n >= siteLine) {
2711
3464
  const bare = line.raw.match(BARE_STRING_RE);
2712
3465
  if (bare && bare[1] !== toolName) return bare[1];
2713
3466
  }
2714
3467
  }
2715
- return void 0;
3468
+ return nonLiteral;
2716
3469
  }
2717
3470
  var TRIGGER_LIST_RE = /use this whenever/i;
2718
3471
  var TRIGGER_CLOSE_RE = /even if they don'?t say/i;
@@ -2727,7 +3480,10 @@ function c19PositiveControls() {
2727
3480
  triggerListAccepts: TRIGGER_LIST_RE.test(triggerSample),
2728
3481
  triggerCloseAccepts: TRIGGER_CLOSE_RE.test(triggerSample),
2729
3482
  bilingualAccepts: BILINGUAL_SPLIT_RE.test(bilingualSample),
2730
- bilingualRejectsMonolingual: !BILINGUAL_SPLIT_RE.test(monoSample)
3483
+ bilingualRejectsMonolingual: !BILINGUAL_SPLIT_RE.test(monoSample),
3484
+ concatenationAccepts: JSON.stringify(readDescriptionValue(`"a, b's " +
3485
+ "c"`)) === JSON.stringify({ kind: "literal", text: "a, b's c" }),
3486
+ nonLiteralRejects: readDescriptionValue("describeTool(x)").kind === "non-literal"
2731
3487
  };
2732
3488
  }
2733
3489
  var c19ServedDescriptions = {
@@ -2736,7 +3492,7 @@ var c19ServedDescriptions = {
2736
3492
  pin: PIN6,
2737
3493
  run(tree) {
2738
3494
  const controls = c19PositiveControls();
2739
- if (!controls.inlineDescAccepts || !controls.triggerListAccepts || !controls.triggerCloseAccepts || !controls.bilingualAccepts || !controls.bilingualRejectsMonolingual) {
3495
+ if (!controls.inlineDescAccepts || !controls.triggerListAccepts || !controls.triggerCloseAccepts || !controls.bilingualAccepts || !controls.bilingualRejectsMonolingual || !controls.concatenationAccepts || !controls.nonLiteralRejects) {
2740
3496
  throw new Error(
2741
3497
  `C19 positive control FAILED: ${JSON.stringify(controls)} \u2014 matcher broken, verdict untrustworthy`
2742
3498
  );
@@ -2764,8 +3520,13 @@ var c19ServedDescriptions = {
2764
3520
  verdict: "no tool registrations found; served-descriptions check n/a (C-19)"
2765
3521
  };
2766
3522
  }
3523
+ let opaque;
2767
3524
  for (const site of sites) {
2768
3525
  const desc = findInlineDescription(tree, site.anchor, site.name);
3526
+ if (typeof desc === "object") {
3527
+ opaque ??= { anchor: site.anchor, name: site.name, expr: desc.expr };
3528
+ continue;
3529
+ }
2769
3530
  if (!desc) {
2770
3531
  return {
2771
3532
  id: this.id,
@@ -2775,6 +3536,16 @@ var c19ServedDescriptions = {
2775
3536
  };
2776
3537
  }
2777
3538
  }
3539
+ if (opaque) {
3540
+ const reason = `tool "${opaque.name}" at ${opaque.anchor} declares \`description: ${opaque.expr}\`, which is not a string literal, a concatenation of literals, or a template literal without expressions \u2014 the declared text is unreadable statically (C-19)`;
3541
+ return {
3542
+ id: this.id,
3543
+ anchor: opaque.anchor,
3544
+ state: "could-not-judge",
3545
+ reason,
3546
+ verdict: `could not judge: ${reason}`
3547
+ };
3548
+ }
2778
3549
  return {
2779
3550
  id: this.id,
2780
3551
  anchor: sites[0].anchor,
@@ -3117,6 +3888,11 @@ var IDENTITY_KEY_NAMES = [
3117
3888
  "gitCommit",
3118
3889
  "gitRevision"
3119
3890
  ];
3891
+ var HEALTH_ROUTE_RE = /\b[A-Za-z_$][\w$]*\.(?:get|all|route|use)\s*\(\s*["'`][^"'`]*\/health|\bpathname\s*===?\s*["'`][^"'`]*\/health/;
3892
+ var HEALTH_IDENTITY_KEYS = [...IDENTITY_KEY_NAMES, "commit"];
3893
+ var HEALTH_KEY_LINE_RE = new RegExp(
3894
+ `(?:^|[\\s{,])(${HEALTH_IDENTITY_KEYS.join("|")})\\s*:\\s*(.+?)\\s*,?\\s*$`
3895
+ );
3120
3896
  var CONSTRUCTOR_RE = /\bnew\s+(?:McpServer|Server)\s*\(/;
3121
3897
  var IDENTITY_KEY_LINE_RE = new RegExp(
3122
3898
  `\\b(${IDENTITY_KEY_NAMES.join("|")})\\s*:\\s*(.+?)\\s*,?\\s*$`
@@ -3132,12 +3908,25 @@ function isLiteralValue(expr) {
3132
3908
  if (m[1] === "`" && /\$\{/.test(m[2])) return false;
3133
3909
  return true;
3134
3910
  }
3911
+ function valueIsLiteral(tree, expr) {
3912
+ const trimmed = expr.trim().replace(/,$/, "").trim();
3913
+ if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
3914
+ const bound = constExpression(tree, trimmed);
3915
+ if (bound !== null) return isLiteralValue(bound);
3916
+ }
3917
+ return isLiteralValue(expr);
3918
+ }
3135
3919
  function c25PositiveControls() {
3920
+ const hd = ' commit: process.env.RAILWAY_GIT_COMMIT_SHA ?? "unknown",'.match(HEALTH_KEY_LINE_RE);
3921
+ const hl = ' commit: "4b825dc",'.match(HEALTH_KEY_LINE_RE);
3136
3922
  const literalMatch = LITERAL_SAMPLE.match(IDENTITY_KEY_LINE_RE);
3137
3923
  const derivedMatch = DERIVED_SAMPLE.match(IDENTITY_KEY_LINE_RE);
3138
3924
  return {
3139
3925
  literalAccepts: literalMatch ? isLiteralValue(literalMatch[2]) : false,
3140
- derivedRejectsAsLiteral: derivedMatch ? isLiteralValue(derivedMatch[2]) : false
3926
+ derivedRejectsAsLiteral: derivedMatch ? isLiteralValue(derivedMatch[2]) : false,
3927
+ healthRouteMatches: HEALTH_ROUTE_RE.test('app.get("/health", (c) =>') && !HEALTH_ROUTE_RE.test('app.get("/tools", h)'),
3928
+ healthDerivedCommitReads: hd ? !isLiteralValue(hd[2]) : false,
3929
+ healthLiteralCommitReads: hl ? isLiteralValue(hl[2]) : false
3141
3930
  };
3142
3931
  }
3143
3932
  function extractConstructorArgs(file, startIdx) {
@@ -3162,6 +3951,27 @@ function extractConstructorArgs(file, startIdx) {
3162
3951
  }
3163
3952
  return null;
3164
3953
  }
3954
+ function findHealthSurfaces(tree) {
3955
+ const out = [];
3956
+ for (const file of tree.files) {
3957
+ file.lines.forEach((line, idx) => {
3958
+ if (line.comment || !HEALTH_ROUTE_RE.test(line.raw)) return;
3959
+ const anchor = `${file.rel}:${line.n}`;
3960
+ const body = extractConstructorArgs(file, idx);
3961
+ if (body === null || !/\bversion\s*:/.test(body)) return;
3962
+ for (const raw of body.split("\n")) {
3963
+ const m = raw.match(HEALTH_KEY_LINE_RE);
3964
+ if (!m) continue;
3965
+ const value = m[2];
3966
+ out.push(
3967
+ valueIsLiteral(tree, value) ? { kind: "literal", key: m[1], value: value.trim(), anchor: `${anchor} (/health)` } : { kind: "derived", key: m[1], anchor: `${anchor} (/health)` }
3968
+ );
3969
+ return;
3970
+ }
3971
+ });
3972
+ }
3973
+ return out;
3974
+ }
3165
3975
  function findConstructorSites(tree) {
3166
3976
  const sites = [];
3167
3977
  for (const file of tree.files) {
@@ -3177,14 +3987,14 @@ function findConstructorSites(tree) {
3177
3987
  }
3178
3988
  return sites;
3179
3989
  }
3180
- function classifyArgs(args, anchor) {
3990
+ function classifyArgs(tree, args, anchor) {
3181
3991
  for (const raw of args.split("\n")) {
3182
3992
  const bare = raw.match(IDENTITY_KEY_BARE_RE);
3183
3993
  if (bare) return { kind: "bare-continuation", key: bare[1], anchor };
3184
3994
  const m = raw.match(IDENTITY_KEY_LINE_RE);
3185
3995
  if (m) {
3186
3996
  const [, key, value] = m;
3187
- if (isLiteralValue(value)) return { kind: "literal", key, value: value.trim(), anchor };
3997
+ if (valueIsLiteral(tree, value)) return { kind: "literal", key, value: value.trim(), anchor };
3188
3998
  return { kind: "derived", key, anchor };
3189
3999
  }
3190
4000
  }
@@ -3196,7 +4006,7 @@ var c25BuildIdentity = {
3196
4006
  pin: PIN8,
3197
4007
  run(tree) {
3198
4008
  const controls = c25PositiveControls();
3199
- if (!controls.literalAccepts || controls.derivedRejectsAsLiteral) {
4009
+ if (!controls.literalAccepts || controls.derivedRejectsAsLiteral || !controls.healthRouteMatches || !controls.healthDerivedCommitReads || !controls.healthLiteralCommitReads) {
3200
4010
  throw new Error(
3201
4011
  `C25 literal/derived control FAILED: ${JSON.stringify(controls)} \u2014 matcher broken, verdict untrustworthy`
3202
4012
  );
@@ -3222,8 +4032,18 @@ var c25BuildIdentity = {
3222
4032
  };
3223
4033
  }
3224
4034
  }
4035
+ const health = findHealthSurfaces(tree);
4036
+ const healthDerived = health.find((h) => h.kind === "derived");
4037
+ if (healthDerived && healthDerived.kind === "derived") {
4038
+ return {
4039
+ id: this.id,
4040
+ anchor: healthDerived.anchor,
4041
+ state: "present",
4042
+ verdict: `build-identity field "${healthDerived.key}" at ${healthDerived.anchor} is fed by the build (not a literal) \u2014 served beside version on the unauthenticated /health surface (C-25). Whether the SERVED value equals the expected commit is the dynamique half, run with a live session.`
4043
+ };
4044
+ }
3225
4045
  for (const site of sites) {
3226
- const finding = classifyArgs(site.args, site.anchor);
4046
+ const finding = classifyArgs(tree, site.args, site.anchor);
3227
4047
  if (finding.kind === "bare-continuation") {
3228
4048
  const reason = `identity key "${finding.key}" found at ${finding.anchor} but its value continues past the line (not statically joined) \u2014 verdict unreadable`;
3229
4049
  return {
@@ -3251,6 +4071,15 @@ var c25BuildIdentity = {
3251
4071
  };
3252
4072
  }
3253
4073
  }
4074
+ const healthLiteral = health.find((h) => h.kind === "literal");
4075
+ if (healthLiteral && healthLiteral.kind === "literal") {
4076
+ return {
4077
+ id: this.id,
4078
+ anchor: healthLiteral.anchor,
4079
+ state: "non-conforming",
4080
+ verdict: `build-identity field "${healthLiteral.key}" at ${healthLiteral.anchor} is a hand-written literal (${healthLiteral.value}) -> a literal never changes on its own and proves nothing about the tree actually served; derive it from the build (C-25)`
4081
+ };
4082
+ }
3254
4083
  return {
3255
4084
  id: this.id,
3256
4085
  anchor: sites[0].anchor,
@@ -3359,7 +4188,7 @@ import {
3359
4188
  writeFileSync
3360
4189
  } from "fs";
3361
4190
  import { tmpdir } from "os";
3362
- import { basename as basename2, join as join6, relative as relative2, sep as sep2 } from "path";
4191
+ import { basename as basename2, join as join6, relative as relative3, sep as sep2 } from "path";
3363
4192
  var PIN9 = "20136b50";
3364
4193
  var DEPLOY_CONFIG_FILES = [
3365
4194
  "railway.json",
@@ -3462,12 +4291,12 @@ function c26DeriveScope(root, tree) {
3462
4291
  const skipped = [];
3463
4292
  const onSkip = (skippedDir, depth) => {
3464
4293
  const entry = tree ? recordSkip(tree, skippedDir, root) : {
3465
- path: relative2(root, skippedDir).split(sep2).join("/"),
4294
+ path: relative3(root, skippedDir).split(sep2).join("/"),
3466
4295
  reason: SKIP_DIR_INFO[basename2(skippedDir)]?.reason ?? "skipped"
3467
4296
  };
3468
4297
  if (SKIP_DIR_INFO[basename2(skippedDir)]?.authored) {
3469
4298
  const hit = probeForCandidate(skippedDir, depth);
3470
- if (hit) entry.candidate = relative2(root, hit).split(sep2).join("/");
4299
+ if (hit) entry.candidate = relative3(root, hit).split(sep2).join("/");
3471
4300
  }
3472
4301
  if (!skipped.some((d) => d.path === entry.path)) skipped.push(entry);
3473
4302
  };
@@ -3651,7 +4480,7 @@ function renderCoverage(scope, verdicts) {
3651
4480
  }
3652
4481
  }
3653
4482
  return verdicts.map((v) => {
3654
- const rel = relative2(process.cwd(), v.site.dir) || v.site.dir;
4483
+ const rel = relative3(process.cwd(), v.site.dir) || v.site.dir;
3655
4484
  if (v.status === "skipped") return `SKIPPED ${rel} (${v.reason})`;
3656
4485
  return `ANALYSED ${rel} -> ${v.ok ? "GREEN" : "RED"}: ${v.detail}`;
3657
4486
  }).join(" | ");
@@ -3729,30 +4558,102 @@ var c26SubpackageLockfile = {
3729
4558
 
3730
4559
  // src/detectors/d2.ts
3731
4560
  var SESSION_RE = /mcp-session-id/i;
4561
+ var GENERATOR_RE = /\bsessionIdGenerator\s*:\s*(?!undefined\b)\S/;
4562
+ var HEADER_CALL_RE = /\b(?:get|header|set|setHeader|append|getHeader)\s*\(\s*["'`]mcp-session-id["'`]/i;
4563
+ var HEADER_INDEX_RE = /\[\s*["'`]mcp-session-id["'`]\s*\]/i;
4564
+ var HEADER_KEY_RE = /["'`]mcp-session-id["'`]\s*:/i;
4565
+ var STORE_RE = /\b(?:sessions?|transports?)\w*\s*(?:\[\s*\w*[sS]essionId|\.(?:get|set|has|delete)\(\s*\w*[sS]essionId)/;
4566
+ var KEYING = [
4567
+ { id: "sessionIdGenerator set", re: GENERATOR_RE },
4568
+ { id: "header read/written by call", re: HEADER_CALL_RE },
4569
+ { id: "header read by index", re: HEADER_INDEX_RE },
4570
+ { id: "header sent as an object key", re: HEADER_KEY_RE },
4571
+ { id: "store keyed by a session id", re: STORE_RE }
4572
+ ];
4573
+ var CORS_OPEN_RE = /\b(?:allow|expose)d?Headers\s*:\s*\[|Access-Control-(?:Allow|Expose)-Headers/i;
4574
+ function inCorsList(file, idx) {
4575
+ if (CORS_OPEN_RE.test(file.lines[idx].raw)) return true;
4576
+ let depth = 0;
4577
+ for (let i = idx; i >= Math.max(0, idx - 25); i--) {
4578
+ const l = file.lines[i];
4579
+ if (l.comment) continue;
4580
+ const raw = i === idx ? l.raw.slice(0, l.raw.search(SESSION_RE)) : l.raw;
4581
+ depth += (raw.match(/\]/g) ?? []).length - (raw.match(/\[/g) ?? []).length;
4582
+ if (i !== idx && CORS_OPEN_RE.test(l.raw) && depth < 0) return true;
4583
+ if (depth < 0 && i !== idx) return false;
4584
+ }
4585
+ return false;
4586
+ }
4587
+ function d2PositiveControls() {
4588
+ const f = (s) => ({
4589
+ path: "x.ts",
4590
+ rel: "x.ts",
4591
+ lines: s.split("\n").map((raw, i) => ({ n: i + 1, raw, comment: false }))
4592
+ });
4593
+ const cors = f('cors({\n allowHeaders: [\n "Content-Type",\n "mcp-session-id",\n ],\n})');
4594
+ const notCors = f('const x = [\n "mcp-session-id",\n];');
4595
+ return {
4596
+ "keying:generator": GENERATOR_RE.test("sessionIdGenerator: () => randomUUID(),"),
4597
+ "negative:generator-undefined": !GENERATOR_RE.test("sessionIdGenerator: undefined,"),
4598
+ "keying:index": HEADER_INDEX_RE.test('req.headers["mcp-session-id"]'),
4599
+ "keying:call": HEADER_CALL_RE.test('c.req.header("mcp-session-id")'),
4600
+ "keying:store": STORE_RE.test("transports[sessionId]"),
4601
+ "negative:cors-entry-is-not-a-keying-form": !KEYING.some(
4602
+ (k) => k.re.test(' "mcp-session-id",')
4603
+ ),
4604
+ "cors:array-above": inCorsList(cors, 3),
4605
+ "negative:plain-array-is-not-cors": !inCorsList(notCors, 1)
4606
+ };
4607
+ }
3732
4608
  var d2 = {
3733
4609
  id: "D2 stateless",
3734
4610
  run(tree) {
3735
- const hits = [];
4611
+ const failed = Object.entries(d2PositiveControls()).filter(([, ok]) => !ok);
4612
+ if (failed.length > 0) {
4613
+ throw new Error(
4614
+ `D2 positive control FAILED: ${failed.map(([k]) => k).join(", ")} \u2014 matcher broken, verdict untrustworthy`
4615
+ );
4616
+ }
4617
+ const keying = [];
4618
+ const cors = [];
4619
+ const unknown = [];
3736
4620
  for (const file of tree.files) {
3737
- for (const line of file.lines) {
3738
- if (line.comment) continue;
3739
- if (SESSION_RE.test(line.raw)) hits.push({ file: file.rel, line: line.n });
3740
- }
4621
+ file.lines.forEach((line, idx) => {
4622
+ if (line.comment) return;
4623
+ const site = `${file.rel}:${line.n}`;
4624
+ const form = KEYING.find((k) => k.re.test(line.raw));
4625
+ if (form) {
4626
+ keying.push({ site, form: form.id });
4627
+ return;
4628
+ }
4629
+ if (!SESSION_RE.test(line.raw)) return;
4630
+ if (inCorsList(file, idx)) cors.push(site);
4631
+ else unknown.push(site);
4632
+ });
3741
4633
  }
3742
- if (hits.length > 0) {
3743
- const anchor = `${hits[0].file}:${hits.map((h) => h.line).join(",")}`;
4634
+ if (keying.length > 0) {
3744
4635
  return {
3745
4636
  id: this.id,
3746
- anchor,
4637
+ anchor: `${keying[0].site}`,
3747
4638
  state: "non-conforming",
3748
- verdict: "session-keyed (mcp-session-id header) -> migrate to stateless"
4639
+ verdict: `session-keyed -> migrate to stateless: ${keying.map((k) => `${k.site} (${k.form})`).join("; ")}`
4640
+ };
4641
+ }
4642
+ if (unknown.length > 0) {
4643
+ const reason = `mcp-session-id at ${unknown.join(", ")} is neither a CORS header-list entry nor a recognised session-keying form (generator, header read/write, header key, session-keyed store) \u2014 whether it keys sessions is undecidable statically`;
4644
+ return {
4645
+ id: this.id,
4646
+ anchor: unknown[0],
4647
+ state: "could-not-judge",
4648
+ reason,
4649
+ verdict: `could not judge: ${reason}`
3749
4650
  };
3750
4651
  }
3751
4652
  return {
3752
4653
  id: this.id,
3753
- anchor: "-",
4654
+ anchor: cors[0] ?? "-",
3754
4655
  state: "present",
3755
- verdict: "stateless (no mcp-session-id session-keying)"
4656
+ verdict: cors.length > 0 ? `stateless: mcp-session-id is only named in CORS header lists (${cors.join(", ")}) \u2014 an inert header name, no session id generator, header read or session-keyed store found` : "stateless (no mcp-session-id session-keying)"
3756
4657
  };
3757
4658
  }
3758
4659
  };
@@ -3990,9 +4891,53 @@ function depListed(pkgJson) {
3990
4891
  }
3991
4892
  return false;
3992
4893
  }
4894
+ var SDK_V2_PKG = "@modelcontextprotocol/server";
4895
+ var DEP_KEYS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
4896
+ function sdkV2Dependency(pkgJson) {
4897
+ if (!pkgJson) return { kind: "none" };
4898
+ for (const key of DEP_KEYS) {
4899
+ const deps = pkgJson[key];
4900
+ if (!deps || typeof deps !== "object") continue;
4901
+ const range = deps[SDK_V2_PKG];
4902
+ if (typeof range !== "string") continue;
4903
+ const m = range.trim().match(/^(?:[\^~]|>=|>|=)?\s*v?(\d+)(?:\.(?:\d+|x|\*))*(?:\s|$|-)/);
4904
+ if (!m) return { kind: "unreadable", range };
4905
+ return Number(m[1]) >= 2 ? { kind: "v2", range } : { kind: "below-v2", range };
4906
+ }
4907
+ return { kind: "none" };
4908
+ }
4909
+ var HANDROLLED_FIELD_RE = /\b(resultType|ttlMs|cacheScope)\s*:/;
4910
+ function handRolledBuilder(tree) {
4911
+ for (const file of tree.files) {
4912
+ const code = file.lines;
4913
+ let hintsDepth = 0;
4914
+ for (const line of code) {
4915
+ if (line.comment) continue;
4916
+ const opens = (line.raw.match(/\{/g) ?? []).length;
4917
+ const closes = (line.raw.match(/\}/g) ?? []).length;
4918
+ const startsHints = /\bcacheHints\s*:/.test(line.raw);
4919
+ const inside = hintsDepth > 0 || startsHints;
4920
+ if (!inside) {
4921
+ const m = line.raw.match(HANDROLLED_FIELD_RE);
4922
+ if (m) return { file: file.rel, line: line.n, field: m[1] };
4923
+ }
4924
+ if (startsHints || hintsDepth > 0) {
4925
+ hintsDepth += opens - closes;
4926
+ if (hintsDepth < 0) hintsDepth = 0;
4927
+ }
4928
+ }
4929
+ }
4930
+ return null;
4931
+ }
3993
4932
  var d7Inner = {
3994
4933
  id: "D7 protocol-kit",
3995
4934
  run(tree) {
4935
+ const v2 = (r) => sdkV2Dependency({ dependencies: { [SDK_V2_PKG]: r } }).kind;
4936
+ if (v2("^2.3.1") !== "v2" || v2(">=2 <3") !== "v2" || v2("^1.12.0") !== "below-v2" || v2("latest") !== "unreadable" || !HANDROLLED_FIELD_RE.test('{ resultType: "complete" }') || HANDROLLED_FIELD_RE.test("{ resultTypes: [] }")) {
4937
+ throw new Error(
4938
+ "D7 control FAILED: SDK v2 range reader / hand-rolled matcher broken \u2014 verdict untrustworthy"
4939
+ );
4940
+ }
3996
4941
  const listed = depListed(tree.packageJson?.json);
3997
4942
  let importLine = null;
3998
4943
  let callLine = null;
@@ -4021,9 +4966,38 @@ var d7Inner = {
4021
4966
  verdict: "protocol-kit listed but no builder called -> wire buildToolsListResult/handleServerDiscover"
4022
4967
  };
4023
4968
  }
4969
+ const pkgAnchor = tree.packageJson ? tree.packageJson.path.split("/").slice(-2).join("/") : "-";
4970
+ const sdk = sdkV2Dependency(tree.packageJson?.json);
4971
+ if (sdk.kind === "unreadable") {
4972
+ const reason = `${SDK_V2_PKG} is declared as "${sdk.range}", whose lowest admissible major cannot be read \u2014 whether the SDK v2 path applies is undecidable (D7)`;
4973
+ return {
4974
+ id: this.id,
4975
+ anchor: pkgAnchor,
4976
+ state: "could-not-judge",
4977
+ reason,
4978
+ verdict: `could not judge: ${reason}`
4979
+ };
4980
+ }
4981
+ if (sdk.kind === "v2") {
4982
+ const hand = handRolledBuilder(tree);
4983
+ if (hand) {
4984
+ return {
4985
+ id: this.id,
4986
+ anchor: `${hand.file}:${hand.line}`,
4987
+ state: "non-conforming",
4988
+ verdict: `hand-rolled result builder (\`${hand.field}:\` written in source at ${hand.file}:${hand.line}) and @vantageos/mcp-protocol-kit not a dependency -> let the SDK emit resultType/ttlMs/cacheScope (cacheHints) or adopt the kit (D7)`
4989
+ };
4990
+ }
4991
+ return {
4992
+ id: this.id,
4993
+ anchor: pkgAnchor,
4994
+ state: "present",
4995
+ verdict: `${SDK_V2_PKG} ${sdk.range} (SDK v2) and no hand-rolled result builder found: the SDK emits resultType/ttlMs/cacheScope, so protocol-kit is not required (D7). That the SDK serves all three fields is proven on the served response (S-2/S-3), not here`
4996
+ };
4997
+ }
4024
4998
  return {
4025
4999
  id: this.id,
4026
- anchor: tree.packageJson ? tree.packageJson.path.split("/").slice(-2).join("/") : "-",
5000
+ anchor: pkgAnchor,
4027
5001
  state: "absent",
4028
5002
  verdict: "hand-rolled builders, protocol-kit not a dependency -> add @vantageos/mcp-protocol-kit"
4029
5003
  };
@@ -4033,7 +5007,7 @@ var d7 = guardUnreadablePackageJson(d7Inner, { authoritative: true });
4033
5007
 
4034
5008
  // src/detectors/d8.ts
4035
5009
  import { existsSync as existsSync7, statSync as statSync3 } from "fs";
4036
- import { dirname as dirname2, join as join7, relative as relative3 } from "path";
5010
+ import { dirname as dirname3, join as join7, relative as relative4 } from "path";
4037
5011
  var POSITIVE_CONTROL2 = 'server.registerTool("list_items", { ... }, handler);';
4038
5012
  function d8PositiveControl() {
4039
5013
  return {
@@ -4043,9 +5017,9 @@ function d8PositiveControl() {
4043
5017
  };
4044
5018
  }
4045
5019
  function resolveBaseDir(tree) {
4046
- if (tree.packageJson) return dirname2(tree.packageJson.path);
5020
+ if (tree.packageJson) return dirname3(tree.packageJson.path);
4047
5021
  try {
4048
- return statSync3(tree.root).isDirectory() ? tree.root : dirname2(tree.root);
5022
+ return statSync3(tree.root).isDirectory() ? tree.root : dirname3(tree.root);
4049
5023
  } catch {
4050
5024
  return tree.root;
4051
5025
  }
@@ -4055,7 +5029,7 @@ function findEvalsFile(tree) {
4055
5029
  for (let depth = 0; depth < 8; depth++) {
4056
5030
  const candidate = join7(dir, "evals", "evals.json");
4057
5031
  if (existsSync7(candidate)) return candidate;
4058
- const parent = dirname2(dir);
5032
+ const parent = dirname3(dir);
4059
5033
  if (parent === dir) break;
4060
5034
  dir = parent;
4061
5035
  }
@@ -4116,7 +5090,7 @@ var d8 = {
4116
5090
  };
4117
5091
  }
4118
5092
  const baseDir = resolveBaseDir(tree);
4119
- const displayPath = relative3(baseDir, evalsPath) || evalsPath;
5093
+ const displayPath = relative4(baseDir, evalsPath) || evalsPath;
4120
5094
  const parsed = readJsonObject(evalsPath);
4121
5095
  if (parsed === null || !Array.isArray(parsed.cases)) {
4122
5096
  return couldNotJudge(
@@ -4200,7 +5174,7 @@ var d8 = {
4200
5174
 
4201
5175
  // src/detectors/d9.ts
4202
5176
  import { existsSync as existsSync8 } from "fs";
4203
- import { dirname as dirname3, join as join8 } from "path";
5177
+ import { dirname as dirname4, join as join8 } from "path";
4204
5178
  var SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
4205
5179
  var POSITIVE_CONTROL3 = "1.2.3-beta.1+build.7";
4206
5180
  function d9PositiveControl() {
@@ -4248,7 +5222,7 @@ var d9Inner = {
4248
5222
  verdict: `no "version" field in ${pkgPath.split("/").pop()} -> set a valid SemVer version`
4249
5223
  };
4250
5224
  }
4251
- const projectDir = dirname3(pkgPath);
5225
+ const projectDir = dirname4(pkgPath);
4252
5226
  const changelogPath = join8(projectDir, "CHANGELOG.md");
4253
5227
  const hasChangelog = existsSync8(changelogPath);
4254
5228
  const validSemver = SEMVER_RE.test(version);
@@ -4280,7 +5254,7 @@ var d9 = guardUnreadablePackageJson(d9Inner, { authoritative: false });
4280
5254
 
4281
5255
  // src/detectors/d10.ts
4282
5256
  import { existsSync as existsSync9 } from "fs";
4283
- import { dirname as dirname4, join as join9 } from "path";
5257
+ import { dirname as dirname5, join as join9 } from "path";
4284
5258
  var DESCRIPTION_FR_RE = /"description_fr"\s*:\s*"[^"]+"/;
4285
5259
  var POSITIVE_CONTROL4 = '"description_fr": "Un serveur MCP conforme.",';
4286
5260
  function d10PositiveControl() {
@@ -4315,17 +5289,38 @@ function firstSourceAnchor(tree, re) {
4315
5289
  function scanSourceDescriptionFr(tree) {
4316
5290
  const recognized = firstSourceAnchor(tree, SOURCE_DESCRIPTION_FR_RE);
4317
5291
  if (recognized) return { present: true, anchor: recognized.anchor };
4318
- const mentioned = firstSourceAnchor(tree, MENTION_RE);
4319
- if (mentioned) {
4320
- return {
4321
- present: false,
4322
- anchor: null,
4323
- undecidable: {
4324
- anchor: mentioned.anchor,
4325
- reason: `description_fr occurrence at ${mentioned.anchor} (\`${mentioned.raw}\`) does not match the recognized source form /${SOURCE_DESCRIPTION_FR_RE.source}/ \u2014 unknown bilingual-declaration shape, verdict undecidable`
5292
+ const bindings = resolveNameBindings(tree);
5293
+ let undecidable;
5294
+ for (const file of tree.files) {
5295
+ for (let i = 0; i < file.lines.length; i++) {
5296
+ const line = file.lines[i];
5297
+ if (line.comment || !MENTION_RE.test(line.raw)) continue;
5298
+ const anchor = `${file.rel}:${line.n}`;
5299
+ const key = line.raw.match(/description_fr\s*:/);
5300
+ if (!key || key.index === void 0) {
5301
+ undecidable ??= {
5302
+ anchor,
5303
+ reason: `description_fr occurrence at ${anchor} (\`${line.raw.trim()}\`) does not match the recognized source form /${SOURCE_DESCRIPTION_FR_RE.source}/ \u2014 unknown bilingual-declaration shape, verdict undecidable`
5304
+ };
5305
+ continue;
4326
5306
  }
4327
- };
5307
+ const after = line.raw.slice(key.index + key[0].length);
5308
+ const tail = file.lines.slice(i + 1, i + 9).filter((l) => !l.comment).map((l) => l.raw).join("\n");
5309
+ const v = readDescriptionValue(`${after}
5310
+ ${tail}`);
5311
+ if (v.kind === "literal" && v.text.length > 0) return { present: true, anchor };
5312
+ if (v.kind === "non-literal") {
5313
+ const ident = v.expr.match(/^([A-Za-z_$][\w$]*)\s*[,}]?\s*$/);
5314
+ if (ident && bindings.get(ident[1])) return { present: true, anchor };
5315
+ const form = ident ? "an identifier that does not resolve to a same-tree string const" : /\w\s*\(/.test(v.expr) ? "a call" : "a non-literal expression";
5316
+ undecidable ??= {
5317
+ anchor,
5318
+ reason: `description_fr at ${anchor} is followed by ${form} (\`${v.expr}\`) \u2014 the French text is computed, so whether it is declared and non-empty is unverifiable statically; verdict undecidable`
5319
+ };
5320
+ }
5321
+ }
4328
5322
  }
5323
+ if (undecidable) return { present: false, anchor: null, undecidable };
4329
5324
  return { present: false, anchor: null };
4330
5325
  }
4331
5326
  var d10Inner = {
@@ -4345,6 +5340,11 @@ var d10Inner = {
4345
5340
  `D10 source-form control FAILED: /${sourceControl.pattern}/ accept=${sourceControl.matchedPositive} reject=${!sourceControl.matchedNegative} \u2014 matcher broken, absence verdict untrustworthy`
4346
5341
  );
4347
5342
  }
5343
+ if (readDescriptionValue('\n "Un serveur."').kind !== "literal" || readDescriptionValue("t(locale, k)").kind !== "non-literal") {
5344
+ throw new Error(
5345
+ "D10 value-reader control FAILED: must accept a next-line literal and reject a call \u2014 verdict untrustworthy"
5346
+ );
5347
+ }
4348
5348
  if (!tree.packageJson) {
4349
5349
  return {
4350
5350
  id: this.id,
@@ -4355,7 +5355,7 @@ var d10Inner = {
4355
5355
  };
4356
5356
  }
4357
5357
  const { path: pkgPath, json: pkg } = tree.packageJson;
4358
- const root = dirname4(pkgPath);
5358
+ const root = dirname5(pkgPath);
4359
5359
  const descriptionFr = typeof pkg.description_fr === "string" ? pkg.description_fr.trim() : "";
4360
5360
  const hasDescriptionFrJson = descriptionFr.length > 0;
4361
5361
  const sourceScan = scanSourceDescriptionFr(tree);
@@ -4775,11 +5775,11 @@ var d15 = {
4775
5775
 
4776
5776
  // src/detectors/d16.ts
4777
5777
  import { existsSync as existsSync10, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
4778
- import { dirname as dirname5, join as join10 } from "path";
5778
+ import { dirname as dirname6, join as join10 } from "path";
4779
5779
  var DISCOVER_RE2 = /server\/discover|DiscoverResult|ServerDiscover|handleServerDiscover/;
4780
5780
  var HEALTH_ROUTE_FRAMEWORK_RE = /\b[A-Za-z_$][\w$]*\.(?:get|post|put|patch|delete|all|route|use)\s*\(\s*["'`][^"'`]*\/health/;
4781
5781
  var HEALTH_ROUTE_RAWPATH_RE = /\bpathname\s*===?\s*["'`][^"'`]*\/health/;
4782
- var HEALTH_ROUTE_RE = new RegExp(
5782
+ var HEALTH_ROUTE_RE2 = new RegExp(
4783
5783
  `${HEALTH_ROUTE_FRAMEWORK_RE.source}|${HEALTH_ROUTE_RAWPATH_RE.source}`
4784
5784
  );
4785
5785
  var VERSION_FIELD_RE = /\bversion\s*:/;
@@ -4790,6 +5790,7 @@ var VERSION_COMPUTED_RE = /\bversion\s*:\s*[A-Za-z_$][\w.]*\s*\(/;
4790
5790
  var VERSION_LITERAL_RE = /\bversion\s*:\s*["'`][^"'`]+["'`]/;
4791
5791
  var AUTH_GATE_RE = /requireAuth|verifyBearer|assertAuth|ensureAuthenticated|withAuth|requireBearer/;
4792
5792
  var WINDOW = 25;
5793
+ var SDK_SERVER_NEW_RE = /\bnew\s+McpServer\s*\(/;
4793
5794
  var CONTROLS2 = [
4794
5795
  {
4795
5796
  key: "surface",
@@ -4799,7 +5800,7 @@ var CONTROLS2 = [
4799
5800
  },
4800
5801
  {
4801
5802
  key: "health-framework",
4802
- re: HEALTH_ROUTE_RE,
5803
+ re: HEALTH_ROUTE_RE2,
4803
5804
  positive: 'app.get("/health", handler)',
4804
5805
  negative: 'app.get("/tools", handler)'
4805
5806
  },
@@ -4808,7 +5809,7 @@ var CONTROLS2 = [
4808
5809
  // Positive is the REAL vantage-registry-mcp:297 shape; negative is the
4809
5810
  // sibling `/mcp` route on the same server (a near-miss that must NOT match).
4810
5811
  key: "health-rawpath",
4811
- re: HEALTH_ROUTE_RE,
5812
+ re: HEALTH_ROUTE_RE2,
4812
5813
  positive: 'if (req.method === "GET" && url.pathname === "/health") {',
4813
5814
  negative: 'if (url.pathname === "/mcp") {'
4814
5815
  },
@@ -4818,6 +5819,12 @@ var CONTROLS2 = [
4818
5819
  positive: "version: pkg.version",
4819
5820
  negative: "version: tool.version"
4820
5821
  },
5822
+ {
5823
+ key: "sdk-construction",
5824
+ re: SDK_SERVER_NEW_RE,
5825
+ positive: "const s = new McpServer({ name, version }, opts)",
5826
+ negative: "const s = new Server({ name, version })"
5827
+ },
4821
5828
  {
4822
5829
  key: "auth-gate",
4823
5830
  re: AUTH_GATE_RE,
@@ -4890,13 +5897,58 @@ function classifySurface(file, startIdx, anchor, pkgIdents) {
4890
5897
  if (!hasVersion) return { kind: "absent-version", anchor };
4891
5898
  return { kind: "unreadable-source", anchor };
4892
5899
  }
5900
+ function packageJsonVersionConsts(tree) {
5901
+ const names = /* @__PURE__ */ new Set();
5902
+ const decl = /\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\b/;
5903
+ for (const file of tree.files) {
5904
+ const code = file.lines.filter((l) => !l.comment);
5905
+ for (let i = 0; i < code.length; i++) {
5906
+ const m = code[i].raw.match(decl);
5907
+ if (!m) continue;
5908
+ let stmt = "";
5909
+ let depth = 0;
5910
+ for (let j = i; j < Math.min(code.length, i + 10); j++) {
5911
+ const raw = code[j].raw;
5912
+ stmt += `${raw}
5913
+ `;
5914
+ depth += (raw.match(/[({[]/g) ?? []).length - (raw.match(/[)}\]]/g) ?? []).length;
5915
+ if (depth <= 0 && /;\s*$/.test(raw)) break;
5916
+ }
5917
+ if (/package\.json/.test(stmt) && /\.version\b/.test(stmt)) names.add(m[1]);
5918
+ }
5919
+ }
5920
+ return names;
5921
+ }
5922
+ function sdkServerVersionSurface(tree) {
5923
+ const derived = packageJsonVersionConsts(tree);
5924
+ for (const file of tree.files) {
5925
+ const pkgIdents = collectPkgIdents(file);
5926
+ for (let idx = 0; idx < file.lines.length; idx++) {
5927
+ const line = file.lines[idx];
5928
+ if (line.comment || !SDK_SERVER_NEW_RE.test(line.raw)) continue;
5929
+ const body = extractHandlerBody(file, idx);
5930
+ if (body === null) continue;
5931
+ const v = body.match(/\bversion\s*:\s*([A-Za-z_$][\w$]*(?:\.[\w$]+)*)\s*(?:[,}]|$)/m);
5932
+ if (!v) continue;
5933
+ const expr = v[1];
5934
+ const root = expr.split(".")[0];
5935
+ const direct = pkgIdents.has(root) && expr.endsWith(".version") || PKG_SOURCE_RE.test(`version: ${expr}`);
5936
+ if (direct || !expr.includes(".") && derived.has(expr)) {
5937
+ return { kind: "present", anchor: `${file.rel}:${line.n}`, via: "sdk-server-info" };
5938
+ }
5939
+ }
5940
+ }
5941
+ return null;
5942
+ }
4893
5943
  function evalSurfaces(tree) {
4894
5944
  const out = [];
5945
+ const sdk = sdkServerVersionSurface(tree);
5946
+ if (sdk) out.push(sdk);
4895
5947
  for (const file of tree.files) {
4896
5948
  const pkgIdents = collectPkgIdents(file);
4897
5949
  file.lines.forEach((line, idx) => {
4898
5950
  if (line.comment) return;
4899
- if (DISCOVER_RE2.test(line.raw) || HEALTH_ROUTE_RE.test(line.raw)) {
5951
+ if (DISCOVER_RE2.test(line.raw) || HEALTH_ROUTE_RE2.test(line.raw)) {
4900
5952
  out.push(classifySurface(file, idx, `${file.rel}:${line.n}`, pkgIdents));
4901
5953
  }
4902
5954
  });
@@ -4928,7 +5980,7 @@ var d16 = {
4928
5980
  id: this.id,
4929
5981
  anchor: present.anchor,
4930
5982
  state: "present",
4931
- verdict: `[static read] version exposed pre-auth at ${present.anchor}, sourced from package.json (Server Rule 16)`
5983
+ verdict: present.via === "sdk-server-info" ? `[static read] version sourced from package.json is carried by the SDK server construction (new McpServer({ name, version })) at ${present.anchor}; the SDK serves it on server/discover and initialize. Declared residual: whether that SDK transport is reachable pre-auth is not readable statically \u2014 run --mode=dynamic for the served-value half (Server Rule 16)` : `[static read] version exposed pre-auth at ${present.anchor}, sourced from package.json (Server Rule 16)`
4932
5984
  };
4933
5985
  }
4934
5986
  const decisive = surfaces.find(
@@ -5042,14 +6094,14 @@ var d16 = {
5042
6094
  };
5043
6095
  function readPackageVersion(root) {
5044
6096
  try {
5045
- let dir = statSync4(root).isDirectory() ? root : dirname5(root);
6097
+ let dir = statSync4(root).isDirectory() ? root : dirname6(root);
5046
6098
  for (let depth = 0; depth < 8; depth++) {
5047
6099
  const candidate = join10(dir, "package.json");
5048
6100
  if (existsSync10(candidate)) {
5049
6101
  const pkg = JSON.parse(readFileSync8(candidate, "utf8"));
5050
6102
  return typeof pkg.version === "string" ? pkg.version : null;
5051
6103
  }
5052
- const parent = dirname5(dir);
6104
+ const parent = dirname6(dir);
5053
6105
  if (parent === dir) break;
5054
6106
  dir = parent;
5055
6107
  }
@@ -5061,7 +6113,7 @@ function readPackageVersion(root) {
5061
6113
 
5062
6114
  // src/detectors/dAppI18n.ts
5063
6115
  import { existsSync as existsSync11, readFileSync as readFileSync9 } from "fs";
5064
- import { dirname as dirname6, join as join11 } from "path";
6116
+ import { dirname as dirname7, join as join11 } from "path";
5065
6117
  function flattenKeys(obj, prefix = "") {
5066
6118
  if (obj === null || typeof obj !== "object" || Array.isArray(obj)) {
5067
6119
  return prefix ? [prefix] : [];
@@ -5108,7 +6160,7 @@ var dAppI18n = {
5108
6160
  `DAppI18n key-diff control FAILED: detectsGap=${control.detectsGap} detectsComplete=${control.detectsComplete} \u2014 matcher broken, verdict untrustworthy`
5109
6161
  );
5110
6162
  }
5111
- const root = tree.packageJson ? dirname6(tree.packageJson.path) : tree.root;
6163
+ const root = tree.packageJson ? dirname7(tree.packageJson.path) : tree.root;
5112
6164
  const i18nDir = join11(root, "i18n");
5113
6165
  const enPath = join11(i18nDir, "en.json");
5114
6166
  const frPath = join11(i18nDir, "fr.json");
@@ -5981,7 +7033,7 @@ var dExt4 = {
5981
7033
 
5982
7034
  // src/detectors/dExt5.ts
5983
7035
  import { existsSync as existsSync12 } from "fs";
5984
- import { dirname as dirname7, join as join14 } from "path";
7036
+ import { dirname as dirname8, join as join14 } from "path";
5985
7037
  function nonEmptyString(v) {
5986
7038
  return typeof v === "string" && v.trim().length > 0;
5987
7039
  }
@@ -6018,7 +7070,7 @@ var dExt5 = {
6018
7070
  }
6019
7071
  for (const manifestPath2 of manifests) {
6020
7072
  const shortPath = manifestPath2.split("/").slice(-2).join("/");
6021
- const root = dirname7(manifestPath2);
7073
+ const root = dirname8(manifestPath2);
6022
7074
  const cannotRead = (reason) => ({
6023
7075
  id: this.id,
6024
7076
  anchor: shortPath,
@@ -6085,7 +7137,7 @@ var dExt5 = {
6085
7137
 
6086
7138
  // src/detectors/dExt7.ts
6087
7139
  import { existsSync as existsSync13 } from "fs";
6088
- import { dirname as dirname8, join as join15 } from "path";
7140
+ import { dirname as dirname9, join as join15 } from "path";
6089
7141
  var ABSENCE_FLAG_FIELDS = [
6090
7142
  "extensionAbsent",
6091
7143
  "extension_absent",
@@ -6149,7 +7201,7 @@ var dExt7 = {
6149
7201
  const unreadable = [];
6150
7202
  const decided = (() => {
6151
7203
  for (const manifestPath2 of manifests) {
6152
- const extDir = dirname8(manifestPath2);
7204
+ const extDir = dirname9(manifestPath2);
6153
7205
  const extLabel = extDir.split("/").pop() ?? extDir;
6154
7206
  const evalsPath = join15(extDir, "evals", "evals.json");
6155
7207
  const shortPath = `${extLabel}/evals/evals.json`;
@@ -6218,7 +7270,7 @@ var dExt7 = {
6218
7270
 
6219
7271
  // src/detectors/dExt8.ts
6220
7272
  import { existsSync as existsSync14 } from "fs";
6221
- import { dirname as dirname9, join as join16 } from "path";
7273
+ import { dirname as dirname10, join as join16 } from "path";
6222
7274
  var SEMVER_RE2 = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
6223
7275
  var POSITIVE_CONTROL5 = "1.2.3-beta.1+build.7";
6224
7276
  function dExt8PositiveControl() {
@@ -6250,7 +7302,7 @@ var dExt8 = {
6250
7302
  let finding;
6251
7303
  let judgedConforming = 0;
6252
7304
  for (const manifestPath2 of manifests) {
6253
- const extDir = dirname9(manifestPath2);
7305
+ const extDir = dirname10(manifestPath2);
6254
7306
  const extLabel = extDir.split("/").pop() ?? extDir;
6255
7307
  const pkgPath = join16(extDir, "package.json");
6256
7308
  const shortPkgPath = `${extLabel}/package.json`;
@@ -6806,6 +7858,50 @@ function collectLogCalls(file) {
6806
7858
  }
6807
7859
  return { calls, unbalanced };
6808
7860
  }
7861
+ var LITERAL_VALUE_RE = /^\s*(?:"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`[^`$\\]*`|-?\d[\d_.]*)\s*[;,)]?\s*$/;
7862
+ function codeLines(tree) {
7863
+ const out = [];
7864
+ for (const file of tree.files) for (const l of file.lines) if (!l.comment) out.push(l.raw);
7865
+ return out;
7866
+ }
7867
+ var esc = (x) => x.replace(/[$]/g, "\\$&");
7868
+ function resolvesToLiteral(tree, path) {
7869
+ const lines = codeLines(tree);
7870
+ const plainLiteral = (name) => {
7871
+ const decl = new RegExp(`\\b(?:const|let|var)\\s+${esc(name)}\\s*(?::[^=]+)?=\\s*(.*)$`);
7872
+ const found = lines.map((l) => l.match(decl)).filter((m) => m !== null);
7873
+ if (found.length !== 1 || !LITERAL_VALUE_RE.test(found[0][1])) return false;
7874
+ const param = new RegExp(`[(,]\\s*${esc(name)}\\s*[:,)=?]|\\bfunction\\s+${esc(name)}\\b`);
7875
+ return !lines.some((l) => param.test(l));
7876
+ };
7877
+ if (/[()]/.test(path)) return false;
7878
+ if (!path.includes(".")) return plainLiteral(path);
7879
+ const prop = path.split(".").pop() ?? "";
7880
+ const def = new RegExp(`(?:^|[\\s{,])${esc(prop)}\\s*:\\s*([^,}\\n]+)`);
7881
+ const shorthand = new RegExp(`[{,]\\s*${esc(prop)}\\s*[,}]`);
7882
+ const defs = lines.map((l) => l.match(def)).filter((m) => m !== null);
7883
+ if (defs.length === 0 || lines.some((l) => shorthand.test(l))) return false;
7884
+ return defs.every((m) => {
7885
+ const v = m[1].trim();
7886
+ return LITERAL_VALUE_RE.test(v) || /^[A-Za-z_$][\w$]*$/.test(v) && plainLiteral(v);
7887
+ });
7888
+ }
7889
+ function referenceHits(tree, refs) {
7890
+ const out = [];
7891
+ for (const m of refs.matchAll(/[A-Za-z_$][\w$]*(?:\??\.[A-Za-z_$][\w$]*)*\s*\(?/g)) {
7892
+ const isCall = m[0].trimEnd().endsWith("(");
7893
+ const path = m[0].replace(/\s*\(?$/, "").replace(/\?/g, "");
7894
+ const form = SECRET_RE.test(path) ? "A" : PII_RE.test(path) ? "C" : INTERNAL_RE.test(path) ? "D" : null;
7895
+ if (form) out.push({ form, path, resolved: !isCall && resolvesToLiteral(tree, path) });
7896
+ }
7897
+ return out;
7898
+ }
7899
+ var FORM_TEXT = {
7900
+ A: "log call references a secret-like identifier -> never log secrets; redact before logging",
7901
+ C: "log call references a PII-shaped identifier -> never log PII; redact before logging",
7902
+ D: "log call references an internal-infrastructure identifier -> never log internal system detail; redact before logging",
7903
+ B: "raw error.stack serialized into a client-facing response -> return an McpError message, never the raw stack"
7904
+ };
6809
7905
  var PIN10 = "a74a042b53c71a095ab88b3073bd47728eeda814";
6810
7906
  var dServer6 = {
6811
7907
  id: "DServer6 secret-hygiene",
@@ -6833,8 +7929,15 @@ var dServer6 = {
6833
7929
  `DServer6 stack control FAILED: /${controls.stack.pattern}/ accept(\`${STACK_POSITIVE}\`)=${controls.stack.positive} reject(\`${STACK_NEGATIVE}\`)=${!controls.stack.negative} \u2014 matcher broken, verdict untrustworthy`
6834
7930
  );
6835
7931
  }
7932
+ if (!LITERAL_VALUE_RE.test('"0.0.0.0";') || LITERAL_VALUE_RE.test("os.hostname();")) {
7933
+ throw new Error(
7934
+ "DServer6 literal-value control FAILED: must accept a string literal and reject a call \u2014 verdict untrustworthy"
7935
+ );
7936
+ }
6836
7937
  const unreadable = [];
6837
7938
  const decided = (() => {
7939
+ const findings = [];
7940
+ const resolved = [];
6838
7941
  for (const file of tree.files) {
6839
7942
  const { calls, unbalanced } = collectLogCalls(file);
6840
7943
  for (const anchor of unbalanced) {
@@ -6851,30 +7954,20 @@ var dServer6 = {
6851
7954
  });
6852
7955
  continue;
6853
7956
  }
6854
- const refs = referencedIdentifiers(call.argText);
6855
- if (SECRET_RE.test(refs)) {
6856
- return {
6857
- id: this.id,
6858
- anchor: call.anchor,
6859
- state: "non-conforming",
6860
- verdict: "Form A \u2014 log call references a secret-like identifier (positive-control OK) -> never log secrets; redact before logging (C-6)"
6861
- };
6862
- }
6863
- if (PII_RE.test(refs)) {
6864
- return {
6865
- id: this.id,
6866
- anchor: call.anchor,
6867
- state: "non-conforming",
6868
- verdict: "Form C \u2014 log call references a PII-shaped identifier (positive-control OK) -> never log PII; redact before logging (C-6)"
6869
- };
6870
- }
6871
- if (INTERNAL_RE.test(refs)) {
6872
- return {
6873
- id: this.id,
6874
- anchor: call.anchor,
6875
- state: "non-conforming",
6876
- verdict: "Form D \u2014 log call references an internal-infrastructure identifier (positive-control OK) -> never log internal system detail; redact before logging (C-6)"
6877
- };
7957
+ const hits = referenceHits(tree, referencedIdentifiers(call.argText));
7958
+ for (const form of ["A", "C", "D"]) {
7959
+ const mine = hits.filter((h) => h.form === form);
7960
+ if (mine.length === 0) continue;
7961
+ const open = mine.filter((h) => !h.resolved);
7962
+ if (open.length === 0) {
7963
+ resolved.push(`${call.anchor} (${mine.map((h) => h.path).join(", ")})`);
7964
+ continue;
7965
+ }
7966
+ findings.push({
7967
+ site: call.anchor,
7968
+ form,
7969
+ note: `identifier-name match, value unresolved: ${open.map((h) => h.path).join(", ")}`
7970
+ });
6878
7971
  }
6879
7972
  }
6880
7973
  }
@@ -6882,20 +7975,29 @@ var dServer6 = {
6882
7975
  for (const line of file.lines) {
6883
7976
  if (line.comment) continue;
6884
7977
  if (STACK_RE.test(line.raw) && CLIENT_SINK_RE.test(line.raw)) {
6885
- return {
6886
- id: this.id,
6887
- anchor: `${file.rel}:${line.n}`,
6888
- state: "non-conforming",
6889
- verdict: "Form B \u2014 raw error.stack serialized into a client-facing response (positive-control OK) -> return an McpError message, never the raw stack (C-6)"
6890
- };
7978
+ findings.push({
7979
+ site: `${file.rel}:${line.n}`,
7980
+ form: "B",
7981
+ note: "client-facing stack"
7982
+ });
6891
7983
  }
6892
7984
  }
6893
7985
  }
7986
+ if (findings.length > 0) {
7987
+ const forms = [...new Set(findings.map((f) => f.form))].sort();
7988
+ const sites = findings.map((f) => `${f.site} [Form ${f.form}, ${f.note}]`).join("; ");
7989
+ return {
7990
+ id: this.id,
7991
+ anchor: findings[0].site,
7992
+ state: "non-conforming",
7993
+ verdict: `${findings.length} site(s) (positive-control OK): ${sites}. ${forms.map((f) => `Form ${f} \u2014 ${FORM_TEXT[f]}`).join("; ")} (C-6)${resolved.length > 0 ? `; not counted, value is a literal constant: ${resolved.join("; ")}` : ""}`
7994
+ };
7995
+ }
6894
7996
  return {
6895
7997
  id: this.id,
6896
7998
  anchor: tree.files.length > 0 ? `${tree.files[0].rel}:1` : tree.root,
6897
7999
  state: "present",
6898
- verdict: "no secret/PII/internal-detail referenced in any log call; no raw error.stack exposed to the client (all positive-controls OK). Logging-sink configuration and explicit-activation-only diagnostic mode are the gate/runtime half, out of static scope (declared)."
8000
+ verdict: `no secret/PII/internal-detail referenced in any log call; no raw error.stack exposed to the client (all positive-controls OK)${resolved.length > 0 ? `; ${resolved.length} name match(es) resolved to a literal constant and not counted: ${resolved.join("; ")}` : ""}. Logging-sink configuration and explicit-activation-only diagnostic mode are the gate/runtime half, out of static scope (declared).`
6899
8001
  };
6900
8002
  })();
6901
8003
  return settle(this.id, decided, unreadable);
@@ -7051,7 +8153,7 @@ var PRM_ROUTE_RE = /(?:app|router|server)\.(?:get|route|all)\s*\(\s*["'`][^"'`]*
7051
8153
  var CLERK_RE = /clerk/i;
7052
8154
  var AS_DISCOVERY_RE = /authorizationServers|authorization_servers|openid-configuration|oauth-authorization-server/i;
7053
8155
  var JWKS_RE = /jwks|createRemoteJWKSet|createLocalJWKSet/i;
7054
- var RS256_RE = /RS256|jwtVerify|verifyMcpBearer|verifyBearer/;
8156
+ var RS256_RE = /RS256|jwtVerify|verifyMcpBearer|verifyBearer|verifyMcpAccessToken/;
7055
8157
  var AUDIENCE_RE = /audience|\baud\b/i;
7056
8158
  var ISSUER_RE = /issuer/i;
7057
8159
  var REUSE_RE = /@vantageos\/cloud-identity/;
@@ -7085,13 +8187,163 @@ var CONTROLS4 = [
7085
8187
  negative: 'from "@vantageos/other-kit"'
7086
8188
  }
7087
8189
  ];
8190
+ var DELEGATING_FNS = ["unauthorizedChallenge", "verifyMcpAccessToken"];
8191
+ var KIT_IMPORT_RE2 = /import\s+(?:type\s+)?\{([^}]*)\}\s*from\s*["']@vantageos\/cloud-identity(?:\/resource-server)?["']/g;
8192
+ function delegatingLocals(text) {
8193
+ const out = [];
8194
+ for (const m of text.matchAll(KIT_IMPORT_RE2)) {
8195
+ for (const part of m[1].split(",")) {
8196
+ const spec = part.trim().replace(/^type\s+/, "");
8197
+ if (!spec) continue;
8198
+ const [imported, local] = spec.split(/\s+as\s+/).map((x) => x.trim());
8199
+ if (DELEGATING_FNS.includes(imported)) out.push(local ?? imported);
8200
+ }
8201
+ }
8202
+ return out;
8203
+ }
8204
+ var PRM_PATH_LITERAL = ".well-known/oauth-protected-resource";
8205
+ function prmRouteViaConstant(tree) {
8206
+ for (const file of tree.files) {
8207
+ const text = file.lines.filter((l) => !l.comment).map((l) => l.raw).join("\n");
8208
+ if (!text.includes(PRM_PATH_LITERAL)) continue;
8209
+ const holders = /* @__PURE__ */ new Set();
8210
+ const decl = /\bconst\s+([A-Za-z_$][\w$]*)\s*(?::[^=\n]+)?=\s*(\[[^\]]*\]|"[^"\n]*"|'[^'\n]*'|`[^`]*`)/g;
8211
+ for (const m of text.matchAll(decl)) if (m[2].includes(PRM_PATH_LITERAL)) holders.add(m[1]);
8212
+ if (holders.size === 0) continue;
8213
+ const names = new Set(holders);
8214
+ const loop = /\bfor\s*\(\s*(?:const|let)\s+([A-Za-z_$][\w$]*)\s+of\s+([A-Za-z_$][\w$]*)\s*\)/g;
8215
+ for (const m of text.matchAll(loop)) if (holders.has(m[2])) names.add(m[1]);
8216
+ const route = /\b(?:app|router|server)\.(?:get|route|all)\s*\(\s*([A-Za-z_$][\w$]*)\s*,/g;
8217
+ for (const m of text.matchAll(route)) if (names.has(m[1])) return true;
8218
+ }
8219
+ return false;
8220
+ }
8221
+ var FORWARD_HEADER_LINE_RE = /WWW-Authenticate[^\n]*\.headers\b|\bheaders\s*:\s*[\w$.]+\.headers\b/i;
8222
+ var FORWARD_STATUS_RE = /\bstatus\s*:\s*[\w$.]+\.status\b|[\w$]+\.status\b/;
8223
+ function challengeForwarded(tree) {
8224
+ for (const file of tree.files) {
8225
+ const code = file.lines.filter((l) => !l.comment);
8226
+ const text = code.map((l) => l.raw).join("\n");
8227
+ if (!FORWARD_STATUS_RE.test(text)) continue;
8228
+ const hit = code.find((l) => FORWARD_HEADER_LINE_RE.test(l.raw));
8229
+ if (hit) return `${file.rel}:${hit.n}`;
8230
+ }
8231
+ return null;
8232
+ }
8233
+ function findDelegation(tree) {
8234
+ let call = null;
8235
+ for (const file of tree.files) {
8236
+ const text = file.lines.filter((l) => !l.comment).map((l) => l.raw).join("\n");
8237
+ const locals = delegatingLocals(text);
8238
+ if (locals.length === 0) continue;
8239
+ for (const line of file.lines) {
8240
+ if (line.comment || /^\s*(?:import\b|\}\s*from\b)/.test(line.raw)) continue;
8241
+ if (locals.some((n) => new RegExp(`\\b${n}\\s*\\(`).test(line.raw))) {
8242
+ call = `${file.rel}:${line.n}`;
8243
+ break;
8244
+ }
8245
+ }
8246
+ if (call) break;
8247
+ }
8248
+ if (!call) return null;
8249
+ const fwd = challengeForwarded(tree);
8250
+ return { anchor: fwd ?? call, forwarded: fwd !== null };
8251
+ }
8252
+ var IDENTITY_PKG = "@vantageos/cloud-identity";
8253
+ var DEPRECATED_AUTH_PKG = "@vantageos/mcp-auth-kit";
8254
+ var SHIPPED_DEP_KEYS = ["dependencies", "peerDependencies", "optionalDependencies"];
8255
+ var IDENTITY_FLOOR = [0, 15, 0];
8256
+ function lowestAdmissible(range) {
8257
+ const alts = range.split("||").map((a) => a.trim());
8258
+ let lowest = null;
8259
+ for (const alt of alts) {
8260
+ let bound = null;
8261
+ for (const tok of alt.split(/\s+/).filter(Boolean)) {
8262
+ const m = tok.match(
8263
+ /^(?:[\^~]|>=|>|=)?v?(\d+)(?:\.(\d+|x|\*))?(?:\.(\d+|x|\*))?(?:[-+].*)?$/
8264
+ );
8265
+ if (!m) {
8266
+ if (/^<=?/.test(tok)) continue;
8267
+ return null;
8268
+ }
8269
+ const t = [
8270
+ Number(m[1]),
8271
+ m[2] && /^\d+$/.test(m[2]) ? Number(m[2]) : 0,
8272
+ m[3] && /^\d+$/.test(m[3]) ? Number(m[3]) : 0
8273
+ ];
8274
+ if (!bound || cmp(t, bound) > 0) bound = t;
8275
+ }
8276
+ if (!bound) return null;
8277
+ if (!lowest || cmp(bound, lowest) < 0) lowest = bound;
8278
+ }
8279
+ return lowest;
8280
+ }
8281
+ function cmp(a, b) {
8282
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] - b[i];
8283
+ return 0;
8284
+ }
8285
+ function shippedDep(pkg, name) {
8286
+ for (const key of SHIPPED_DEP_KEYS) {
8287
+ const deps = pkg[key];
8288
+ if (deps && typeof deps === "object") {
8289
+ const v = deps[name];
8290
+ if (typeof v === "string") return { key, range: v };
8291
+ }
8292
+ }
8293
+ return null;
8294
+ }
8295
+ function packageRule(tree, id) {
8296
+ const pj = tree.packageJson;
8297
+ if (!pj || pj.unreadable) return null;
8298
+ const anchor = pj.path.split("/").slice(-2).join("/");
8299
+ const kit = shippedDep(pj.json, DEPRECATED_AUTH_PKG);
8300
+ if (kit) {
8301
+ return {
8302
+ id,
8303
+ anchor,
8304
+ state: "non-conforming",
8305
+ verdict: `package depends on ${DEPRECATED_AUTH_PKG} (${kit.key}: "${kit.range}") \u2014 deprecated -> remove it; ${IDENTITY_PKG} >= 0.15.0 is the only identity package (Server Rule 14 / S-5)`
8306
+ };
8307
+ }
8308
+ const ci = shippedDep(pj.json, IDENTITY_PKG);
8309
+ if (!ci) return null;
8310
+ const floor = lowestAdmissible(ci.range);
8311
+ if (!floor) {
8312
+ const reason = `${IDENTITY_PKG} is declared as "${ci.range}" (${ci.key}), whose lowest admissible version cannot be read \u2014 the >= 0.15.0 floor is undecidable (Server Rule 14 / S-5)`;
8313
+ return { id, anchor, state: "could-not-judge", reason, verdict: `could not judge: ${reason}` };
8314
+ }
8315
+ if (cmp(floor, IDENTITY_FLOOR) < 0) {
8316
+ return {
8317
+ id,
8318
+ anchor,
8319
+ state: "non-conforming",
8320
+ verdict: `${IDENTITY_PKG} is declared as "${ci.range}" (${ci.key}), admitting ${floor.join(".")} \u2014 below the 0.15.0 floor of the resource-server entry point -> raise the range to >= 0.15.0 (Server Rule 14 / S-5)`
8321
+ };
8322
+ }
8323
+ return null;
8324
+ }
7088
8325
  function dServer14PositiveControls() {
7089
- return CONTROLS4.map((c) => ({
8326
+ const base = CONTROLS4.map((c) => ({
7090
8327
  key: c.key,
7091
8328
  pattern: c.re.source,
7092
8329
  positive: c.re.test(c.positive),
7093
8330
  negative: c.re.test(c.negative)
7094
8331
  }));
8332
+ base.push({
8333
+ key: "dependency-floor",
8334
+ pattern: "lowestAdmissible",
8335
+ positive: lowestAdmissible("^0.14.2")?.join(".") === "0.14.2" && lowestAdmissible(">=0.15.0 <1")?.join(".") === "0.15.0",
8336
+ negative: lowestAdmissible("latest") !== null
8337
+ });
8338
+ base.push({
8339
+ key: "delegation",
8340
+ pattern: KIT_IMPORT_RE2.source,
8341
+ positive: delegatingLocals(
8342
+ 'import {\n verifyMcpAccessToken as v,\n} from "@vantageos/cloud-identity/resource-server";'
8343
+ ).join() === "v",
8344
+ negative: delegatingLocals('import { verifyMcpAccessToken } from "some-other-kit";').length > 0
8345
+ });
8346
+ return base;
7095
8347
  }
7096
8348
  function codeBlob(tree) {
7097
8349
  const parts = [];
@@ -7134,6 +8386,8 @@ var dServer14 = {
7134
8386
  );
7135
8387
  }
7136
8388
  }
8389
+ const pkgVerdict = packageRule(tree, this.id);
8390
+ if (pkgVerdict) return pkgVerdict;
7137
8391
  const blob = codeBlob(tree);
7138
8392
  const surfacePresent = WWW_AUTH_RE.test(blob) || PRM_ROUTE_RE.test(blob) || JWKS_RE.test(blob) || CLERK_RE.test(blob) || REUSE_RE.test(blob);
7139
8393
  if (!surfacePresent) {
@@ -7144,6 +8398,17 @@ var dServer14 = {
7144
8398
  verdict: "no OAuth 2.1 resource-server surface detected (n/a for Server Rule 14)"
7145
8399
  };
7146
8400
  }
8401
+ const delegation = findDelegation(tree);
8402
+ if (delegation && !delegation.forwarded && !CHALLENGE_PARAM_RE.test(blob)) {
8403
+ const reason = `${delegation.anchor} calls a challenge-returning function of @vantageos/cloud-identity but no \`<result>.status\` / \`<result>.headers\` forwarding is readable in that file \u2014 whether the 401 + WWW-Authenticate reaches the client is unverifiable statically (Server Rule 14, Element 1)`;
8404
+ return {
8405
+ id: this.id,
8406
+ anchor: delegation.anchor,
8407
+ state: "could-not-judge",
8408
+ reason,
8409
+ verdict: `could not judge: ${reason}`
8410
+ };
8411
+ }
7147
8412
  if (!CHALLENGE_PARAM_RE.test(blob)) {
7148
8413
  const computedAnchor = firstAnchor(tree, COMPUTED_CHALLENGE_RE);
7149
8414
  if (computedAnchor) {
@@ -7160,13 +8425,13 @@ var dServer14 = {
7160
8425
  const elements = [
7161
8426
  {
7162
8427
  name: "Element 1 CHALLENGE (401 + WWW-Authenticate: Bearer \u2026 resource_metadata=\u2026)",
7163
- present: WWW_AUTH_RE.test(blob) && CHALLENGE_PARAM_RE.test(blob) && STATUS_401_RE.test(blob) && BEARER_RE.test(blob),
8428
+ present: Boolean(delegation?.forwarded) || WWW_AUTH_RE.test(blob) && CHALLENGE_PARAM_RE.test(blob) && STATUS_401_RE.test(blob) && BEARER_RE.test(blob),
7164
8429
  probe: WWW_AUTH_RE,
7165
8430
  fix: "answer 401 with WWW-Authenticate: Bearer \u2026 resource_metadata=\u2026 (RFC 9728)"
7166
8431
  },
7167
8432
  {
7168
8433
  name: "Element 2 PRM ROUTE (/.well-known/oauth-protected-resource)",
7169
- present: PRM_ROUTE_RE.test(blob),
8434
+ present: PRM_ROUTE_RE.test(blob) || prmRouteViaConstant(tree),
7170
8435
  probe: PRM_ROUTE_RE,
7171
8436
  fix: "serve /.well-known/oauth-protected-resource (Protected Resource Metadata, RFC 9728)"
7172
8437
  },
@@ -7218,7 +8483,7 @@ var dServer14 = {
7218
8483
 
7219
8484
  // src/detectors/s28-claim-forwarded.ts
7220
8485
  import { existsSync as existsSync15, readFileSync as readFileSync11, readdirSync as readdirSync7, statSync as statSync8 } from "fs";
7221
- import { dirname as dirname10, join as join18, relative as relative4 } from "path";
8486
+ import { dirname as dirname11, join as join18, relative as relative5 } from "path";
7222
8487
  var PIN11 = "bacc54a53d5f6f9f20e5254908e2b46807902faa";
7223
8488
  var CLAIM_TOKEN = /\bverified(?:Org|Person)\b/;
7224
8489
  var ANY_CLAIM = /\bverified(?:Actor|Org|Person)\b/g;
@@ -7300,14 +8565,14 @@ function listTs(dir, out = []) {
7300
8565
  function discoverBackend(root) {
7301
8566
  let dir = root;
7302
8567
  try {
7303
- if (!statSync8(root).isDirectory()) dir = dirname10(root);
8568
+ if (!statSync8(root).isDirectory()) dir = dirname11(root);
7304
8569
  } catch {
7305
8570
  return void 0;
7306
8571
  }
7307
8572
  for (let up = 0; up < 5; up++) {
7308
8573
  const cand = join18(dir, "convex");
7309
8574
  if (existsSync15(join18(cand, "schema.ts"))) return cand;
7310
- const parent = dirname10(dir);
8575
+ const parent = dirname11(dir);
7311
8576
  if (parent === dir) break;
7312
8577
  dir = parent;
7313
8578
  }
@@ -7324,11 +8589,11 @@ function readDoors(backendRoot) {
7324
8589
  }
7325
8590
  const file = {
7326
8591
  path,
7327
- rel: relative4(backendRoot, path),
8592
+ rel: relative5(backendRoot, path),
7328
8593
  lines: text.split("\n").map((raw, i) => ({ n: i + 1, raw, comment: false }))
7329
8594
  };
7330
8595
  const m = masked(file);
7331
- const module = relative4(backendRoot, path).replace(/\.ts$/, "").split("\\").join("/");
8596
+ const module = relative5(backendRoot, path).replace(/\.ts$/, "").split("\\").join("/");
7332
8597
  const re = /\bexport\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*(?:query|mutation|action)\s*\(/g;
7333
8598
  for (let hit = re.exec(m); hit; hit = re.exec(m)) {
7334
8599
  const open = hit.index + hit[0].length - 1;
@@ -7382,8 +8647,8 @@ function readDoors(backendRoot) {
7382
8647
  return doors;
7383
8648
  }
7384
8649
  function definitionOf(name, files) {
7385
- const esc = name.replace(/\$/g, "\\$");
7386
- const re = new RegExp(`(?:\\b(?:const|let|var)\\s+${esc}\\b|\\bfunction\\s+${esc}\\b)`);
8650
+ const esc2 = name.replace(/\$/g, "\\$");
8651
+ const re = new RegExp(`(?:\\b(?:const|let|var)\\s+${esc2}\\b|\\bfunction\\s+${esc2}\\b)`);
7387
8652
  for (const f of files) {
7388
8653
  const hit = re.exec(f.m);
7389
8654
  if (!hit) continue;
@@ -7700,17 +8965,17 @@ var detectors = [
7700
8965
  // src/runtime/launch.ts
7701
8966
  import { spawnSync as spawnSync2 } from "child_process";
7702
8967
  import { existsSync as existsSync16, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
7703
- import { dirname as dirname11, isAbsolute, join as join19, resolve } from "path";
8968
+ import { dirname as dirname12, isAbsolute, join as join19, resolve as resolve2 } from "path";
7704
8969
  function findPackageDir(target) {
7705
8970
  let dir;
7706
8971
  try {
7707
- dir = statSync9(target).isDirectory() ? target : dirname11(target);
8972
+ dir = statSync9(target).isDirectory() ? target : dirname12(target);
7708
8973
  } catch {
7709
8974
  return null;
7710
8975
  }
7711
8976
  for (let depth = 0; depth < 8; depth++) {
7712
8977
  if (existsSync16(join19(dir, "package.json"))) return dir;
7713
- const parent = dirname11(dir);
8978
+ const parent = dirname12(dir);
7714
8979
  if (parent === dir) break;
7715
8980
  dir = parent;
7716
8981
  }
@@ -7744,7 +9009,7 @@ function deriveLaunchPlan(target) {
7744
9009
  if (bin) {
7745
9010
  return {
7746
9011
  command: process.execPath,
7747
- args: [isAbsolute(bin) ? bin : resolve(pkgDir, bin)],
9012
+ args: [isAbsolute(bin) ? bin : resolve2(pkgDir, bin)],
7748
9013
  cwd: pkgDir
7749
9014
  };
7750
9015
  }
@@ -7757,7 +9022,7 @@ function deriveLaunchPlan(target) {
7757
9022
  if (main2) {
7758
9023
  return {
7759
9024
  command: process.execPath,
7760
- args: [isAbsolute(main2) ? main2 : resolve(pkgDir, main2)],
9025
+ args: [isAbsolute(main2) ? main2 : resolve2(pkgDir, main2)],
7761
9026
  cwd: pkgDir
7762
9027
  };
7763
9028
  }
@@ -7826,7 +9091,7 @@ function readEvalCounts(target) {
7826
9091
  }
7827
9092
  return counts;
7828
9093
  }
7829
- const parent = dirname11(dir);
9094
+ const parent = dirname12(dir);
7830
9095
  if (parent === dir) break;
7831
9096
  dir = parent;
7832
9097
  }
@@ -8103,7 +9368,7 @@ var USAGE = [
8103
9368
  ].join("\n");
8104
9369
  function readVersion() {
8105
9370
  try {
8106
- const here = dirname12(fileURLToPath(import.meta.url));
9371
+ const here = dirname13(fileURLToPath(import.meta.url));
8107
9372
  const manifest = JSON.parse(readFileSync13(join20(here, "..", "package.json"), "utf8"));
8108
9373
  return manifest.version ?? null;
8109
9374
  } catch {
@@ -8167,7 +9432,7 @@ function isDirectInvocation() {
8167
9432
  return false;
8168
9433
  }
8169
9434
  try {
8170
- return realpathSync(resolve2(entry)) === selfReal;
9435
+ return realpathSync(resolve3(entry)) === selfReal;
8171
9436
  } catch {
8172
9437
  return false;
8173
9438
  }