@astrasyncai/verification-gateway 2.4.12 → 2.4.14

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.
Files changed (91) hide show
  1. package/dist/adapter-interface/interface.d.mts +2 -2
  2. package/dist/adapter-interface/interface.d.ts +2 -2
  3. package/dist/adapters/express.d.mts +2 -2
  4. package/dist/adapters/express.d.ts +2 -2
  5. package/dist/adapters/express.js +125 -35
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +125 -35
  8. package/dist/adapters/express.mjs.map +1 -1
  9. package/dist/adapters/mcp.d.mts +26 -4
  10. package/dist/adapters/mcp.d.ts +26 -4
  11. package/dist/adapters/mcp.js +94 -28
  12. package/dist/adapters/mcp.js.map +1 -1
  13. package/dist/adapters/mcp.mjs +94 -28
  14. package/dist/adapters/mcp.mjs.map +1 -1
  15. package/dist/adapters/nextjs.d.mts +2 -2
  16. package/dist/adapters/nextjs.d.ts +2 -2
  17. package/dist/adapters/nextjs.js +71 -28
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +71 -28
  20. package/dist/adapters/nextjs.mjs.map +1 -1
  21. package/dist/adapters/sdk.d.mts +2 -2
  22. package/dist/adapters/sdk.d.ts +2 -2
  23. package/dist/adapters/sdk.js +45 -22
  24. package/dist/adapters/sdk.js.map +1 -1
  25. package/dist/adapters/sdk.mjs +45 -22
  26. package/dist/adapters/sdk.mjs.map +1 -1
  27. package/dist/agent/index.d.mts +2 -2
  28. package/dist/agent/index.d.ts +2 -2
  29. package/dist/agent/index.js +29 -0
  30. package/dist/agent/index.js.map +1 -1
  31. package/dist/agent/index.mjs +29 -0
  32. package/dist/agent/index.mjs.map +1 -1
  33. package/dist/browser/background.js +86 -24
  34. package/dist/browser/background.js.map +1 -1
  35. package/dist/browser/background.mjs +86 -24
  36. package/dist/browser/background.mjs.map +1 -1
  37. package/dist/browser/browser-adapter.d.mts +2 -2
  38. package/dist/browser/browser-adapter.d.ts +2 -2
  39. package/dist/cli/index.d.mts +2 -2
  40. package/dist/cli/index.d.ts +2 -2
  41. package/dist/cursor/cursor-adapter.d.mts +2 -2
  42. package/dist/cursor/cursor-adapter.d.ts +2 -2
  43. package/dist/cursor/extension.d.mts +2 -2
  44. package/dist/cursor/extension.d.ts +2 -2
  45. package/dist/cursor/extension.js +86 -24
  46. package/dist/cursor/extension.js.map +1 -1
  47. package/dist/cursor/extension.mjs +86 -24
  48. package/dist/cursor/extension.mjs.map +1 -1
  49. package/dist/{express-C1ePFB7n.d.ts → express-CrfwoNAR.d.ts} +1 -1
  50. package/dist/{express-4WStX3PV.d.mts → express-ienhAXps.d.mts} +1 -1
  51. package/dist/gateway/gateway.d.mts +2 -2
  52. package/dist/gateway/gateway.d.ts +2 -2
  53. package/dist/gateway/gateway.js +86 -24
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +86 -24
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/git-trigger/git-hooks.d.mts +2 -2
  58. package/dist/git-trigger/git-hooks.d.ts +2 -2
  59. package/dist/{index-ChPX4WHl.d.mts → index-B5e2IDWU.d.mts} +1 -1
  60. package/dist/{index-CzJMCgEy.d.ts → index-CCdZxvAr.d.ts} +71 -6
  61. package/dist/{index-D8IEntil.d.mts → index-CEg_WG6y.d.mts} +71 -6
  62. package/dist/{index-Cjm-zBeZ.d.ts → index-DC5f8eoQ.d.ts} +1 -1
  63. package/dist/index.d.mts +7 -7
  64. package/dist/index.d.ts +7 -7
  65. package/dist/index.js +336 -71
  66. package/dist/index.js.map +1 -1
  67. package/dist/index.mjs +336 -71
  68. package/dist/index.mjs.map +1 -1
  69. package/dist/local-evaluator/evaluator.d.mts +2 -2
  70. package/dist/local-evaluator/evaluator.d.ts +2 -2
  71. package/dist/local-evaluator/evaluator.js +12 -2
  72. package/dist/local-evaluator/evaluator.js.map +1 -1
  73. package/dist/local-evaluator/evaluator.mjs +12 -2
  74. package/dist/local-evaluator/evaluator.mjs.map +1 -1
  75. package/dist/{nextjs-BIORS__0.d.ts → nextjs-66R1KW8e.d.ts} +1 -1
  76. package/dist/{nextjs-CjzHdaXA.d.mts → nextjs-DSpisQst.d.mts} +1 -1
  77. package/dist/{sdk-Chhz-FcT.d.mts → sdk-5U_CBRpr.d.mts} +1 -1
  78. package/dist/{sdk-CqTEQAc6.d.ts → sdk-Bm8np66n.d.ts} +1 -1
  79. package/dist/transport/index.d.mts +2 -2
  80. package/dist/transport/index.d.ts +2 -2
  81. package/dist/transport/index.js +146 -28
  82. package/dist/transport/index.js.map +1 -1
  83. package/dist/transport/index.mjs +146 -28
  84. package/dist/transport/index.mjs.map +1 -1
  85. package/dist/{types-L15pYd2c.d.mts → types-B3USs-Kx.d.mts} +42 -1
  86. package/dist/{types-L15pYd2c.d.ts → types-B3USs-Kx.d.ts} +42 -1
  87. package/dist/{types-DNK2BgIf.d.mts → types-CgDCUfo8.d.mts} +1 -1
  88. package/dist/{types-DoWIuzfj.d.ts → types-R5N4ET6x.d.ts} +1 -1
  89. package/dist/ui/index.d.mts +1 -1
  90. package/dist/ui/index.d.ts +1 -1
  91. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -189,7 +189,7 @@ function getCapabilities(accessLevel) {
189
189
  }
