@astrasyncai/verification-gateway 2.4.11 → 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 +129 -36
  6. package/dist/adapters/express.js.map +1 -1
  7. package/dist/adapters/express.mjs +129 -36
  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 +75 -29
  18. package/dist/adapters/nextjs.js.map +1 -1
  19. package/dist/adapters/nextjs.mjs +75 -29
  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 +344 -73
  66. package/dist/index.js.map +1 -1
  67. package/dist/index.mjs +344 -73
  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
@@ -18,7 +18,7 @@ function hasMinimumAccess(actual, required) {
18
18
  }
19
19
 
20
20
  // src/version.ts
21
- var SDK_VERSION = "2.4.11";
21
+ var SDK_VERSION = "2.4.13";
22
22
 
23
23
  // src/verify.ts
24
24
  var DEFAULT_CONFIG = {
@@ -37,22 +37,27 @@ var DEFAULT_CONFIG = {
37
37
  };
38
38
  var initCheckPerformed = false;
39
39
  var deprecationWarningShown = false;
40
- async function performInitCheck(apiBaseUrl, debug) {
40
+ async function performInitCheck(apiBaseUrl, debug, strictInit) {
41
41
  initCheckPerformed = true;
42
42
  try {
43
43
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
44
44
  const response = await fetch(probeUrl, { method: "HEAD" });
45
45
  const contentType = response.headers.get("content-type") ?? "";
46
46
  if (contentType.startsWith("text/html")) {
47
- console.warn(
48
- `[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.`
49
- );
47
+ 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).`;
48
+ if (strictInit) {
49
+ throw new Error(`${message} (strictInit=true)`);
50
+ }
51
+ console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
50
52
  } else if (debug) {
51
53
  console.log(
52
54
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
53
55
  );
54
56
  }
55
57
  } catch (err) {
58
+ if (strictInit) {
59
+ throw err;
60
+ }
56
61
  if (debug) {
57
62
  console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
58
63
  }
@@ -76,7 +81,23 @@ function getCacheKey(request) {
76
81
  request.counterpartyType || "",
77
82
  request.isSubAgentRequest ? "1" : "0",
78
83
  request.parentAgentId || "",
79
- request.subAgentDepth ?? ""
84
+ request.subAgentDepth ?? "",
85
+ // Audit F-A1-07: previously-missing dimensions that DO affect the
86
+ // backend verdict. Without these, two requests with different
87
+ // durations (e.g. 60s vs 86400s) collided on the same cache key and
88
+ // the shorter-duration allow served the longer-duration request.
89
+ request.durationRequired ?? "",
90
+ request.invocationProtocol || "",
91
+ request.enableRuntimeChallenge ? "1" : "0",
92
+ // callerMetadata fields contribute to risk model; include the ones
93
+ // backend reads. sourceIp/userAgent/forwardedFor change per-request
94
+ // so their inclusion effectively forces a re-check for any varying
95
+ // client (the right behavior — IP-driven anomaly scoring shouldn't
96
+ // be cached across IPs).
97
+ request.callerMetadata?.sourceIp || "",
98
+ request.callerMetadata?.userAgent || "",
99
+ request.callerMetadata?.forwardedFor || "",
100
+ request.callerMetadata?.agentCardUrl || ""
80
101
  ].join("|");
81
102
  }
82
103
  function getCachedResult(request) {
@@ -102,9 +123,13 @@ function cacheResult(request, result, configuredTtl) {
102
123
  }
103
124
  function extractCredentials(headers, query) {
104
125
  const credentials = {};
126
+ const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
105
127
  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"];
106
128
  if (astraIdHeader) {
107
- credentials.astraId = Array.isArray(astraIdHeader) ? astraIdHeader[0] : astraIdHeader;
129
+ const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
130
+ if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
131
+ credentials.astraId = raw;
132
+ }
108
133
  }
109
134
  const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
110
135
  if (apiKeyHeader) {
@@ -113,9 +138,11 @@ function extractCredentials(headers, query) {
113
138
  const authHeader = headers["authorization"] || headers["Authorization"];
114
139
  if (authHeader) {
115
140
  const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
116
- credentials.authorizationHeader = authValue;
117
- if (authValue.startsWith("Bearer ")) {
118
- credentials.jwt = authValue.slice(7);
141
+ if (typeof authValue === "string") {
142
+ credentials.authorizationHeader = authValue;
143
+ if (authValue.startsWith("Bearer ")) {
144
+ credentials.jwt = authValue.slice(7);
145
+ }
119
146
  }
120
147
  }
121
148
  if (query) {
@@ -133,7 +160,7 @@ function createGuidanceResponse(config, reason, options = {}) {
133
160
  const isApiError = source === "api_error";
134
161
  const guidance = isApiError ? {
135
162
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
136
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
163
+ registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
137
164
  documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
138
165
  steps: [
139
166
  "Retry the request with exponential backoff",
@@ -141,7 +168,7 @@ function createGuidanceResponse(config, reason, options = {}) {
141
168
  ]
142
169
  } : {
143
170
  message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
144
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
171
+ registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
145
172
  documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
146
173
  steps: [
147
174
  "Register for an AstraSync account",
@@ -218,12 +245,8 @@ async function callVerifyAccessAPI(config, request) {
218
245
  "Content-Type": "application/json",
219
246
  ...config.customHeaders
220
247
  };
221
- if (credentials.authorizationHeader) {
222
- headers["Authorization"] = credentials.authorizationHeader;
223
- } else if (config.apiKey) {
224
- headers["Authorization"] = `Bearer ${config.apiKey}`;
225
- }
226
248
  if (config.apiKey) {
249
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
227
250
  headers["X-API-Key"] = config.apiKey;
228
251
  }
229
252
  try {
@@ -269,7 +292,11 @@ async function callVerifyAccessAPI(config, request) {
269
292
  async function verify(config, request) {
270
293
  const mergedConfig = { ...DEFAULT_CONFIG, ...config };
271
294
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
272
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug);
295
+ if (mergedConfig.strictInit) {
296
+ await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
297
+ } else {
298
+ void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
299
+ }
273
300
  }
274
301
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
275
302
  deprecationWarningShown = true;
@@ -323,7 +350,7 @@ async function verify(config, request) {
323
350
  requiresApproval: apiResponse.access?.requiresApproval,
324
351
  guidance: {
325
352
  message: apiResponse.access?.reason || "Access denied by PDLSS policy",
326
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/register`,
353
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
327
354
  documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
328
355
  },
329
356
  verifiedAt: /* @__PURE__ */ new Date(),
@@ -393,13 +420,15 @@ async function verify(config, request) {
393
420
  result.denialReasons = result.recommendationReasons || [
394
421
  "Access denied by AstraSync recommendation"
395
422
  ];
396
- if (result.runtimeChallenge) {
397
- result.guidance = {
398
- message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
399
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/register`,
400
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
401
- };
402
- }
423
+ result.guidance = result.runtimeChallenge ? {
424
+ message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
425
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
426
+ documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
427
+ } : {
428
+ message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
429
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
430
+ documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
431
+ };
403
432
  } else if (result.recommendation === "step_up_required") {
404
433
  result.requiresStepUp = true;
405
434
  if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
@@ -588,18 +617,40 @@ function defaultExtractPurpose(req) {
588
617
  return "general";
589
618
  }
590
619
  }
591
- function matchRoute(pattern, path) {
620
+ function matchRoute(pattern, path, opts) {
592
621
  const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
593
- const regex = new RegExp(`^${regexPattern}$`);
594
- return regex.test(path);
622
+ const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
623
+ const caseSensitiveResult = caseSensitiveRegex.test(path);
624
+ if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
625
+ return caseSensitiveResult;
626
+ }
627
+ const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
628
+ const caseInsensitiveResult = caseInsensitiveRegex.test(path);
629
+ if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
630
+ console.warn(
631
+ `[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
632
+ );
633
+ }
634
+ return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
595
635
  }
596
- function findRouteConfig(routes, path, method) {
636
+ function findRouteConfig(routes, path, method, opts) {
597
637
  return routes.find((route) => {
598
638
  const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
599
- const pathMatches = matchRoute(route.pattern, path);
639
+ const pathMatches = matchRoute(route.pattern, path, opts);
600
640
  return methodMatches && pathMatches;
601
641
  });
602
642
  }
643
+ function dedupeFailures(result) {
644
+ if (result.failures && result.failures.length > 1) {
645
+ const seen = /* @__PURE__ */ new Set();
646
+ result.failures = result.failures.filter((f) => {
647
+ const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
648
+ if (seen.has(key)) return false;
649
+ seen.add(key);
650
+ return true;
651
+ });
652
+ }
653
+ }
603
654
  function defaultOnDenied(result, _req, res) {
604
655
  const statusCode = !result.identityVerified ? 401 : 403;
605
656
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
@@ -626,6 +677,8 @@ function createMiddleware(options) {
626
677
  recordDecisions,
627
678
  enableRuntimeChallenge = true,
628
679
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
680
+ failOnError = "open",
681
+ caseInsensitiveRouteMatch = false,
629
682
  ...config
630
683
  } = options;
631
684
  let cachedRoutes = [];
@@ -648,7 +701,7 @@ function createMiddleware(options) {
648
701
  cachedRoutes = fetched;
649
702
  lastFetchAt = Date.now();
650
703
  if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
651
- const dashboard = config.dashboardUrl ?? "https://app.astrasync.ai";
704
+ const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
652
705
  console.warn(
653
706
  `[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`
654
707
  );
@@ -674,7 +727,12 @@ function createMiddleware(options) {
674
727
  refreshing = null;
675
728
  });
676
729
  }
677
- const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method);
730
+ const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
731
+ const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
732
+ caseInsensitive: caseInsensitiveRouteMatch,
733
+ logShadowDivergence: true,
734
+ correlationId
735
+ });
678
736
  if (!routeConfig) {
679
737
  if (config.setPassThroughHeader) {
680
738
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
@@ -706,7 +764,7 @@ function createMiddleware(options) {
706
764
  denialReasons: preCheckFailures.map((f) => f.message),
707
765
  guidance: {
708
766
  message: "Request exceeds counterparty-defined PDLSS limits.",
709
- registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
767
+ registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
710
768
  documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
711
769
  },
712
770
  verifiedAt: /* @__PURE__ */ new Date()
@@ -721,18 +779,27 @@ function createMiddleware(options) {
721
779
  requestMethod: req.method
722
780
  }).catch(() => {
723
781
  });
782
+ dedupeFailures(result2);
724
783
  onDenied(result2, req, res);
725
784
  return;
726
785
  }
727
786
  const shouldRecordDecisions = recordDecisions !== false;
728
787
  const forwardedFor = req.headers["x-forwarded-for"];
729
788
  const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
730
- const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() : req.ip;
789
+ const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
790
+ if (!req.ip && forwardedForStr) {
791
+ console.warn(
792
+ "[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."
793
+ );
794
+ }
731
795
  const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
732
796
  const result = await verify(config, {
733
797
  credentials,
734
798
  purpose,
735
- action: req.method.toLowerCase(),
799
+ // RFC 7230 § 3.1.1 — HTTP method tokens uppercase by IANA convention.
800
+ // Backend evaluator tolerates either case as defense-in-depth
801
+ // (round-18.6 batch 2); SDK emits canonical form.
802
+ action: req.method.toUpperCase(),
736
803
  resource: req.path,
737
804
  createSession: shouldRecordDecisions,
738
805
  counterpartyUrl,
@@ -755,6 +822,7 @@ function createMiddleware(options) {
755
822
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
756
823
  });
757
824
  }
825
+ dedupeFailures(result);
758
826
  onDenied(result, req, res);
759
827
  return;
760
828
  }
@@ -781,6 +849,7 @@ function createMiddleware(options) {
781
849
  recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
782
850
  });
783
851
  }
