@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
@@ -1,6 +1,6 @@
1
1
  import { AstraSyncGateway } from '../gateway/gateway.mjs';
2
- import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-DNK2BgIf.mjs';
3
- import '../types-L15pYd2c.mjs';
2
+ import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-CgDCUfo8.mjs';
3
+ import '../types-B3USs-Kx.mjs';
4
4
 
5
5
  /**
6
6
  * PlatformAdapter Interface
@@ -1,6 +1,6 @@
1
1
  import { AstraSyncGateway } from '../gateway/gateway.js';
2
- import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-DoWIuzfj.js';
3
- import '../types-L15pYd2c.js';
2
+ import { A as AgentAction, I as InterceptResult, P as PDLSSContext, V as VerificationDecision } from '../types-R5N4ET6x.js';
3
+ import '../types-B3USs-Kx.js';
4
4
 
5
5
  /**
6
6
  * PlatformAdapter Interface
@@ -1,3 +1,3 @@
1
1
  import 'express';
2
- import '../types-L15pYd2c.mjs';
3
- export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-4WStX3PV.mjs';
2
+ import '../types-B3USs-Kx.mjs';
3
+ export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-ienhAXps.mjs';
@@ -1,3 +1,3 @@
1
1
  import 'express';
2
- import '../types-L15pYd2c.js';
3
- export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-C1ePFB7n.js';
2
+ import '../types-B3USs-Kx.js';
3
+ export { c as createMiddleware, a as extractAstraSyncCredentials } from '../express-CrfwoNAR.js';
@@ -45,7 +45,7 @@ function hasMinimumAccess(actual, required) {
45
45
  }
46
46
 
47
47
  // src/version.ts
48
- var SDK_VERSION = "2.4.12";
48
+ var SDK_VERSION = "2.4.13";
49
49
 
50
50
  // src/verify.ts
51
51
  var DEFAULT_CONFIG = {
@@ -64,22 +64,27 @@ var DEFAULT_CONFIG = {
64
64
  };
65
65
  var initCheckPerformed = false;
66
66
  var deprecationWarningShown = false;
67
- async function performInitCheck(apiBaseUrl, debug) {
67
+ async function performInitCheck(apiBaseUrl, debug, strictInit) {
68
68
  initCheckPerformed = true;
69
69
  try {
70
70
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
71
71
  const response = await fetch(probeUrl, { method: "HEAD" });
72
72
  const contentType = response.headers.get("content-type") ?? "";
73
73
  if (contentType.startsWith("text/html")) {
74
- console.warn(
75
- `[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.`
76
- );
74
+ 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).`;
75
+ if (strictInit) {
76
+ throw new Error(`${message} (strictInit=true)`);
77
+ }
78
+ console.warn(`${message} Set disableInitChecks: true on GatewayConfig to silence.`);
77
79
  } else if (debug) {
78
80
  console.log(
79
81
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
80
82
  );
81
83
  }
82
84
  } catch (err) {
85
+ if (strictInit) {
86
+ throw err;
87
+ }
83
88
  if (debug) {
84
89
  console.log(`[VerificationGateway] init check failed (non-blocking): ${String(err)}`);
85
90
  }
@@ -103,7 +108,23 @@ function getCacheKey(request) {
103
108
  request.counterpartyType || "",
104
109
  request.isSubAgentRequest ? "1" : "0",
105
110
  request.parentAgentId || "",
106
- request.subAgentDepth ?? ""
111
+ request.subAgentDepth ?? "",
112
+ // Audit F-A1-07: previously-missing dimensions that DO affect the
113
+ // backend verdict. Without these, two requests with different
114
+ // durations (e.g. 60s vs 86400s) collided on the same cache key and
115
+ // the shorter-duration allow served the longer-duration request.
116
+ request.durationRequired ?? "",
117
+ request.invocationProtocol || "",
118
+ request.enableRuntimeChallenge ? "1" : "0",
119
+ // callerMetadata fields contribute to risk model; include the ones
120
+ // backend reads. sourceIp/userAgent/forwardedFor change per-request
121
+ // so their inclusion effectively forces a re-check for any varying
122
+ // client (the right behavior — IP-driven anomaly scoring shouldn't
123
+ // be cached across IPs).
124
+ request.callerMetadata?.sourceIp || "",
125
+ request.callerMetadata?.userAgent || "",
126
+ request.callerMetadata?.forwardedFor || "",
127
+ request.callerMetadata?.agentCardUrl || ""
107
128
  ].join("|");
108
129
  }
109
130
  function getCachedResult(request) {
@@ -129,9 +150,13 @@ function cacheResult(request, result, configuredTtl) {
129
150
  }
130
151
  function extractCredentials(headers, query) {
131
152
  const credentials = {};
153
+ const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
132
154
  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"];
133
155
  if (astraIdHeader) {
134
- credentials.astraId = Array.isArray(astraIdHeader) ? astraIdHeader[0] : astraIdHeader;
156
+ const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
157
+ if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
158
+ credentials.astraId = raw;
159
+ }
135
160
  }
136
161
  const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
137
162
  if (apiKeyHeader) {
@@ -140,9 +165,11 @@ function extractCredentials(headers, query) {
140
165
  const authHeader = headers["authorization"] || headers["Authorization"];
141
166
  if (authHeader) {
142
167
  const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
143
- credentials.authorizationHeader = authValue;
144
- if (authValue.startsWith("Bearer ")) {
145
- credentials.jwt = authValue.slice(7);
168
+ if (typeof authValue === "string") {
169
+ credentials.authorizationHeader = authValue;
170
+ if (authValue.startsWith("Bearer ")) {
171
+ credentials.jwt = authValue.slice(7);
172
+ }
146
173
  }
147
174
  }
148
175
  if (query) {
@@ -160,7 +187,7 @@ function createGuidanceResponse(config, reason, options = {}) {
160
187
  const isApiError = source === "api_error";
161
188
  const guidance = isApiError ? {
162
189
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
163
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
190
+ registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
164
191
  documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
165
192
  steps: [
166
193
  "Retry the request with exponential backoff",
@@ -168,7 +195,7 @@ function createGuidanceResponse(config, reason, options = {}) {
168
195
  ]
169
196
  } : {
170
197
  message: "This service verifies AI agents before granting access. Please register your agent with AstraSync.",
171
- registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/register`,
198
+ registrationUrl: `${config.apiBaseUrl.replace("/api", "")}/agents/register`,
172
199
  documentationUrl: `${config.apiBaseUrl.replace("/api", "")}/docs/agent-access`,
173
200
  steps: [
174
201
  "Register for an AstraSync account",
@@ -245,12 +272,8 @@ async function callVerifyAccessAPI(config, request) {
245
272
  "Content-Type": "application/json",
246
273
  ...config.customHeaders
247
274
  };
248
- if (credentials.authorizationHeader) {
249
- headers["Authorization"] = credentials.authorizationHeader;
250
- } else if (config.apiKey) {
251
- headers["Authorization"] = `Bearer ${config.apiKey}`;
252
- }
253
275
  if (config.apiKey) {
276
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
254
277
  headers["X-API-Key"] = config.apiKey;
255
278
  }
256
279
  try {
@@ -296,7 +319,11 @@ async function callVerifyAccessAPI(config, request) {
296
319
  async function verify(config, request) {
297
320
  const mergedConfig = { ...DEFAULT_CONFIG, ...config };
298
321
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
299
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug);
322
+ if (mergedConfig.strictInit) {
323
+ await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
324
+ } else {
325
+ void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
326
+ }
300
327
  }
301
328
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
302
329
  deprecationWarningShown = true;
@@ -350,7 +377,7 @@ async function verify(config, request) {
350
377
  requiresApproval: apiResponse.access?.requiresApproval,
351
378
  guidance: {
352
379
  message: apiResponse.access?.reason || "Access denied by PDLSS policy",
353
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/register`,
380
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
354
381
  documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
355
382
  },
356
383
  verifiedAt: /* @__PURE__ */ new Date(),