190
190
 
191
191
  // src/version.ts
192
- var SDK_VERSION = "2.4.12";
192
+ var SDK_VERSION = "2.4.13";
193
193
 
194
194
  // src/verify.ts
195
195
  var DEFAULT_CONFIG = {
@@ -208,22 +208,27 @@ var DEFAULT_CONFIG = {
208
208
  };
209
209
  var initCheckPerformed = false;
210
210
  var deprecationWarningShown = false;
211
- async function performInitCheck(apiBaseUrl, debug) {
211
+ async function performInitCheck(apiBaseUrl, debug, strictInit) {
212
212
  initCheckPerformed = true;
213
213
  try {
214
214
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
215
215
  const response = await fetch(probeUrl, { method: "HEAD" });
216
216
  const contentType = response.headers.get("content-type") ?? "";
217
217
  if (contentType.startsWith("text/html")) {
218
- console.warn(
219
- `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging). Set disableInitChecks: true on GatewayConfig to silence this warning.`
220
- );
218
+ const message = `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;
219
+ if (strictInit) {
220
+ throw new Error(`${message} (strictInit=true)`);
221
+ }
222
+ console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
221
223
  } else if (debug) {
222
224
  console.log(
223
225
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
224
226
  );
225
227
  }
226
228
  } catch (err) {
229
+ if (strictInit) {
230
+ throw err;
231
+ }
227
232
  if (debug) {
228
233
  console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
229
234
  }
@@ -247,7 +252,23 @@ function getCacheKey(request) {
247
252
  request.counterpartyType || "",
248
253
  request.isSubAgentRequest ? "1" : "0",
249
254
  request.parentAgentId || "",
250
- request.subAgentDepth ?? ""
255
+ request.subAgentDepth ?? "",
256
+ // Audit F-A1-07: previously-missing dimensions that DO affect the
257
+ // backend verdict. Without these, two requests with different
258
+ // durations (e.g. 60s vs 86400s) collided on the same cache key and
259
+ // the shorter-duration allow served the longer-duration request.
260
+ request.durationRequired ?? "",
261
+ request.invocationProtocol || "",
262
+ request.enableRuntimeChallenge ? "1" : "0",
263
+ // callerMetadata fields contribute to risk model; include the ones
264
+ // backend reads. sourceIp/userAgent/forwardedFor change per-request
265
+ // so their inclusion effectively forces a re-check for any varying
266
+ // client (the right behavior — IP-driven anomaly scoring shouldn't
267
+ // be cached across IPs).
268
+ request.callerMetadata?.sourceIp || "",
269
+ request.callerMetadata?.userAgent || "",
270
+ request.callerMetadata?.forwardedFor || "",
271
+ request.callerMetadata?.agentCardUrl || ""
251
272
  ].join("|");
252
273
  }
253
274
  function getCachedResult(request) {
@@ -276,9 +297,13 @@ function clearCache() {
276
297
  }
277
298
  function extractCredentials(headers, query) {
278
299
  const credentials = {};
300
+ const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
279
301
  const astraIdHeader = headers["x-astra-id"] || headers["X-Astra-Id"] || headers["X-ASTRA-ID"] || headers["x-astra-agentid"] || headers["X-Astra-AgentId"] || headers["x-astra-agent-id"] || headers["X-Astra-Agent-Id"] || headers["X-ASTRA-AGENT-ID"];
280
302
  if (astraIdHeader) {
281
- credentials.astraId = Array.isArray(astraIdHeader) ? astraIdHeader[0] : astraIdHeader;
303
+ const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
304
+ if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
305
+ credentials.astraId = raw;
306
+ }
282
307
  }
283
308
  const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
284
309
  if (apiKeyHeader) {
@@ -287,9 +312,11 @@ function extractCredentials(headers, query) {
287
312
  const authHeader = headers["authorization"] || headers["Authorization"];
288
313
  if (authHeader) {
289
314
  const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
290
- credentials.authorizationHeader = authValue;
291
- if (authValue.startsWith("Bearer ")) {
292
- credentials.jwt = authValue.slice(7);
315
+ if (typeof authValue === "string") {
316
+ credentials.authorizationHeader = authValue;
317
+ if (authValue.startsWith("Bearer ")) {
318
+ credentials.jwt = authValue.slice(7);
319
+ }
293
320
  }
294
321
  }
295
322
  if (query) {
@@ -310,7 +337,7 @@ function createGuidanceResponse(config, reason, options = {}) {
310
337
  const isApiError = source === "api_error";
311
338
  const guidance = isApiError ? {
312
339
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
313
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
340
+ registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
314
341
  documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
315
342
  steps: [
316
343
  "Retry the request with exponential backoff",
@@ -318,7 +345,7 @@ function createGuidanceResponse(config, reason, options = {}) {
318
345
  ]
319
346
  } : {
320
347
  message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
321
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
348
+ registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
322
349
  documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
323
350
  steps: [
324
351
  "Register for an AstraSync account",
@@ -395,12 +422,8 @@ async function callVerifyAccessAPI(config, request) {
395
422
  "Content-Type": "application/json",
396
423
  ...config.customHeaders
397
424
  };
398
- if (credentials.authorizationHeader) {
399
- headers["Authorization"] = credentials.authorizationHeader;
400
- } else if (config.apiKey) {
401
- headers["Authorization"] = `Bearer ${config.apiKey}`;
402
- }
403
425
  if (config.apiKey) {
426
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
404
427
  headers["X-API-Key"] = config.apiKey;
405
428
  }
406
429
  try {
@@ -446,7 +469,11 @@ async function callVerifyAccessAPI(config, request) {
446
469
  async function verify(config, request) {
447
470
  const mergedConfig = { ...DEFAULT_CONFIG, ...config };
448
471
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
449
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug);
472
+ if (mergedConfig.strictInit) {
473
+ await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
474
+ } else {
475
+ void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
476
+ }
450
477
  }
451
478
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
452
479
  deprecationWarningShown = true;
@@ -500,7 +527,7 @@ async function verify(config, request) {
500
527
  requiresApproval: apiResponse.access?.requiresApproval,
501
528
  guidance: {
502
529
  message: apiResponse.access?.reason || "Access denied by PDLSS policy",
503
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/register`,
530
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
504
531
  documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
505
532
  },
506
533
  verifiedAt: /* @__PURE__ */ new Date(),
@@ -570,13 +597,15 @@ async function verify(config, request) {
570
597
  result.denialReasons = result.recommendationReasons || [
571
598
  "Access denied by AstraSync recommendation"
572
599
  ];
573
- if (result.runtimeChallenge) {
574
- result.guidance = {
575
- message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
576
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/register`,
577
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
578
- };
579
- }
600
+ result.guidance = result.runtimeChallenge ? {
601
+ message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
602
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
603
+ documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
604
+ } : {
605
+ message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
606
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
607
+ documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
608
+ };
580
609
  } else if (result.recommendation === "step_up_required") {
581
610
  result.requiresStepUp = true;
582
611
  if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
@@ -825,18 +854,40 @@ function defaultExtractPurpose(req) {
825
854
  return "general";
826
855
  }
827
856
  }
828
- function matchRoute(pattern, path) {
857
+ function matchRoute(pattern, path, opts) {
829
858
  const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
830
- const regex = new RegExp(`^${regexPattern}$`);
831
- return regex.test(path);
859
+ const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
860
+ const caseSensitiveResult = caseSensitiveRegex.test(path);
861
+ if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
862
+ return caseSensitiveResult;
863
+ }
864
+ const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
865
+ const caseInsensitiveResult = caseInsensitiveRegex.test(path);
866
+ if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
867
+ console.warn(
868
+ `[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
869
+ );
870
+ }
871
+ return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
832
872
  }
833
- function findRouteConfig(routes, path, method) {
873
+ function findRouteConfig(routes, path, method, opts) {
834
874
  return routes.find((route) => {
835
875
  const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
836
- const pathMatches = matchRoute(route.pattern, path);
876
+ const pathMatches = matchRoute(route.pattern, path, opts);
837
877
  return methodMatches && pathMatches;
838
878
  });
839
879
  }
880
+ function dedupeFailures(result) {
881
+ if (result.failures && result.failures.length > 1) {
882
+ const seen = /* @__PURE__ */ new Set();
883
+ result.failures = result.failures.filter((f) => {
884
+ const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
885
+ if (seen.has(key)) return false;
886
+ seen.add(key);
887
+ return true;
888
+ });
889
+ }
890
+ }
840
891
  function defaultOnDenied(result, _req, res) {
841
892
  const statusCode = !result.identityVerified ? 401 : 403;
842
893
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
@@ -863,6 +914,8 @@ function createMiddleware(options) {
863
914
  recordDecisions,
864
915
  enableRuntimeChallenge = true,
865
916
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
917
+ failOnError = "open",
918
+ caseInsensitiveRouteMatch = false,
866
919
  ...config
867
920
  } = options;
868
921
  let cachedRoutes = [];
@@ -885,7 +938,7 @@ function createMiddleware(options) {
885
938
  cachedRoutes = fetched;
886
939
  lastFetchAt = Date.now();
887
940
  if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
888
- const dashboard = config.dashboardUrl ?? "https://app.astrasync.ai";
941
+ const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
889
942
  console.warn(
890
943
  `[VerificationGateway] No route policy configured for ${config.counterpartyId}. Gateway is in pass-through mode for ALL traffic until you add at least one route. Configure at ${dashboard}/dashboard/endpoints/${config.counterpartyId}/routes`
891
944
  );
@@ -911,7 +964,12 @@ function createMiddleware(options) {
911
964
  refreshing = null;
912
965
  });
913
966
  }
914
- const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method);
967
+ const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
968
+ const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
969
+ caseInsensitive: caseInsensitiveRouteMatch,
970
+ logShadowDivergence: true,
971
+ correlationId
972
+ });
915
973
  if (!routeConfig) {
916
974
  if (config.setPassThroughHeader) {
917
975
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
@@ -943,7 +1001,7 @@ function createMiddleware(options) {
943
1001
  denialReasons: preCheckFailures.map((f) => f.message),
944
1002
  guidance: {
945
1003
  message: "Request exceeds counterparty-defined PDLSS limits.",
946
- registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
1004
+ registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
947
1005
  documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
948
1006
  },
949
1007
  verifiedAt: /* @__PURE__ */ new Date()
@@ -958,13 +1016,19 @@ function createMiddleware(options) {
958
1016
  requestMethod: req.method
959
1017
  }).catch(() => {
960
1018
  });
1019
+ dedupeFailures(result2);
961
1020
  onDenied(result2, req, res);
962
1021
  return;
963
1022
  }
964
1023
  const shouldRecordDecisions = recordDecisions !== false;
965
1024
  const forwardedFor = req.headers["x-forwarded-for"];
966
1025
  const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
967
- const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() : req.ip;
1026
+ const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
1027
+ if (!req.ip && forwardedForStr) {
1028
+ console.warn(
1029
+ "[VerificationGateway] req.ip unset \u2014 falling back to leftmost X-Forwarded-For. Configure Express trust proxy correctly to avoid spoofable client IPs in audit logs."
1030
+ );
1031
+ }
968
1032
  const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
969
1033
  const result = await verify(config, {
970
1034
  credentials,
@@ -995,6 +1059,7 @@ function createMiddleware(options) {
995
1059
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
996
1060
  });
997
1061
  }
1062
+ dedupeFailures(result);
998
1063
  onDenied(result, req, res);
999
1064
  return;
1000
1065
  }
@@ -1021,6 +1086,7 @@ function createMiddleware(options) {
1021
1086
  recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
1022
1087
  });
1023
1088
  }
1089
+ dedupeFailures(result);
1024
1090
  onDenied(result, req, res);
1025
1091
  return;
1026
1092
  }
