@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
@@ -7,7 +7,7 @@ function getTrustLevel(score) {
7
7
  }
8
8
 
9
9
  // src/version.ts
10
- var SDK_VERSION = "3.12.0";
10
+ var SDK_VERSION = "4.1.0";
11
11
 
12
12
  // src/well-known.ts
13
13
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -85,7 +85,7 @@ var DEFAULT_CONFIG = {
85
85
  };
86
86
  var initCheckPerformed = false;
87
87
  var deprecationWarningShown = false;
88
- async function performInitCheck(apiBaseUrl, debug, strictInit) {
88
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
89
89
  initCheckPerformed = true;
90
90
  try {
91
91
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -102,6 +102,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
102
102
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
103
103
  );
104
104
  }
105
+ if (apiKey) {
106
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
107
+ headers: { Authorization: `Bearer ${apiKey}` }
108
+ });
109
+ if (selfCheck.status === 401) {
110
+ const body = await selfCheck.json().catch(() => ({}));
111
+ 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}` : "") + `.`;
112
+ if (strictInit) {
113
+ throw new Error(`${message} (strictInit=true)`);
114
+ }
115
+ console.error(message);
116
+ } else if (selfCheck.ok) {
117
+ const body = await selfCheck.json().catch(() => ({}));
118
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
119
+ console.warn(
120
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
121
+ );
122
+ } else if (debug) {
123
+ console.log("[VerificationGateway] API key self-check passed");
124
+ }
125
+ }
126
+ }
105
127
  } catch (err) {
106
128
  if (strictInit) {
107
129
  throw err;
@@ -213,8 +235,17 @@ function extractCredentials(headers, query) {
213
235
  function createGuidanceResponse(_config, reason, options = {}) {
214
236
  const source = options.source ?? "no_credentials";
215
237
  const isApiError = source === "api_error";
238
+ const isMisconfig = source === "merchant_misconfig";
216
239
  const urls = options.urls;
217
- const guidance = isApiError ? {
240
+ const guidance = isMisconfig ? {
241
+ 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.",
242
+ registrationUrl: urls?.registrationUrl ?? "",
243
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
244
+ steps: [
245
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
246
+ "Do not retry: the failure is deterministic until the operator updates their key"
247
+ ]
248
+ } : isApiError ? {
218
249
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
219
250
  registrationUrl: urls?.registrationUrl ?? "",
220
251
  documentationUrl: urls?.documentationUrl ?? "",
@@ -252,13 +283,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
252
283
  // partners (and their custom onDenied handlers) can branch on
253
284
  // dimension. Without this, the synthesised stub was indistinguishable
254
285
  // from a real policy deny.
255
- failures: isApiError ? [
286
+ failures: isMisconfig ? [
287
+ {
288
+ dimension: "verify_access.merchant_misconfigured",
289
+ message: reason ?? "The integration API key was rejected by AstraSync",
290
+ guidance: guidance.message
291
+ }
292
+ ] : isApiError ? [
256
293
  {
257
294
  dimension: "verify_access.api_error",
258
295
  message: reason ?? "Verification temporarily unavailable",
259
296
  guidance: guidance.message
260
297
  }
261
298
  ] : void 0,
299
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
300
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
301
+ ...isMisconfig && { misconfigured: true },
262
302
  correlationId: options.correlationId,
263
303
  attemptId: options.attemptId,
264
304
  verifiedAt: /* @__PURE__ */ new Date()
@@ -313,7 +353,8 @@ async function callVerifyAccessAPI(config, request) {
313
353
  const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
314
354
  method: "POST",
315
355
  headers,
316
- body: JSON.stringify(body)
356
+ body: JSON.stringify(body),
357
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
317
358
  });
318
359
  const data = await response.json();
319
360
  if (response.status === 410) {
@@ -337,6 +378,17 @@ async function callVerifyAccessAPI(config, request) {
337
378
  };
338
379
  }
339
380
  if (!response.ok) {
381
+ if (response.status === 401) {
382
+ return {
383
+ success: false,
384
+ error: data.message || data.error || "Integration API key rejected",
385
+ misconfig: true,
386
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
387
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
388
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
389
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
390
+ };
391
+ }
340
392
  return {
341
393
  success: false,
342
394
  error: data.message || data.error || `API returned ${response.status}`,
@@ -358,9 +410,19 @@ async function verify(config, request, options) {
358
410
  const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
359
411
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
360
412
  if (mergedConfig.strictInit) {
361
- await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
413
+ await performInitCheck(
414
+ mergedConfig.apiBaseUrl,
415
+ mergedConfig.debug,
416
+ true,
417
+ mergedConfig.apiKey
418
+ );
362
419
  } else {
363
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
420
+ void performInitCheck(
421
+ mergedConfig.apiBaseUrl,
422
+ mergedConfig.debug,
423
+ false,
424
+ mergedConfig.apiKey
425
+ );
364
426
  }
365
427
  }
366
428
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
@@ -399,9 +461,10 @@ async function verify(config, request, options) {
399
461
  const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
400
462
  if (!apiResponse.success) {
401
463
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
402
- source: "api_error",
464
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
403
465
  correlationId: apiResponse.correlationId,
404
466
  attemptId: apiResponse.attemptId,
467
+ remediation: apiResponse.remediation,
405
468
  urls
406
469
  });
407
470
  }
@@ -538,6 +601,205 @@ async function recordDecision(config, sessionId, decision, reason) {
538
601
  });
539
602
  }
540
603
 
604
+ // src/metadata-capture.ts
605
+ var CAPTURE_SCHEMA_VERSION = 1;
606
+ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
607
+ "cookie",
608
+ "set-cookie",
609
+ "x-csrf-token",
610
+ "proxy-authorization",
611
+ "x-astrasync-signature",
612
+ "x-astrasync-secret",
613
+ "x-hub-signature",
614
+ "x-hub-signature-256"
615
+ ]);
616
+ var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
617
+ var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
618
+ "authorization",
619
+ "x-api-key",
620
+ "x-goog-api-key",
621
+ "api-key"
622
+ ]);
623
+ function isSignatureHeader(name) {
624
+ return name === "signature" || name.endsWith("-signature") || name.endsWith("-signature-256");
625
+ }
626
+ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
627
+ "openai-organization",
628
+ "openai-project",
629
+ "openai-version",
630
+ "openai-processing-ms",
631
+ "anthropic-version",
632
+ "anthropic-beta",
633
+ "x-goog-user-project",
634
+ "x-goog-api-client",
635
+ "user-agent",
636
+ "x-agent-card-url",
637
+ "x-agent-provider"
638
+ ]);
639
+ var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
640
+ var KEY_FORMAT_PREFIXES = [
641
+ "sk-ant-api03",
642
+ "sk-ant-api",
643
+ "sk-ant",
644
+ "sk-proj",
645
+ "sk-svcacct",
646
+ "sk-or-v1",
647
+ "sk-or",
648
+ "sk-lf",
649
+ "sk-",
650
+ "gsk_",
651
+ "aiza",
652
+ "ya29",
653
+ "ghp_",
654
+ "gho_",
655
+ "github_pat_",
656
+ "xai-",
657
+ "pplx-",
658
+ "r8_",
659
+ "hf_",
660
+ "astra-",
661
+ "astrae-"
662
+ ];
663
+ var MAX_HEADERS = 64;
664
+ var MAX_HEADER_VALUE_BYTES = 1024;
665
+ var MAX_TOTAL_BYTES = 16 * 1024;
666
+ function byteLength(s) {
667
+ let bytes = 0;
668
+ for (let i = 0; i < s.length; i++) {
669
+ const code = s.charCodeAt(i);
670
+ if (code < 128) bytes += 1;
671
+ else if (code < 2048) bytes += 2;
672
+ else if (code >= 55296 && code <= 56319) {
673
+ bytes += 4;
674
+ i++;
675
+ } else bytes += 3;
676
+ }
677
+ return bytes;
678
+ }
679
+ function extractApiKeyFormat(rawValue) {
680
+ if (!rawValue) return void 0;
681
+ let value = rawValue.trim();
682
+ const spaceIdx = value.indexOf(" ");
683
+ if (spaceIdx > 0) {
684
+ const scheme = value.slice(0, spaceIdx).toLowerCase();
685
+ if (scheme === "bearer" || scheme === "basic" || scheme === "token") {
686
+ value = value.slice(spaceIdx + 1).trim();
687
+ }
688
+ }
689
+ const lower = value.toLowerCase();
690
+ for (const prefix of KEY_FORMAT_PREFIXES) {
691
+ if (lower.startsWith(prefix)) {
692
+ return prefix;
693
+ }
694
+ }
695
+ return void 0;
696
+ }
697
+ function isSecretHeader(name) {
698
+ if (SECRET_HEADER_NAMES.has(name)) return true;
699
+ if (SECRET_HEADER_PREFIXES.some((p) => name.startsWith(p))) return true;
700
+ if (isSignatureHeader(name)) return true;
701
+ return false;
702
+ }
703
+ function isPlatformHeader(name) {
704
+ if (PLATFORM_HEADER_NAMES.has(name)) return true;
705
+ return PLATFORM_HEADER_PREFIXES.some((p) => name.startsWith(p));
706
+ }
707
+ function sanitizeHeaders(raw) {
708
+ const headers = {};
709
+ const platformHeaders = {};
710
+ let apiKeyFormat;
711
+ let totalBytes = 0;
712
+ let count = 0;
713
+ if (!raw) return { headers };
714
+ for (const rawName of Object.keys(raw)) {
715
+ if (count >= MAX_HEADERS) break;
716
+ const name = rawName.toLowerCase();
717
+ const rawValue = raw[rawName];
718
+ if (rawValue === void 0) continue;
719
+ const joined = Array.isArray(rawValue) ? rawValue.join(", ") : String(rawValue);
720
+ if (CREDENTIAL_HEADER_NAMES.has(name)) {
721
+ const fmt = extractApiKeyFormat(joined);
722
+ if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
723
+ continue;
724
+ }
725
+ if (isSecretHeader(name)) continue;
726
+ let value = joined;
727
+ if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
728
+ value = value.slice(0, MAX_HEADER_VALUE_BYTES);
729
+ }
730
+ const entryBytes = byteLength(name) + byteLength(value);
731
+ if (totalBytes + entryBytes > MAX_TOTAL_BYTES) continue;
732
+ totalBytes += entryBytes;
733
+ count++;
734
+ headers[name] = value;
735
+ if (isPlatformHeader(name)) platformHeaders[name] = value;
736
+ }
737
+ const result = { headers };
738
+ if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
739
+ if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
740
+ return result;
741
+ }
742
+ var CONNECTION_HEADER_SOURCES = [
743
+ ["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
744
+ [
745
+ "country",
746
+ ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
747
+ ],
748
+ ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
749
+ ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
750
+ ["asn", ["cloudfront-viewer-asn"]],
751
+ ["tlsVersion", ["cloudfront-viewer-tls"]],
752
+ ["httpVersion", ["cloudfront-viewer-http-version"]],
753
+ // TLS fingerprints can only be computed where TLS terminates. When a CDN
754
+ // terminates TLS in front of the merchant, its fingerprint headers ARE the
755
+ // connection-layer truth — normalising them here is the permanent design.
756
+ ["tlsFingerprint", ["cf-ja3-hash"]],
757
+ ["ja4", ["cf-ja4"]]
758
+ ];
759
+ function deriveConnectionFromHeaders(headers) {
760
+ if (!headers) return void 0;
761
+ const lower = {};
762
+ for (const name of Object.keys(headers)) {
763
+ const value = headers[name];
764
+ if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
765
+ }
766
+ const connection = {};
767
+ for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
768
+ for (const source of sources) {
769
+ const value = lower[source]?.trim();
770
+ if (value) {
771
+ connection[key] = value;
772
+ break;
773
+ }
774
+ }
775
+ }
776
+ if (!connection.ip) {
777
+ const xff = lower["x-forwarded-for"];
778
+ const client = xff?.split(",")[0]?.trim();
779
+ if (client) connection.ip = client;
780
+ }
781
+ return Object.keys(connection).length > 0 ? connection : void 0;
782
+ }
783
+ function buildSdkObservedMetadata(rawHeaders) {
784
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
785
+ const flat = {};
786
+ if (rawHeaders) {
787
+ for (const name of Object.keys(rawHeaders)) {
788
+ const value = rawHeaders[name];
789
+ if (value === void 0) continue;
790
+ flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
791
+ }
792
+ }
793
+ const connection = deriveConnectionFromHeaders(flat);
794
+ return {
795
+ headers,
796
+ ...apiKeyFormat && { apiKeyFormat },
797
+ ...platformHeaders && { platformHeaders },
798
+ ...connection && { connection },
799
+ schemaVersion: CAPTURE_SCHEMA_VERSION
800
+ };
801
+ }
802
+
541
803
  // src/adapters/approval-gate.ts
542
804
  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.";
543
805
  function requiresHumanApproval(result) {
@@ -716,8 +978,11 @@ function mcpToPdlss(parsed, requestPath, headerPurpose, headerAction, toolGate)
716
978
  } else if (parsed.actionFromBody && parsed.actionSourceFromBody) {
717
979
  action = parsed.actionFromBody;
718
980
  actionSource = parsed.actionSourceFromBody;
981
+ } else if (parsed.method === "tools/call") {
982
+ action = void 0;
983
+ actionSource = void 0;
719
984
  } else {
720
- action = parsed.toolName ? parsed.method === "tools/call" ? parsed.toolName : `${parsed.method}:${parsed.toolName}` : parsed.method;
985
+ action = parsed.toolName ? `${parsed.method}:${parsed.toolName}` : parsed.method;
721
986
  actionSource = "transport_layer";
722
987
  }
723
988
  return { purpose, action, resource, purposeSource, actionSource };
@@ -755,6 +1020,24 @@ function dedupeFailures(result) {
755
1020
  }
756
1021
  function defaultMcpDenied(result, req, res) {
757
1022
  const id = req.body?.id ?? null;
1023
+ if (result.misconfigured) {
1024
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1025
+ res.status(503).json({
1026
+ jsonrpc: "2.0",
1027
+ id,
1028
+ error: {
1029
+ // -32002 → merchant verification misconfigured (see -32000/-32001 below)
1030
+ code: -32002,
1031
+ message: result.denialReasons?.[0] ?? "This server's AstraSync integration is misconfigured",
1032
+ data: {
1033
+ guidance: result.guidance,
1034
+ failures: result.failures,
1035
+ correlationId: result.correlationId
1036
+ }
1037
+ }
1038
+ });
1039
+ return;
1040
+ }
758
1041
  const status = !result.identityVerified ? 401 : 403;
759
1042
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
760
1043
  res.status(status).json({
@@ -764,6 +1047,7 @@ function defaultMcpDenied(result, req, res) {
764
1047
  // JSON-RPC error codes:
765
1048
  // -32000 → unauthorized (no identity resolved)
766
1049
  // -32001 → insufficient access (identity OK, policy denied)
1050
+ // -32002 → merchant verification misconfigured (handled above)
767
1051
  code: !result.identityVerified ? -32e3 : -32001,
768
1052
  message: result.denialReasons?.[0] ?? "Access denied",
769
1053
  data: {
@@ -813,6 +1097,15 @@ function createMcpMiddleware(options) {
813
1097
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
814
1098
  });
815
1099
  }
1100
+ if (toolGates) {
1101
+ for (const [toolName, gate] of Object.entries(toolGates)) {
1102
+ if (typeof gate === "string") {
1103
+ console.warn(
1104
+ `[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: '...' }.`
1105
+ );
1106
+ }
1107
+ }
1108
+ }
816
1109
  return async (req, res, next) => {
817
1110
  try {
818
1111
  if (skip) return next();
@@ -932,7 +1225,13 @@ function createMcpMiddleware(options) {
932
1225
  callerMetadata: {
933
1226
  sourceIp: req.ip,
934
1227
  userAgent: req.headers["user-agent"],
935
- host: req.headers.host
1228
+ host: req.headers.host,
1229
+ // Capture EVERY inbound header (parity with the express adapter —
1230
+ // MCP-only merchants have no edge to do it upstream) plus the
1231
+ // CDN-derived connection block. Local string work, secrets
1232
+ // stripped; out of the verify cache key so it doesn't defeat
1233
+ // caching.
1234
+ observedMetadata: buildSdkObservedMetadata(req.headers)
936
1235
  }
937
1236
  };
938
1237
  let result = await verify(config, verificationRequest);