@@ -420,13 +447,15 @@ async function verify(config, request) {
420
447
  result.denialReasons = result.recommendationReasons || [
421
448
  "Access denied by AstraSync recommendation"
422
449
  ];
423
- if (result.runtimeChallenge) {
424
- result.guidance = {
425
- message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
426
- registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/register`,
427
- documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
428
- };
429
- }
450
+ result.guidance = result.runtimeChallenge ? {
451
+ message: `Verification failed: ${result.runtimeChallenge.reason || "runtime challenge failed"}`,
452
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
453
+ documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/runtime-challenge`
454
+ } : {
455
+ message: result.recommendationReasons?.[0] || "Access denied by AstraSync recommendation",
456
+ registrationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/agents/register`,
457
+ documentationUrl: `${mergedConfig.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
458
+ };
430
459
  } else if (result.recommendation === "step_up_required") {
431
460
  result.requiresStepUp = true;
432
461
  if (ACCESS_LEVEL_HIERARCHY[result.accessLevel] > ACCESS_LEVEL_HIERARCHY["read-only"]) {
@@ -615,18 +644,40 @@ function defaultExtractPurpose(req) {
615
644
  return "general";
616
645
  }
617
646
  }
618
- function matchRoute(pattern, path) {
647
+ function matchRoute(pattern, path, opts) {
619
648
  const regexPattern = pattern.replace(/\*/g, ".*").replace(/\//g, "\\/");
620
- const regex = new RegExp(`^${regexPattern}$`);
621
- return regex.test(path);
649
+ const caseSensitiveRegex = new RegExp(`^${regexPattern}$`);
650
+ const caseSensitiveResult = caseSensitiveRegex.test(path);
651
+ if (!opts?.caseInsensitive && !opts?.logShadowDivergence) {
652
+ return caseSensitiveResult;
653
+ }
654
+ const caseInsensitiveRegex = new RegExp(`^${regexPattern}$`, "i");
655
+ const caseInsensitiveResult = caseInsensitiveRegex.test(path);
656
+ if (opts?.logShadowDivergence && caseSensitiveResult !== caseInsensitiveResult) {
657
+ console.warn(
658
+ `[SHADOW] matchRoute case-insensitive would change result: pattern=${pattern} path=${path} caseSensitive=${caseSensitiveResult} caseInsensitive=${caseInsensitiveResult} correlationId=${opts.correlationId ?? "unknown"}`
659
+ );
660
+ }
661
+ return opts?.caseInsensitive ? caseInsensitiveResult : caseSensitiveResult;
622
662
  }
623
- function findRouteConfig(routes, path, method) {
663
+ function findRouteConfig(routes, path, method, opts) {
624
664
  return routes.find((route) => {
625
665
  const methodMatches = route.method === "*" || route.method.toUpperCase() === method.toUpperCase();
626
- const pathMatches = matchRoute(route.pattern, path);
666
+ const pathMatches = matchRoute(route.pattern, path, opts);
627
667
  return methodMatches && pathMatches;
628
668
  });
629
669
  }
670
+ function dedupeFailures(result) {
671
+ if (result.failures && result.failures.length > 1) {
672
+ const seen = /* @__PURE__ */ new Set();
673
+ result.failures = result.failures.filter((f) => {
674
+ const key = `${f.dimension}|${f.message}|${f.guidance ?? ""}`;
675
+ if (seen.has(key)) return false;
676
+ seen.add(key);
677
+ return true;
678
+ });
679
+ }
680
+ }
630
681
  function defaultOnDenied(result, _req, res) {
631
682
  const statusCode = !result.identityVerified ? 401 : 403;
632
683
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
@@ -653,6 +704,8 @@ function createMiddleware(options) {
653
704
  recordDecisions,
654
705
  enableRuntimeChallenge = true,
655
706
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
707
+ failOnError = "open",
708
+ caseInsensitiveRouteMatch = false,
656
709
  ...config
657
710
  } = options;
658
711
  let cachedRoutes = [];
@@ -675,7 +728,7 @@ function createMiddleware(options) {
675
728
  cachedRoutes = fetched;
676
729
  lastFetchAt = Date.now();
677
730
  if (cachedRoutes.length === 0 && !warnedEmptyRoutes) {
678
- const dashboard = config.dashboardUrl ?? "https://app.astrasync.ai";
731
+ const dashboard = config.dashboardUrl ?? "https://astrasync.ai/dashboard";
679
732
  console.warn(
680
733
  `[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`
681
734
  );
@@ -701,7 +754,12 @@ function createMiddleware(options) {
701
754
  refreshing = null;
702
755
  });
703
756
  }
704
- const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method);
757
+ const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"];
758
+ const routeConfig = findRouteConfig(cachedRoutes, req.path, req.method, {
759
+ caseInsensitive: caseInsensitiveRouteMatch,
760
+ logShadowDivergence: true,
761
+ correlationId
762
+ });
705
763
  if (!routeConfig) {
706
764
  if (config.setPassThroughHeader) {
707
765
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
@@ -733,7 +791,7 @@ function createMiddleware(options) {
733
791
  denialReasons: preCheckFailures.map((f) => f.message),
734
792
  guidance: {
735
793
  message: "Request exceeds counterparty-defined PDLSS limits.",
736
- registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/register`,
794
+ registrationUrl: `${config.apiBaseUrl?.replace("/api", "")}/agents/register`,
737
795
  documentationUrl: `${config.apiBaseUrl?.replace("/api", "")}/docs/pdlss`
738
796
  },
739
797
  verifiedAt: /* @__PURE__ */ new Date()
@@ -748,13 +806,19 @@ function createMiddleware(options) {
748
806
  requestMethod: req.method
749
807
  }).catch(() => {
750
808
  });
809
+ dedupeFailures(result2);
751
810
  onDenied(result2, req, res);
752
811
  return;
753
812
  }
754
813
  const shouldRecordDecisions = recordDecisions !== false;
755
814
  const forwardedFor = req.headers["x-forwarded-for"];
756
815
  const forwardedForStr = Array.isArray(forwardedFor) ? forwardedFor.join(", ") : forwardedFor;
757
- const originalClientIp = forwardedForStr ? forwardedForStr.split(",")[0].trim() : req.ip;
816
+ const originalClientIp = req.ip ?? (forwardedForStr ? forwardedForStr.split(",")[0].trim() : void 0);
817
+ if (!req.ip && forwardedForStr) {
818
+ console.warn(
819
+ "[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."
820
+ );
821
+ }
758
822
  const agentCardUrl = typeof req.headers["x-astrasync-agent-card"] === "string" ? req.headers["x-astrasync-agent-card"] : void 0;
759
823
  const result = await verify(config, {
760
824
  credentials,
@@ -785,6 +849,7 @@ function createMiddleware(options) {
785
849
  recordDecision(config, sessionId, "denied", result.denialReasons?.[0]).catch(() => {
786
850
  });
787
851
  }
852
+ dedupeFailures(result);
788
853
  onDenied(result, req, res);
789
854
  return;
790
855
  }
@@ -811,6 +876,7 @@ function createMiddleware(options) {
811
876
  recordDecision(config, sessionId, "denied", insufficientFailure.message).catch(() => {
812
877
  });
813
878
  }
879
+ dedupeFailures(result);
814
880
  onDenied(result, req, res);
815
881
  return;
816
882
  }
@@ -827,6 +893,7 @@ function createMiddleware(options) {
827
893
  recordDecision(config, sessionId, "denied", trustFailure.message).catch(() => {
828
894
  });
829
895
  }
896
+ dedupeFailures(result);
830
897
  onDenied(result, req, res);
831
898
  return;
832
899
  }
@@ -841,7 +908,30 @@ function createMiddleware(options) {
841
908
  }
842
909
  next();
843
910
  } catch (error) {
911
+ const errorClass = error instanceof Error ? error.constructor.name : typeof error;
912
+ const correlationId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || `gen-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
844
913
  console.error("[VerificationGateway] Middleware error:", error);
914
+ console.warn(
915
+ `[SHADOW] would-have-denied: errorClass=${errorClass} route=${req.method}:${req.path} merchantId=${config.counterpartyId ?? "unknown"} correlationId=${correlationId}`
916
+ );
917
+ if (failOnError === "closed") {
918
+ const result = {
919
+ identityVerified: false,
920
+ policyAllowed: false,
921
+ accessLevel: "none",
922
+ denialReasons: [`Verification middleware internal error: ${errorClass}`],
923
+ failures: [
924
+ {
925
+ dimension: "middleware.internal_error",
926
+ message: `Middleware threw ${errorClass} \u2014 failing closed`
927
+ }
928
+ ],
929
+ verifiedAt: /* @__PURE__ */ new Date(),
930
+ correlationId
931
+ };
932
+ dedupeFailures(result);
933
+ return onDenied(result, req, res);
934
+ }
845
935
  next();
846
936
  }
847
937
  };