@@ -1037,6 +1103,7 @@ function createMiddleware(options) {
1037
1103
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
1038
1104
  });
1039
1105
  }
1106
+ dedupeFailures(result);
1040
1107
  onDenied(result, req, res);
1041
1108
  return;
1042
1109
  }
@@ -1051,7 +1118,30 @@ function createMiddleware(options) {
1051
1118
  }
1052
1119
  next();
1053
1120
  } catch (error) {
1121
+ const errorClass = error instanceof Error ? error.constructor.name : typeof error;
1122
+ const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1054
1123
  console.error("[VerificationGateway] Middleware error:", error);
1124
+ console.warn(
1125
+ `[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
1126
+ );
1127
+ if (failOnError === "closed") {
1128
+ const result = {
1129
+ identityVerified: false,
1130
+ policyAllowed: false,
1131
+ accessLevel: "none",
1132
+ denialReasons: [`Verification middleware internal error: ${errorClass}`],
1133
+ failures: [
1134
+ {
1135
+ dimension: "middleware.internal_error",
1136
+ message: `Middleware threw ${errorClass} \u2014 failing closed`
1137
+ }
1138
+ ],
1139
+ verifiedAt: /* @__PURE__ */ new Date(),
1140
+ correlationId
1141
+ };
1142
+ dedupeFailures(result);
1143
+ return onDenied(result, req, res);
1144
+ }
1055
1145
  next();
1056
1146
  }
1057
1147
  };
@@ -1063,6 +1153,18 @@ __export(nextjs_exports, {
1063
1153
  createMatcherConfig: () => createMatcherConfig,
1064
1154
  createMiddleware: () => createMiddleware2
1065
1155
  });
1156
+ function escapeHtml(value) {
1157
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1158
+ }
1159
+ function sanitizeUrl(value, fallback) {
1160
+ if (typeof value !== "string" || value.length === 0) return escapeHtml(fallback);
1161
+ const trimmed = value.trim();
1162
+ if (/^javascript:|^data:|^vbscript:/i.test(trimmed)) return escapeHtml(fallback);
1163
+ if (/^https?:\/\//i.test(trimmed) || trimmed.startsWith("/")) {
1164
+ return escapeHtml(trimmed);
1165
+ }
1166
+ return escapeHtml(fallback);
1167
+ }
1066
1168
  function extractCredentialsFromNextRequest(request) {
1067
1169
  const credentials = {};
1068
1170
  const astraId = request.headers.get("x-astra-id") || request.headers.get("X-Astra-Id");
@@ -1134,10 +1236,18 @@ function extractPurpose(request) {
1134
1236
  }
1135
1237
  }
1136
1238
  function generateCommerceShieldHtml(result, options) {
1137
- const title = options.commerceShield?.title || "AstraSync Agent Verification";
1138
- const message = options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials.";
1139
- const registrationUrl = result.guidance?.registrationUrl || "https://astrasync.ai/register";
1140
- const docsUrl = result.guidance?.documentationUrl || "https://astrasync.ai/docs/agent-access";
1239
+ const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
1240
+ const message = escapeHtml(
1241
+ options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
1242
+ );
1243
+ const registrationUrl = sanitizeUrl(
1244
+ result.guidance?.registrationUrl,
1245
+ "https://astrasync.ai/register"
1246
+ );
1247
+ const docsUrl = sanitizeUrl(
1248
+ result.guidance?.documentationUrl,
1249
+ "https://astrasync.ai/docs/agent-access"
1250
+ );
1141
1251
  const allowGuest = options.commerceShield?.allowGuestAccess ?? true;
1142
1252
  return `
1143
1253
  <!DOCTYPE html>
@@ -1259,7 +1369,7 @@ function generateCommerceShieldHtml(result, options) {
1259
1369
  <div class="shield-steps">
1260
1370
  <h3>To get verified access:</h3>
1261
1371
  <ol>
1262
- <li>Register at <a href="${registrationUrl}">astrasync.ai/register</a></li>
1372
+ <li>Register at <a href="${registrationUrl}">astrasync.ai/agents/register</a></li>
1263
1373
  <li>Create and register your agent</li>
1264
1374
  <li>Add your ASTRA-ID to request headers</li>
1265
1375
  <li>Refresh this page</li>
@@ -1347,7 +1457,7 @@ function createMiddleware2(options) {
1347
1457
  denialReasons: preCheckFailures.map((f) => f.message),
1348
1458
  guidance: {
1349
1459
  message: "Request exceeds counterparty-defined PDLSS limits.",
1350
- registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
1460
+ registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
1351
1461
  documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
1352
1462
  },
1353
1463
  verifiedAt: /* @__PURE__ */ new Date()
@@ -2008,12 +2118,45 @@ function bufferToBase64(bytes) {
2008
2118
 
2009
2119
  // src/transport/rfc9421-verify.ts
2010
2120
  var import_http_message_signatures = require("http-message-signatures");
2121
+
2122
+ // src/transport/nonce-store.ts
2123
+ var InMemoryNonceStore = class {
2124
+ constructor(capacity = 1e4) {
2125
+ this.entries = /* @__PURE__ */ new Map();
2126
+ this.lastSweepMs = 0;
2127
+ this.capacity = capacity;
2128
+ }
2129
+ seen(key, expiresAtMs) {
2130
+ const nowMs = Date.now();
2131
+ if (nowMs - this.lastSweepMs > 1e3) {
2132
+ for (const [k, exp] of this.entries) {
2133
+ if (exp <= nowMs) this.entries.delete(k);
2134
+ }
2135
+ this.lastSweepMs = nowMs;
2136
+ }
2137
+ const existing = this.entries.get(key);
2138
+ if (existing !== void 0 && existing > nowMs) {
2139
+ return true;
2140
+ }
2141
+ if (this.entries.size >= this.capacity) {
2142
+ const oldest = this.entries.keys().next().value;
2143
+ if (oldest !== void 0) this.entries.delete(oldest);
2144
+ }
2145
+ this.entries.set(key, expiresAtMs);
2146
+ return false;
2147
+ }
2148
+ };
2149
+ var defaultNonceStore = new InMemoryNonceStore();
2150
+
2151
+ // src/transport/rfc9421-verify.ts
2011
2152
  async function verifyRFC9421(request, options) {
2012
2153
  const { resolver } = options;
2013
- const tolerance = options.clockSkewSec ?? 300;
2154
+ const tolerance = options.clockSkewSec ?? 60;
2014
2155
  const nowSec = options.now ? options.now() : Math.floor(Date.now() / 1e3);
2156
+ const nonceStore = options.nonceStore ?? defaultNonceStore;
2015
2157
  let resolvedKid;
2016
2158
  let resolvedAlg;
2159
+ let replayDetected = false;
2017
2160
  const keyLookup = async (parameters) => {
2018
2161
  const kid = typeof parameters.keyid === "string" ? parameters.keyid : void 0;
2019
2162
  if (!kid) return null;
@@ -2027,6 +2170,14 @@ async function verifyRFC9421(request, options) {
2027
2170
  const expires = toUnixSeconds(parameters.expires);
2028
2171
  if (created !== void 0 && Math.abs(nowSec - created) > tolerance) return null;
2029
2172
  if (expires !== void 0 && nowSec > expires + tolerance) return null;
2173
+ const nonce = typeof parameters.nonce === "string" ? parameters.nonce : void 0;
2174
+ if (nonce) {
2175
+ const expiresAtMs = (expires !== void 0 ? expires + tolerance : nowSec + tolerance) * 1e3;
2176
+ if (nonceStore.seen(`rfc9421:${kid}:${nonce}`, expiresAtMs)) {
2177
+ replayDetected = true;
2178
+ return null;
2179
+ }
2180
+ }
2030
2181
  return jwkToVerifyingKey(kid, jwk, alg);
2031
2182
  };
2032
2183
  try {
@@ -2049,7 +2200,7 @@ async function verifyRFC9421(request, options) {
2049
2200
  kid: resolvedKid,
2050
2201
  registry: resolver.name,
2051
2202
  algorithm: resolvedAlg,
2052
- error: result === false ? "signature invalid" : "no signature found"
2203
+ error: replayDetected ? "RFC9421 signature replay \u2014 already seen within tolerance window" : result === false ? "signature invalid" : "no signature found"
2053
2204
  };
2054
2205
  } catch (err) {
2055
2206
  return {
@@ -2874,14 +3025,26 @@ function sha256Sync2(data) {
2874
3025
  function verifyAP2Chain(input) {
2875
3026
  const { triple } = input;
2876
3027
  const errors = [];
3028
+ const toleranceSec = input.clockSkewSec ?? 60;
3029
+ const nonceStore = input.nonceStore ?? defaultNonceStore;
2877
3030
  const intentPresent = triple.intent !== void 0;
2878
3031
  const cartRefOk = checkCartRef(triple, errors);
2879
3032
  const paymentRefOk = checkPaymentRef(triple, errors);
2880
3033
  const { ok: agentIdContinuity, agentId } = checkAgentContinuity(triple, errors);
2881
3034
  const paymentMethodAllowed = checkPaymentMethod(triple, errors);
2882
3035
  const totalsConsistent = checkTotals(triple, errors);
2883
- const expiryOk = checkExpiries(triple, input.clockSkewSec ?? 300, input.now, errors);
2884
- const ok = cartRefOk && paymentRefOk && agentIdContinuity && paymentMethodAllowed && totalsConsistent && expiryOk;
3036
+ const expiryOk = checkExpiries(triple, toleranceSec, input.now, errors);
3037
+ let replayOk = true;
3038
+ const replayId = triple.payment?.raw?.id ?? triple.cart?.raw?.id;
3039
+ if (typeof replayId === "string" && replayId.length > 0) {
3040
+ const now = input.now ? input.now() : Math.floor(Date.now() / 1e3);
3041
+ const expiresAt = (now + toleranceSec) * 1e3;
3042
+ if (nonceStore.seen(`ap2:${replayId}`, expiresAt)) {
3043
+ errors.push(`AP2 chain replay \u2014 mandate ${replayId} already seen within tolerance window`);
3044
+ replayOk = false;
3045
+ }
3046
+ }
3047
+ const ok = cartRefOk && paymentRefOk && agentIdContinuity && paymentMethodAllowed && totalsConsistent && expiryOk && replayOk;
2885
3048
  return {
2886
3049
  ok,
2887
3050
  checks: {
@@ -2927,7 +3090,10 @@ function checkAgentContinuity(triple, errors) {
2927
3090
  const ids = [triple.intent?.agent_id, triple.cart?.agent_id, triple.payment?.agent_id].filter(
2928
3091
  (id) => typeof id === "string" && id.length > 0
2929
3092
  );
2930
- if (ids.length === 0) return { ok: true };
3093
+ if (ids.length === 0) {
3094
+ errors.push("agent_id missing across all three mandates (intent/cart/payment)");
3095
+ return { ok: false };
3096
+ }
2931
3097
  const unique = new Set(ids);
2932
3098
  if (unique.size > 1) {
2933
3099
  errors.push(`agent_id mismatch across mandates: ${Array.from(unique).join(", ")}`);
@@ -2936,9 +3102,16 @@ function checkAgentContinuity(triple, errors) {
2936
3102
  return { ok: true, agentId: ids[0] };
2937
3103
  }
2938
3104
  function checkPaymentMethod(triple, errors) {
2939
- const paymentMethod = triple.payment?.payment_method;
2940
3105
  const allowed = triple.intent?.paymentMethods;
2941
- if (!paymentMethod || !allowed || allowed.length === 0) return true;
3106
+ if (!allowed || allowed.length === 0) return true;
3107
+ if (!triple.payment) return true;
3108
+ const paymentMethod = triple.payment.payment_method;
3109
+ if (!paymentMethod) {
3110
+ errors.push(
3111
+ `payment.payment_method missing but intent declares allowlist [${allowed.join(", ")}]`
3112
+ );
3113
+ return false;
3114
+ }
2942
3115
  if (!allowed.includes(paymentMethod)) {
2943
3116
  errors.push(
2944
3117
  `payment_method "${paymentMethod}" not in intent.paymentMethods [${allowed.join(", ")}]`
@@ -2972,19 +3145,24 @@ function checkTotals(triple, errors) {
2972
3145
  function checkExpiries(triple, toleranceSec, nowFn, errors) {
2973
3146
  const now = nowFn ? nowFn() : Math.floor(Date.now() / 1e3);
2974
3147
  let ok = true;
2975
- for (const [name, mandate] of [
2976
- ["intent", triple.intent],
2977
- ["cart", triple.cart]
2978
- ]) {
2979
- if (!mandate?.expires) continue;
2980
- const parsed = parseExpiry(mandate.expires);
3148
+ const layers = [
3149
+ ["intent", triple.intent?.expires],
3150
+ ["cart", triple.cart?.expires],
3151
+ [
3152
+ "payment",
3153
+ typeof triple.payment?.raw?.expires === "string" ? triple.payment.raw.expires : typeof triple.payment?.raw?.exp === "string" ? triple.payment.raw.exp : void 0
3154
+ ]
3155
+ ];
3156
+ for (const [name, expires] of layers) {
3157
+ if (!expires) continue;
3158
+ const parsed = parseExpiry(expires);
2981
3159
  if (parsed === null) {
2982
3160
  errors.push(`${name}.expires unparseable`);
2983
3161
  ok = false;
2984
3162
  continue;
2985
3163
  }
2986
3164
  if (now > parsed + toleranceSec) {
2987
- errors.push(`${name} mandate expired at ${mandate.expires}`);
3165
+ errors.push(`${name} mandate expired at ${expires}`);
2988
3166
  ok = false;
2989
3167
  }
2990
3168
  }
@@ -3011,10 +3189,21 @@ async function verifyACPSignature(input) {
3011
3189
  if (!input.signatureHeader) {
3012
3190
  return { ok: false, error: "missing Signature header" };
3013
3191
  }
3014
- const freshness = checkTimestamp(input.timestampHeader, input.clockSkewSec ?? 300, input.now);
3192
+ const tolerance = input.clockSkewSec ?? 60;
3193
+ const nonceStore = input.nonceStore ?? defaultNonceStore;
3194
+ const freshness = checkTimestamp(input.timestampHeader, tolerance, input.now);
3015
3195
  if (!freshness.ok) {
3016
3196
  return { ok: false, error: freshness.error, timestampStale: true };
3017
3197
  }
3198
+ const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1e3);
3199
+ const expiresAtMs = (nowSec + tolerance) * 1e3;
3200
+ const replayKey = `acp:${input.signatureHeader}:${input.timestampHeader ?? ""}`;
3201
+ if (nonceStore.seen(replayKey, expiresAtMs)) {
3202
+ return {
3203
+ ok: false,
3204
+ error: "ACP signature replay \u2014 already seen within tolerance window"
3205
+ };
3206
+ }
3018
3207
  const signatureBytes = decodeBase64(input.signatureHeader);
3019
3208
  if (!signatureBytes) {
3020
3209
  return { ok: false, error: "signature header is not valid base64" };
@@ -3232,8 +3421,9 @@ function coerceString6(v) {
3232
3421
  var import_mppx2 = require("mppx");
3233
3422
  function verifyMPP(input) {
3234
3423
  const { context } = input;
3235
- const tolerance = input.clockSkewSec ?? 300;
3424
+ const tolerance = input.clockSkewSec ?? 60;
3236
3425
  const nowSec = input.now ? input.now() : Math.floor(Date.now() / 1e3);
3426
+ const nonceStore = input.nonceStore ?? defaultNonceStore;
3237
3427
  const challenge = context.credential?.challenge ?? (context.challenges && context.challenges[0]);
3238
3428
  const source = context.credential?.source;
3239
3429
  const method = challenge?.method;
@@ -3256,21 +3446,38 @@ function verifyMPP(input) {
3256
3446
  }
3257
3447
  }
3258
3448
  let bodyDigestOk = null;
3259
- if (challenge?.digest && input.rawBody !== void 0) {
3260
- try {
3261
- if (!/^sha-256=/.test(challenge.digest)) {
3449
+ if (input.rawBody !== void 0) {
3450
+ if (!challenge?.digest) {
3451
+ bodyDigestOk = false;
3452
+ } else {
3453
+ try {
3454
+ if (!/^sha-256=/.test(challenge.digest)) {
3455
+ bodyDigestOk = false;
3456
+ } else {
3457
+ bodyDigestOk = import_mppx2.BodyDigest.verify(challenge.digest, input.rawBody);
3458
+ }
3459
+ } catch {
3262
3460
  bodyDigestOk = false;
3263
- } else {
3264
- bodyDigestOk = import_mppx2.BodyDigest.verify(challenge.digest, input.rawBody);
3265
3461
  }
3266
- } catch {
3267
- bodyDigestOk = false;
3268
3462
  }
3269
3463
  }
3270
- const ok = expiryOk && (bodyDigestOk === null || bodyDigestOk === true);
3464
+ let replayOk = true;
3465
+ if (challenge?.digest && expiryOk) {
3466
+ const replayKey = `mpp:${challenge.digest}:${challenge.nonce ?? ""}`;
3467
+ const expiresAt = (nowSec + tolerance) * 1e3;
3468
+ if (nonceStore.seen(replayKey, expiresAt)) {
3469
+ replayOk = false;
3470
+ }
3471
+ }
3472
+ const ok = expiryOk && (bodyDigestOk === null || bodyDigestOk === true) && replayOk;
3271
3473
  const errors = [];
3272
3474
  if (!expiryOk) errors.push("challenge expired");
3273
- if (bodyDigestOk === false) errors.push("body digest mismatch");
3475
+ if (bodyDigestOk === false) {
3476
+ errors.push(
3477
+ input.rawBody !== void 0 && !challenge?.digest ? "body digest required when rawBody present" : "body digest mismatch"
3478
+ );
3479
+ }
3480
+ if (!replayOk) errors.push("MPP challenge replay \u2014 already seen within tolerance window");
3274
3481
  return {
3275
3482
  ok,
3276
3483
  expiryOk,
@@ -3431,14 +3638,32 @@ function readHeader4(headers, name) {
3431
3638
  var import_node_crypto4 = require("crypto");
3432
3639
  async function verifyVIChain(input) {
3433
3640
  const errors = [];
3434
- const tolerance = input.clockSkewSec ?? 300;
3641
+ const tolerance = input.clockSkewSec ?? 60;
3435
3642
  const now = input.now ? input.now() : Math.floor(Date.now() / 1e3);
3436
3643
  const { l1, l2, l3a, l3b } = input.layers;
3644
+ const nonceStore = input.nonceStore ?? defaultNonceStore;
3645
+ if (!l1) {
3646
+ if (!input.allowUnboundChain) {
3647
+ errors.push(
3648
+ "L1 missing \u2014 chain root unbound (set allowUnboundChain + expectedL2Key to override)"
3649
+ );
3650
+ } else if (!input.expectedL2Key) {
3651
+ errors.push("allowUnboundChain set but expectedL2Key missing");
3652
+ }
3653
+ }
3437
3654
  const l1SigOk = l1 ? await input.verifySignature(l1, null) : null;
3438
3655
  if (l1 && !l1SigOk) errors.push("L1 signature invalid");
3439
3656
  const l1Cnf = extractCnfJwk(l1?.payload);
3440
- const l2SigOk = await input.verifySignature(l2, l1Cnf ?? null);
3657
+ const l2ExpectedKey = l1Cnf ?? input.expectedL2Key ?? null;
3658
+ const l2SigOk = await input.verifySignature(l2, l2ExpectedKey);
3441
3659
  if (!l2SigOk) errors.push("L2 signature invalid");
3660
+ if (l2SigOk) {
3661
+ const replayKey = `vi:l2:${l2.compact}`;
3662
+ const expiresAt = now * 1e3 + tolerance * 1e3;
3663
+ if (nonceStore.seen(replayKey, expiresAt)) {
3664
+ errors.push("L2 signature replay \u2014 already seen within tolerance window");
3665
+ }
3666
+ }
3442
3667
  const l2Cnf = extractCnfJwk(l2.payload);
3443
3668
  const l3aSigOk = l3a ? await input.verifySignature(l3a, l2Cnf ?? null) : null;
3444
3669
  if (l3a && !l3aSigOk) errors.push("L3a signature invalid");
@@ -3482,7 +3707,10 @@ async function verifyVIChain(input) {
3482
3707
  }
3483
3708
  }
3484
3709
  const expiryOk = checkExpiryAcross([l1, l2, l3a, l3b], tolerance, now, errors);
3485
- const ok = l1SigOk !== false && l2SigOk && l3aSigOk !== false && l3bSigOk !== false && l1BindsL2 && l2BindsL3 && l3aL3bTxnIdMatch !== false && checkoutHashOk !== false && expiryOk;
3710
+ const noUnboundChainOrReplayErrors = !errors.some(
3711
+ (e) => e.startsWith("L1 missing") || e.startsWith("allowUnboundChain set") || e.startsWith("L2 signature replay")
3712
+ );
3713
+ const ok = l1SigOk !== false && l2SigOk && l3aSigOk !== false && l3bSigOk !== false && l1BindsL2 && l2BindsL3 && l3aL3bTxnIdMatch !== false && checkoutHashOk !== false && expiryOk && noUnboundChainOrReplayErrors;
3486
3714
  return {
3487
3715
  ok,
3488
3716
  checks: {
@@ -4137,7 +4365,7 @@ function mcpToPdlss(parsed, headerPurpose, headerAction) {
4137
4365
  action = parsed.actionFromBody;
4138
4366
  actionSource = parsed.actionSourceFromBody;
4139
4367
  } else {
4140
- action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
4368
+ action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
4141
4369
  actionSource = "transport_layer";
4142
4370
  }
4143
4371
  return { purpose, action, resource, purposeSource, actionSource };
@@ -4156,6 +4384,17 @@ function readSingleHeader(value) {
4156
4384
  if (Array.isArray(value)) return value[0];
4157
4385
  return void 0;
4158
4386
  }
4387
+ function dedupeFailures2(result) {
4388
+ if (result.failures && result.failures.length > 1) {
4389
+ const seen = /* @__PURE__ */ new Set();
4390
+ result.failures = result.failures.filter((f) => {
4391
+ const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
4392
+ if (seen.has(key)) return false;
4393
+ seen.add(key);
4394
+ return true;
4395
+ });
4396
+ }
4397
+ }
4159
4398
  function defaultMcpDenied(result, req, res) {
4160
4399
  const id = req.body?.id ?? null;
4161
4400
  const status = !result.identityVerified ? 401 : 403;
@@ -4195,10 +4434,11 @@ function createMcpMiddleware(options) {
4195
4434
  onAgentIdMismatch = "reject",
4196
4435
  skip = false,
4197
4436
  onDenied = defaultMcpDenied,
4198
- trustVerifiedHop = true,
4437
+ trustVerifiedHop = false,
4199
4438
  verifiedHopMaxAgeMs,
4200
4439
  recordDecisions,
4201
4440
  enableRuntimeChallenge = true,
4441
+ failOnError = "open",
4202
4442
  ...config
4203
4443
  } = options;
4204
4444
  return async (req, res, next) => {
@@ -4306,6 +4546,7 @@ function createMcpMiddleware(options) {
4306
4546
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
4307
4547
  });
4308
4548
  }
4549
+ dedupeFailures2(result);
4309
4550
  onDenied(result, req, res);
4310
4551
  return;
4311
4552
  }
@@ -4351,6 +4592,7 @@ function createMcpMiddleware(options) {
4351
4592
  });
4352
4593
  }
4353
4594
  }
4595
+ dedupeFailures2(result);
4354
4596
  onDenied(result, req, res);
4355
4597
  return;
4356
4598
  }
@@ -4374,7 +4616,30 @@ function createMcpMiddleware(options) {
4374
4616
  }
4375
4617
  next();
4376
4618
  } catch (error) {
4619
+ const errorClass = error instanceof Error ? error.constructor.name : typeof error;
4620
+ const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
4377
4621
  console.error("[VerificationGateway/MCP] Middleware error:", error);
4622
+ console.warn(
4623
+ `[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
4624
+ );
4625
+ if (failOnError === "closed") {
4626
+ const result = {
4627
+ identityVerified: false,
4628
+ policyAllowed: false,
4629
+ accessLevel: "none",
4630
+ denialReasons: [`MCP middleware internal error: ${errorClass}`],
4631
+ failures: [
4632
+ {
4633
+ dimension: "middleware.internal_error",
4634
+ message: `Middleware threw ${errorClass} \u2014 failing closed`
4635
+ }
4636
+ ],
4637
+ verifiedAt: /* @__PURE__ */ new Date(),
4638
+ correlationId
4639
+ };
4640
+ dedupeFailures2(result);
4641
+ return onDenied(result, req, res);
4642
+ }
4378
4643
  next();
4379
4644
  }
4380
4645
  };