@astrasyncai/verification-gateway 3.12.0 → 4.1.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.
Files changed (134) hide show
  1. package/README.md +106 -29
  2. package/dist/adapter-interface/interface.d.mts +2 -2
  3. package/dist/adapter-interface/interface.d.ts +2 -2
  4. package/dist/adapters/express.d.mts +2 -2
  5. package/dist/adapters/express.d.ts +2 -2
  6. package/dist/adapters/express.js +482 -22
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +482 -22
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/http-pdlss.d.mts +3 -3
  11. package/dist/adapters/http-pdlss.d.ts +3 -3
  12. package/dist/adapters/http-pdlss.js.map +1 -1
  13. package/dist/adapters/http-pdlss.mjs.map +1 -1
  14. package/dist/adapters/mcp.d.mts +56 -36
  15. package/dist/adapters/mcp.d.ts +56 -36
  16. package/dist/adapters/mcp.js +309 -10
  17. package/dist/adapters/mcp.js.map +1 -1
  18. package/dist/adapters/mcp.mjs +309 -10
  19. package/dist/adapters/mcp.mjs.map +1 -1
  20. package/dist/adapters/nextjs.d.mts +2 -2
  21. package/dist/adapters/nextjs.d.ts +2 -2
  22. package/dist/adapters/nextjs.js +518 -25
  23. package/dist/adapters/nextjs.js.map +1 -1
  24. package/dist/adapters/nextjs.mjs +518 -25
  25. package/dist/adapters/nextjs.mjs.map +1 -1
  26. package/dist/adapters/sdk.d.mts +2 -2
  27. package/dist/adapters/sdk.d.ts +2 -2
  28. package/dist/adapters/sdk.js +72 -9
  29. package/dist/adapters/sdk.js.map +1 -1
  30. package/dist/adapters/sdk.mjs +72 -9
  31. package/dist/adapters/sdk.mjs.map +1 -1
  32. package/dist/agent/index.d.mts +2 -2
  33. package/dist/agent/index.d.ts +2 -2
  34. package/dist/agent/index.js.map +1 -1
  35. package/dist/agent/index.mjs.map +1 -1
  36. package/dist/bin/astrasync-claude-hook.js +83 -20
  37. package/dist/bin/astrasync-codex-hook.js +83 -20
  38. package/dist/bin/astrasync-guard.js +83 -20
  39. package/dist/bin/astrasync.js +150 -33
  40. package/dist/browser/background.js +83 -20
  41. package/dist/browser/background.js.map +1 -1
  42. package/dist/browser/background.mjs +83 -20
  43. package/dist/browser/background.mjs.map +1 -1
  44. package/dist/browser/browser-adapter.d.mts +2 -2
  45. package/dist/browser/browser-adapter.d.ts +2 -2
  46. package/dist/claude-code/claude-code-adapter.d.mts +2 -2
  47. package/dist/claude-code/claude-code-adapter.d.ts +2 -2
  48. package/dist/cli/index.d.mts +2 -2
  49. package/dist/cli/index.d.ts +2 -2
  50. package/dist/codex/index.d.mts +3 -3
  51. package/dist/codex/index.d.ts +3 -3
  52. package/dist/codex/index.js +83 -20
  53. package/dist/codex/index.js.map +1 -1
  54. package/dist/codex/index.mjs +83 -20
  55. package/dist/codex/index.mjs.map +1 -1
  56. package/dist/cursor/cursor-adapter.d.mts +2 -2
  57. package/dist/cursor/cursor-adapter.d.ts +2 -2
  58. package/dist/cursor/extension.d.mts +2 -2
  59. package/dist/cursor/extension.d.ts +2 -2
  60. package/dist/cursor/extension.js +83 -20
  61. package/dist/cursor/extension.js.map +1 -1
  62. package/dist/cursor/extension.mjs +83 -20
  63. package/dist/cursor/extension.mjs.map +1 -1
  64. package/dist/edge-config.d.mts +22 -7
  65. package/dist/edge-config.d.ts +22 -7
  66. package/dist/edge-config.js.map +1 -1
  67. package/dist/edge-config.mjs.map +1 -1
  68. package/dist/edge-core/index.d.mts +425 -0
  69. package/dist/edge-core/index.d.ts +425 -0
  70. package/dist/edge-core/index.js +1482 -0
  71. package/dist/edge-core/index.js.map +1 -0
  72. package/dist/edge-core/index.mjs +1437 -0
  73. package/dist/edge-core/index.mjs.map +1 -0
  74. package/dist/{express-B39o89gf.d.mts → express-BVd1_3FE.d.ts} +9 -7
  75. package/dist/{express-BuxKNO_q.d.ts → express-D_4hTn5Z.d.mts} +9 -7
  76. package/dist/gateway/gateway.d.mts +2 -2
  77. package/dist/gateway/gateway.d.ts +2 -2
  78. package/dist/gateway/gateway.js +83 -20
  79. package/dist/gateway/gateway.js.map +1 -1
  80. package/dist/gateway/gateway.mjs +83 -20
  81. package/dist/gateway/gateway.mjs.map +1 -1
  82. package/dist/git-trigger/git-hooks.d.mts +2 -2
  83. package/dist/git-trigger/git-hooks.d.ts +2 -2
  84. package/dist/{index-CLHdqs2M.d.ts → index-B0YHu_SP.d.ts} +30 -27
  85. package/dist/{index-DME3w3lf.d.mts → index-BPEBlOsE.d.mts} +30 -27
  86. package/dist/{index-CqydB4ks.d.mts → index-DQb5-_1X.d.mts} +1 -1
  87. package/dist/{index-BG62SXso.d.ts → index-T1aBoUcc.d.ts} +1 -1
  88. package/dist/index.d.mts +11 -11
  89. package/dist/index.d.ts +11 -11
  90. package/dist/index.js +595 -272
  91. package/dist/index.js.map +1 -1
  92. package/dist/index.mjs +592 -272
  93. package/dist/index.mjs.map +1 -1
  94. package/dist/local-evaluator/evaluator.d.mts +2 -2
  95. package/dist/local-evaluator/evaluator.d.ts +2 -2
  96. package/dist/metadata-capture.d.mts +63 -4
  97. package/dist/metadata-capture.d.ts +63 -4
  98. package/dist/metadata-capture.js +67 -0
  99. package/dist/metadata-capture.js.map +1 -1
  100. package/dist/metadata-capture.mjs +64 -0
  101. package/dist/metadata-capture.mjs.map +1 -1
  102. package/dist/{nextjs-D74W7wt-.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
  103. package/dist/{nextjs-Ddlw_J1J.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
  104. package/dist/platform-signatures.d.mts +5 -6
  105. package/dist/platform-signatures.d.ts +5 -6
  106. package/dist/platform-signatures.js.map +1 -1
  107. package/dist/platform-signatures.mjs.map +1 -1
  108. package/dist/registration/index.d.mts +10 -11
  109. package/dist/registration/index.d.ts +10 -11
  110. package/dist/registration/index.js.map +1 -1
  111. package/dist/registration/index.mjs.map +1 -1
  112. package/dist/{sdk-CT7gdkfR.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
  113. package/dist/{sdk-Bn6bj7kt.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
  114. package/dist/transport/index.d.mts +2 -2
  115. package/dist/transport/index.d.ts +2 -2
  116. package/dist/transport/index.js.map +1 -1
  117. package/dist/transport/index.mjs.map +1 -1
  118. package/dist/{types-DHu7m9HI.d.ts → types-BLUx92FJ.d.ts} +1 -1
  119. package/dist/{types-Djw4GLtz.d.mts → types-DqfPU5Bl.d.mts} +1 -1
  120. package/dist/{types-BntuzEEn.d.mts → types-r850cOt0.d.mts} +121 -88
  121. package/dist/{types-SDu4Llbe.d.ts → types-sMxBT-nM.d.ts} +121 -88
  122. package/dist/ui/index.d.mts +11 -7
  123. package/dist/ui/index.d.ts +11 -7
  124. package/dist/ui/index.js +13 -9
  125. package/dist/ui/index.js.map +1 -1
  126. package/dist/ui/index.mjs +10 -8
  127. package/dist/ui/index.mjs.map +1 -1
  128. package/dist/verify.d.mts +11 -8
  129. package/dist/verify.d.ts +11 -8
  130. package/dist/verify.js +76 -8
  131. package/dist/verify.js.map +1 -1
  132. package/dist/verify.mjs +75 -8
  133. package/dist/verify.mjs.map +1 -1
  134. package/package.json +8 -2
@@ -40,7 +40,7 @@ function getTrustLevel(score) {
40
40
  }
41
41
 
42
42
  // src/version.ts
43
- var SDK_VERSION = "3.12.0";
43
+ var SDK_VERSION = "4.1.0";
44
44
 
45
45
  // src/well-known.ts
46
46
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -118,7 +118,7 @@ var DEFAULT_CONFIG = {
118
118
  };
119
119
  var initCheckPerformed = false;
120
120
  var deprecationWarningShown = false;
121
- async function performInitCheck(apiBaseUrl, debug, strictInit) {
121
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
122
122
  initCheckPerformed = true;
123
123
  try {
124
124
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -135,6 +135,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
135
135
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
136
136
  );
137
137
  }
138
+ if (apiKey) {
139
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
140
+ headers: { Authorization: `Bearer ${apiKey}` }
141
+ });
142
+ if (selfCheck.status === 401) {
143
+ const body = await selfCheck.json().catch(() => ({}));
144
+ const message = `[VerificationGateway] the configured AstraSync API key was REJECTED at startup (${body.code ?? "api_key_invalid"}: ${body.message ?? "invalid key"}). Every inbound agent will be turned away until the key is rotated` + (body.remediation?.manageUrl ? ` at ${body.remediation.manageUrl}` : "") + `.`;
145
+ if (strictInit) {
146
+ throw new Error(`${message} (strictInit=true)`);
147
+ }
148
+ console.error(message);
149
+ } else if (selfCheck.ok) {
150
+ const body = await selfCheck.json().catch(() => ({}));
151
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
152
+ console.warn(
153
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
154
+ );
155
+ } else if (debug) {
156
+ console.log("[VerificationGateway] API key self-check passed");
157
+ }
158
+ }
159
+ }
138
160
  } catch (err) {
139
161
  if (strictInit) {
140
162
  throw err;
@@ -246,8 +268,17 @@ function extractCredentials(headers, query) {
246
268
  function createGuidanceResponse(_config, reason, options = {}) {
247
269
  const source = options.source ?? "no_credentials";
248
270
  const isApiError = source === "api_error";
271
+ const isMisconfig = source === "merchant_misconfig";
249
272
  const urls = options.urls;
250
- const guidance = isApiError ? {
273
+ const guidance = isMisconfig ? {
274
+ message: "This site's AstraSync integration is misconfigured (its API key was revoked, expired, or rotated). Your identity and the AstraSync platform are fine \u2014 contact the site operator. Retrying will not help.",
275
+ registrationUrl: urls?.registrationUrl ?? "",
276
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
277
+ steps: [
278
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
279
+ "Do not retry: the failure is deterministic until the operator updates their key"
280
+ ]
281
+ } : isApiError ? {
251
282
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
252
283
  registrationUrl: urls?.registrationUrl ?? "",
253
284
  documentationUrl: urls?.documentationUrl ?? "",
@@ -285,13 +316,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
285
316
  // partners (and their custom onDenied handlers) can branch on
286
317
  // dimension. Without this, the synthesised stub was indistinguishable
287
318
  // from a real policy deny.
288
- failures: isApiError ? [
319
+ failures: isMisconfig ? [
320
+ {
321
+ dimension: "verify_access.merchant_misconfigured",
322
+ message: reason ?? "The integration API key was rejected by AstraSync",
323
+ guidance: guidance.message
324
+ }
325
+ ] : isApiError ? [
289
326
  {
290
327
  dimension: "verify_access.api_error",
291
328
  message: reason ?? "Verification temporarily unavailable",
292
329
  guidance: guidance.message
293
330
  }
294
331
  ] : void 0,
332
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
333
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
334
+ ...isMisconfig && { misconfigured: true },
295
335
  correlationId: options.correlationId,
296
336
  attemptId: options.attemptId,
297
337
  verifiedAt: /* @__PURE__ */ new Date()
@@ -346,7 +386,8 @@ async function callVerifyAccessAPI(config, request) {
346
386
  const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
347
387
  method: "POST",
348
388
  headers,
349
- body: JSON.stringify(body)
389
+ body: JSON.stringify(body),
390
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
350
391
  });
351
392
  const data = await response.json();
352
393
  if (response.status === 410) {
@@ -370,6 +411,17 @@ async function callVerifyAccessAPI(config, request) {
370
411
  };
371
412
  }
372
413
  if (!response.ok) {
414
+ if (response.status === 401) {
415
+ return {
416
+ success: false,
417
+ error: data.message || data.error || "Integration API key rejected",
418
+ misconfig: true,
419
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
420
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
421
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
422
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
423
+ };
424
+ }
373
425
  return {
374
426
  success: false,
375
427
  error: data.message || data.error || `API returned ${response.status}`,
@@ -391,9 +443,19 @@ async function verify(config, request, options) {
391
443
  const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
392
444
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
393
445
  if (mergedConfig.strictInit) {
394
- await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
446
+ await performInitCheck(
447
+ mergedConfig.apiBaseUrl,
448
+ mergedConfig.debug,
449
+ true,
450
+ mergedConfig.apiKey
451
+ );
395
452
  } else {
396
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
453
+ void performInitCheck(
454
+ mergedConfig.apiBaseUrl,
455
+ mergedConfig.debug,
456
+ false,
457
+ mergedConfig.apiKey
458
+ );
397
459
  }
398
460
  }
399
461
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
@@ -432,9 +494,10 @@ async function verify(config, request, options) {
432
494
  const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
433
495
  if (!apiResponse.success) {
434
496
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
435
- source: "api_error",
497
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
436
498
  correlationId: apiResponse.correlationId,
437
499
  attemptId: apiResponse.attemptId,
500
+ remediation: apiResponse.remediation,
438
501
  urls
439
502
  });
440
503
  }
@@ -571,6 +634,205 @@ async function recordDecision(config, sessionId, decision, reason) {
571
634
  });
572
635
  }
573
636
 
637
+ // src/metadata-capture.ts
638
+ var CAPTURE_SCHEMA_VERSION = 1;
639
+ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
640
+ "cookie",
641
+ "set-cookie",
642
+ "x-csrf-token",
643
+ "proxy-authorization",
644
+ "x-astrasync-signature",
645
+ "x-astrasync-secret",
646
+ "x-hub-signature",
647
+ "x-hub-signature-256"
648
+ ]);
649
+ var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
650
+ var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
651
+ "authorization",
652
+ "x-api-key",
653
+ "x-goog-api-key",
654
+ "api-key"
655
+ ]);
656
+ function isSignatureHeader(name) {
657
+ return name === "signature" || name.endsWith("-signature") || name.endsWith("-signature-256");
658
+ }
659
+ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
660
+ "openai-organization",
661
+ "openai-project",
662
+ "openai-version",
663
+ "openai-processing-ms",
664
+ "anthropic-version",
665
+ "anthropic-beta",
666
+ "x-goog-user-project",
667
+ "x-goog-api-client",
668
+ "user-agent",
669
+ "x-agent-card-url",
670
+ "x-agent-provider"
671
+ ]);
672
+ var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
673
+ var KEY_FORMAT_PREFIXES = [
674
+ "sk-ant-api03",
675
+ "sk-ant-api",
676
+ "sk-ant",
677
+ "sk-proj",
678
+ "sk-svcacct",
679
+ "sk-or-v1",
680
+ "sk-or",
681
+ "sk-lf",
682
+ "sk-",
683
+ "gsk_",
684
+ "aiza",
685
+ "ya29",
686
+ "ghp_",
687
+ "gho_",
688
+ "github_pat_",
689
+ "xai-",
690
+ "pplx-",
691
+ "r8_",
692
+ "hf_",
693
+ "astra-",
694
+ "astrae-"
695
+ ];
696
+ var MAX_HEADERS = 64;
697
+ var MAX_HEADER_VALUE_BYTES = 1024;
698
+ var MAX_TOTAL_BYTES = 16 * 1024;
699
+ function byteLength(s) {
700
+ let bytes = 0;
701
+ for (let i = 0; i < s.length; i++) {
702
+ const code = s.charCodeAt(i);
703
+ if (code < 128) bytes += 1;
704
+ else if (code < 2048) bytes += 2;
705
+ else if (code >= 55296 && code <= 56319) {
706
+ bytes += 4;
707
+ i++;
708
+ } else bytes += 3;
709
+ }
710
+ return bytes;
711
+ }
712
+ function extractApiKeyFormat(rawValue) {
713
+ if (!rawValue) return void 0;
714
+ let value = rawValue.trim();
715
+ const spaceIdx = value.indexOf(" ");
716
+ if (spaceIdx > 0) {
717
+ const scheme = value.slice(0, spaceIdx).toLowerCase();
718
+ if (scheme === "bearer" || scheme === "basic" || scheme === "token") {
719
+ value = value.slice(spaceIdx + 1).trim();
720
+ }
721
+ }
722
+ const lower = value.toLowerCase();
723
+ for (const prefix of KEY_FORMAT_PREFIXES) {
724
+ if (lower.startsWith(prefix)) {
725
+ return prefix;
726
+ }
727
+ }
728
+ return void 0;
729
+ }
730
+ function isSecretHeader(name) {
731
+ if (SECRET_HEADER_NAMES.has(name)) return true;
732
+ if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;
733
+ if (isSignatureHeader(name)) return true;
734
+ return false;
735
+ }
736
+ function isPlatformHeader(name) {
737
+ if (PLATFORM_HEADER_NAMES.has(name)) return true;
738
+ return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));
739
+ }
740
+ function sanitizeHeaders(raw) {
741
+ const headers = {};
742
+ const platformHeaders = {};
743
+ let apiKeyFormat;
744
+ let totalBytes = 0;
745
+ let count = 0;
746
+ if (!raw) return { headers };
747
+ for (const rawName of Object.keys(raw)) {
748
+ if (count >= MAX_HEADERS) break;
749
+ const name = rawName.toLowerCase();
750
+ const rawValue = raw[rawName];
751
+ if (rawValue === void 0) continue;
752
+ const joined = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
753
+ if (CREDENTIAL_HEADER_NAMES.has(name)) {
754
+ const fmt = extractApiKeyFormat(joined);
755
+ if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
756
+ continue;
757
+ }
758
+ if (isSecretHeader(name)) continue;
759
+ let value = joined;
760
+ if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
761
+ value = value.slice(0, MAX_HEADER_VALUE_BYTES);
762
+ }
763
+ const entryBytes = byteLength(name) + byteLength(value);
764
+ if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;
765
+ totalBytes += entryBytes;
766
+ count++;
767
+ headers[name] = value;
768
+ if (isPlatformHeader(name)) platformHeaders[name] = value;
769
+ }
770
+ const result = { headers };
771
+ if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
772
+ if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
773
+ return result;
774
+ }
775
+ var CONNECTION_HEADER_SOURCES = [
776
+ ["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
777
+ [
778
+ "country",
779
+ ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
780
+ ],
781
+ ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
782
+ ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
783
+ ["asn", ["cloudfront-viewer-asn"]],
784
+ ["tlsVersion", ["cloudfront-viewer-tls"]],
785
+ ["httpVersion", ["cloudfront-viewer-http-version"]],
786
+ // TLS fingerprints can only be computed where TLS terminates. When a CDN
787
+ // terminates TLS in front of the merchant, its fingerprint headers ARE the
788
+ // connection-layer truth — normalising them here is the permanent design.
789
+ ["tlsFingerprint", ["cf-ja3-hash"]],
790
+ ["ja4", ["cf-ja4"]]
791
+ ];
792
+ function deriveConnectionFromHeaders(headers) {
793
+ if (!headers) return void 0;
794
+ const lower = {};
795
+ for (const name of Object.keys(headers)) {
796
+ const value = headers[name];
797
+ if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
798
+ }
799
+ const connection = {};
800
+ for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
801
+ for (const source of sources) {
802
+ const value = lower[source]?.trim();
803
+ if (value) {
804
+ connection[key] = value;
805
+ break;
806
+ }
807
+ }
808
+ }
809
+ if (!connection.ip) {
810
+ const xff = lower["x-forwarded-for"];
811
+ const client = xff?.split(",")[0]?.trim();
812
+ if (client) connection.ip = client;
813
+ }
814
+ return Object.keys(connection).length > 0 ? connection : void 0;
815
+ }
816
+ function buildSdkObservedMetadata(rawHeaders) {
817
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
818
+ const flat = {};
819
+ if (rawHeaders) {
820
+ for (const name of Object.keys(rawHeaders)) {
821
+ const value = rawHeaders[name];
822
+ if (value === void 0) continue;
823
+ flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
824
+ }
825
+ }
826
+ const connection = deriveConnectionFromHeaders(flat);
827
+ return {
828
+ headers,
829
+ ...apiKeyFormat && { apiKeyFormat },
830
+ ...platformHeaders && { platformHeaders },
831
+ ...connection && { connection },
832
+ schemaVersion: CAPTURE_SCHEMA_VERSION
833
+ };
834
+ }
835
+
574
836
  // src/adapters/approval-gate.ts