852
+ dedupeFailures(result);
784
853
  onDenied(result, req, res);
785
854
  return;
786
855
  }
@@ -797,6 +866,7 @@ function createMiddleware(options) {
797
866
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
798
867
  });
799
868
  }
869
+ dedupeFailures(result);
800
870
  onDenied(result, req, res);
801
871
  return;
802
872
  }
@@ -811,7 +881,30 @@ function createMiddleware(options) {
811
881
  }
812
882
  next();
813
883
  } catch (error) {
884
+ const errorClass = error instanceof Error ? error.constructor.name : typeof error;
885
+ const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
814
886
  console.error("[VerificationGateway] Middleware error:", error);
887
+ console.warn(
888
+ `[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
889
+ );
890
+ if (failOnError === "closed") {
891
+ const result = {
892
+ identityVerified: false,
893
+ policyAllowed: false,
894
+ accessLevel: "none",
895
+ denialReasons: [`Verification middleware internal error: ${errorClass}`],
896
+ failures: [
897
+ {
898
+ dimension: "middleware.internal_error",
899
+ message: `Middleware threw ${errorClass} \u2014 failing closed`
900
+ }
901
+ ],
902
+ verifiedAt: /* @__PURE__ */ new Date(),
903
+ correlationId
904
+ };
905
+ dedupeFailures(result);
906
+ return onDenied(result, req, res);
907
+ }
815
908
  next();
816
909
  }
817
910
  };