575
837
  var APPROVAL_REASON = "Transaction is above the autonomous limit and requires human approval. The agent owner can approve or deny this transaction in the AstraSync dashboard.";
576
838
  function requiresHumanApproval(result) {
@@ -749,8 +1011,11 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
749
1011
  } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
750
1012
  action = parsed.actionFromBody;
751
1013
  actionSource = parsed.actionSourceFromBody;
1014
+ } else if (parsed.method === "tools/call") {
1015
+ action = void 0;
1016
+ actionSource = void 0;
752
1017
  } else {
753
- action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
1018
+ action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
754
1019
  actionSource = "transport_layer";
755
1020
  }
756
1021
  return { purpose, action, resource, purposeSource, actionSource };
@@ -788,6 +1053,24 @@ function dedupeFailures(result) {
788
1053
  }
789
1054
  function defaultMcpDenied(result, req, res) {
790
1055
  const id = req.body?.id ?? null;
1056
+ if (result.misconfigured) {
1057
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1058
+ res.status(503).json({
1059
+ jsonrpc: "2.0",
1060
+ id,
1061
+ error: {
1062
+ // -32002 → merchant verification misconfigured (see -32000/-32001 below)
1063
+ code: -32002,
1064
+ message: result.denialReasons?.[0] ?? "This server's AstraSync integration is misconfigured",
1065
+ data: {
1066
+ guidance: result.guidance,
1067
+ failures: result.failures,
1068
+ correlationId: result.correlationId
1069
+ }
1070
+ }
1071
+ });
1072
+ return;
1073
+ }
791
1074
  const status = !result.identityVerified ? 401 : 403;
792
1075
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
793
1076
  res.status(status).json({
@@ -797,6 +1080,7 @@ function defaultMcpDenied(result, req, res) {
797
1080
  // JSON-RPC error codes:
798
1081
  // -32000 → unauthorized (no identity resolved)
799
1082
  // -32001 → insufficient access (identity OK, policy denied)
1083
+ // -32002 → merchant verification misconfigured (handled above)
800
1084
  code: !result.identityVerified ? -32e3 : -32001,
801
1085
  message: result.denialReasons?.[0] ?? "Access denied",
802
1086
  data: {
@@ -846,6 +1130,15 @@ function createMcpMiddleware(options) {
846
1130
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
847
1131
  });
848
1132
  }
1133
+ if (toolGates) {
1134
+ for (const [toolName, gate] of Object.entries(toolGates)) {
1135
+ if (typeof gate === "string") {
1136
+ console.warn(
1137
+ `[VerificationGateway/MCP] toolGates['${toolName}'] uses the bare-string shorthand ('${gate}'). Gated tools need a PDLSS purpose (else PDLSS_PURPOSE_REQUIRED 400) and evaluate purpose-only without a declared action. Use the object form instead: { minAccessLevel: '${gate}', purpose: '...', action: '...' }.`
1138
+ );
1139
+ }
1140
+ }
1141
+ }
849
1142
  return async (req, res, next) => {
850
1143
  try {
851
1144
  if (skip) return next();
@@ -965,7 +1258,13 @@ function createMcpMiddleware(options) {
965
1258
  callerMetadata: {
966
1259
  sourceIp: req.ip,
967
1260
  userAgent: req.headers["user-agent"],
968
- host: req.headers.host
1261
+ host: req.headers.host,
1262
+ // Capture EVERY inbound header (parity with the express adapter —
1263
+ // MCP-only merchants have no edge to do it upstream) plus the
1264
+ // CDN-derived connection block. Local string work, secrets
1265
+ // stripped; out of the verify cache key so it doesn't defeat
1266
+ // caching.
1267
+ observedMetadata: buildSdkObservedMetadata(req.headers)
969
1268
  }
970
1269
  };
971
1270
  let result = await verify(config, verificationRequest);