@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
@@ -65,7 +65,7 @@ function getTrustLevel(score) {
65
65
  }
66
66
 
67
67
  // src/version.ts
68
- var SDK_VERSION = "3.12.0";
68
+ var SDK_VERSION = "4.1.0";
69
69
 
70
70
  // src/well-known.ts
71
71
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -143,7 +143,7 @@ var DEFAULT_CONFIG = {
143
143
  };
144
144
  var initCheckPerformed = false;
145
145
  var deprecationWarningShown = false;
146
- async function performInitCheck(apiBaseUrl, debug, strictInit) {
146
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
147
147
  initCheckPerformed = true;
148
148
  try {
149
149
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -160,6 +160,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
160
160
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
161
161
  );
162
162
  }
163
+ if (apiKey) {
164
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
165
+ headers: { Authorization: `Bearer ${apiKey}` }
166
+ });
167
+ if (selfCheck.status === 401) {
168
+ const body = await selfCheck.json().catch(() => ({}));
169
+ 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}` : "") + `.`;
170
+ if (strictInit) {
171
+ throw new Error(`${message} (strictInit=true)`);
172
+ }
173
+ console.error(message);
174
+ } else if (selfCheck.ok) {
175
+ const body = await selfCheck.json().catch(() => ({}));
176
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
177
+ console.warn(
178
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
179
+ );
180
+ } else if (debug) {
181
+ console.log("[VerificationGateway] API key self-check passed");
182
+ }
183
+ }
184
+ }
163
185
  } catch (err) {
164
186
  if (strictInit) {
165
187
  throw err;
@@ -271,8 +293,17 @@ function extractCredentials(headers, query) {
271
293
  function createGuidanceResponse(_config, reason, options = {}) {
272
294
  const source = options.source ?? "no_credentials";
273
295
  const isApiError = source === "api_error";
296
+ const isMisconfig = source === "merchant_misconfig";
274
297
  const urls = options.urls;
275
- const guidance = isApiError ? {
298
+ const guidance = isMisconfig ? {
299
+ 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.",
300
+ registrationUrl: urls?.registrationUrl ?? "",
301
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
302
+ steps: [
303
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
304
+ "Do not retry: the failure is deterministic until the operator updates their key"
305
+ ]
306
+ } : isApiError ? {
276
307
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
277
308
  registrationUrl: urls?.registrationUrl ?? "",
278
309
  documentationUrl: urls?.documentationUrl ?? "",
@@ -310,13 +341,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
310
341
  // partners (and their custom onDenied handlers) can branch on
311
342
  // dimension. Without this, the synthesised stub was indistinguishable
312
343
  // from a real policy deny.
313
- failures: isApiError ? [
344
+ failures: isMisconfig ? [
345
+ {
346
+ dimension: "verify_access.merchant_misconfigured",
347
+ message: reason ?? "The integration API key was rejected by AstraSync",
348
+ guidance: guidance.message
349
+ }
350
+ ] : isApiError ? [
314
351
  {
315
352
  dimension: "verify_access.api_error",
316
353
  message: reason ?? "Verification temporarily unavailable",
317
354
  guidance: guidance.message
318
355
  }
319
356
  ] : void 0,
357
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
358
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
359
+ ...isMisconfig && { misconfigured: true },
320
360
  correlationId: options.correlationId,
321
361
  attemptId: options.attemptId,
322
362
  verifiedAt: /* @__PURE__ */ new Date()
@@ -371,7 +411,8 @@ async function callVerifyAccessAPI(config, request) {
371
411
  const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
372
412
  method: "POST",
373
413
  headers,
374
- body: JSON.stringify(body)
414
+ body: JSON.stringify(body),
415
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
375
416
  });
376
417
  const data = await response.json();
377
418
  if (response.status === 410) {
@@ -395,6 +436,17 @@ async function callVerifyAccessAPI(config, request) {
395
436
  };
396
437
  }
397
438
  if (!response.ok) {
439
+ if (response.status === 401) {
440
+ return {
441
+ success: false,
442
+ error: data.message || data.error || "Integration API key rejected",
443
+ misconfig: true,
444
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
445
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
446
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
447
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
448
+ };
449
+ }
398
450
  return {
399
451
  success: false,
400
452
  error: data.message || data.error || `API returned ${response.status}`,
@@ -416,9 +468,19 @@ async function verify(config, request, options) {
416
468
  const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
417
469
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
418
470
  if (mergedConfig.strictInit) {
419
- await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
471
+ await performInitCheck(
472
+ mergedConfig.apiBaseUrl,
473
+ mergedConfig.debug,
474
+ true,
475
+ mergedConfig.apiKey
476
+ );
420
477
  } else {
421
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
478
+ void performInitCheck(
479
+ mergedConfig.apiBaseUrl,
480
+ mergedConfig.debug,
481
+ false,
482
+ mergedConfig.apiKey
483
+ );
422
484
  }
423
485
  }
424
486
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
@@ -457,9 +519,10 @@ async function verify(config, request, options) {
457
519
  const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
458
520
  if (!apiResponse.success) {
459
521
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
460
- source: "api_error",
522
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
461
523
  correlationId: apiResponse.correlationId,
462
524
  attemptId: apiResponse.attemptId,
525
+ remediation: apiResponse.remediation,
463
526
  urls
464
527
  });
465
528
  }
@@ -614,6 +677,21 @@ async function fetchRoutes(config, counterpartyId) {
614
677
  return null;
615
678
  }
616
679
  }
680
+ async function reportUnregisteredAttempt(config, data) {
681
+ const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
682
+ const { counterpartyUrl, ...rest } = data;
683
+ const body = {
684
+ ...rest,
685
+ ...counterpartyUrl && { counterpartyUrl },
686
+ ...config.counterpartyId && { counterpartyId: config.counterpartyId }
687
+ };
688
+ await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
689
+ method: "POST",
690
+ headers: { "Content-Type": "application/json" },
691
+ body: JSON.stringify(body)
692
+ }).catch(() => {
693
+ });
694
+ }
617
695
  async function reportCounterpartyPreCheckFailure(config, data) {
618
696
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
619
697
  await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
@@ -811,6 +889,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
811
889
  }
812
890
 
813
891
  // src/metadata-capture.ts
892
+ var CAPTURE_SCHEMA_VERSION = 1;
814
893
  var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
815
894
  "cookie",
816
895
  "set-cookie",
@@ -947,6 +1026,359 @@ function sanitizeHeaders(raw) {
947
1026
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
948
1027
  return result;
949
1028
  }
1029
+ var CONNECTION_HEADER_SOURCES = [
1030
+ ["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
1031
+ [
1032
+ "country",
1033
+ ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
1034
+ ],
1035
+ ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
1036
+ ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
1037
+ ["asn", ["cloudfront-viewer-asn"]],
1038
+ ["tlsVersion", ["cloudfront-viewer-tls"]],
1039
+ ["httpVersion", ["cloudfront-viewer-http-version"]],
1040
+ // TLS fingerprints can only be computed where TLS terminates. When a CDN
1041
+ // terminates TLS in front of the merchant, its fingerprint headers ARE the
1042
+ // connection-layer truth — normalising them here is the permanent design.
1043
+ ["tlsFingerprint", ["cf-ja3-hash"]],
1044
+ ["ja4", ["cf-ja4"]]
1045
+ ];
1046
+ function deriveConnectionFromHeaders(headers) {
1047
+ if (!headers) return void 0;
1048
+ const lower = {};
1049
+ for (const name of Object.keys(headers)) {
1050
+ const value = headers[name];
1051
+ if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
1052
+ }
1053
+ const connection = {};
1054
+ for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
1055
+ for (const source of sources) {
1056
+ const value = lower[source]?.trim();
1057
+ if (value) {
1058
+ connection[key] = value;
1059
+ break;
1060
+ }
1061
+ }
1062
+ }
1063
+ if (!connection.ip) {
1064
+ const xff = lower["x-forwarded-for"];
1065
+ const client = xff?.split(",")[0]?.trim();
1066
+ if (client) connection.ip = client;
1067
+ }
1068
+ return Object.keys(connection).length > 0 ? connection : void 0;
1069
+ }
1070
+ function buildSdkObservedMetadata(rawHeaders) {
1071
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1072
+ const flat = {};
1073
+ if (rawHeaders) {
1074
+ for (const name of Object.keys(rawHeaders)) {
1075
+ const value = rawHeaders[name];
1076
+ if (value === void 0) continue;
1077
+ flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
1078
+ }
1079
+ }
1080
+ const connection = deriveConnectionFromHeaders(flat);
1081
+ return {
1082
+ headers,
1083
+ ...apiKeyFormat && { apiKeyFormat },
1084
+ ...platformHeaders && { platformHeaders },
1085
+ ...connection && { connection },
1086
+ schemaVersion: CAPTURE_SCHEMA_VERSION
1087
+ };
1088
+ }
1089
+
1090
+ // src/edge-config.ts
1091
+ var DEFAULT_EDGE_CONFIG = Object.freeze({
1092
+ version: 1,
1093
+ mode: "observe",
1094
+ depth: "classify",
1095
+ pathRules: [],
1096
+ sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
1097
+ failurePosture: "open"
1098
+ });
1099
+ var EDGE_MODES = ["observe", "enforce"];
1100
+ var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
1101
+ function isEdgeConfig(value) {
1102
+ if (typeof value !== "object" || value === null) return false;
1103
+ const v = value;
1104
+ if (v.version !== 1) return false;
1105
+ if (!EDGE_MODES.includes(v.mode)) return false;
1106
+ if (!EDGE_DEPTHS.includes(v.depth)) return false;
1107
+ if (!Array.isArray(v.pathRules)) return false;
1108
+ for (const rule of v.pathRules) {
1109
+ if (typeof rule !== "object" || rule === null) return false;
1110
+ const r = rule;
1111
+ if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
1112
+ if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
1113
+ if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
1114
+ }
1115
+ const sampling = v.sampling;
1116
+ if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
1117
+ return false;
1118
+ }
1119
+ return v.failurePosture === "open";
1120
+ }
1121
+ async function fetchEdgeConfig(config, counterpartyId, opts) {
1122
+ if (!counterpartyId) return null;
1123
+ const headers = { Accept: "application/json" };
1124
+ if (config.apiKey) {
1125
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
1126
+ headers["X-API-Key"] = config.apiKey;
1127
+ }
1128
+ if (opts?.etag) headers["If-None-Match"] = opts.etag;
1129
+ try {
1130
+ const response = await fetch(
1131
+ `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1132
+ { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1133
+ );
1134
+ if (response.status === 304) return { notModified: true };
1135
+ if (!response.ok) return null;
1136
+ const body = await response.json();
1137
+ const edgeConfig = body.data?.edgeConfig;
1138
+ if (!isEdgeConfig(edgeConfig)) return null;
1139
+ return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
1140
+ } catch {
1141
+ return null;
1142
+ }
1143
+ }
1144
+ var DEFAULT_TTL_MS = 60 * 1e3;
1145
+ var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
1146
+ var cache2 = /* @__PURE__ */ new Map();
1147
+ var inflight2 = /* @__PURE__ */ new Map();
1148
+ function cacheKey(apiBaseUrl, counterpartyId) {
1149
+ return `${apiBaseUrl}|${counterpartyId}`;
1150
+ }
1151
+ function degradeToObserve(config) {
1152
+ if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
1153
+ return config;
1154
+ }
1155
+ return {
1156
+ ...config,
1157
+ mode: "observe",
1158
+ pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
1159
+ };
1160
+ }
1161
+ async function revalidate(config, counterpartyId) {
1162
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1163
+ const existing = inflight2.get(key);
1164
+ if (existing) return existing;
1165
+ const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
1166
+ if (result === null) return;
1167
+ const now = Date.now();
1168
+ if ("notModified" in result) {
1169
+ const entry = cache2.get(key);
1170
+ if (entry) entry.lastSuccessAt = now;
1171
+ return;
1172
+ }
1173
+ cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
1174
+ }).finally(() => {
1175
+ inflight2.delete(key);
1176
+ });
1177
+ inflight2.set(key, promise);
1178
+ return promise;
1179
+ }
1180
+ async function getEdgeConfig(config, counterpartyId, opts) {
1181
+ const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
1182
+ const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
1183
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1184
+ const entry = cache2.get(key);
1185
+ if (entry) {
1186
+ const age = Date.now() - entry.lastSuccessAt;
1187
+ if (age > ttlMs) {
1188
+ revalidate(config, counterpartyId).catch(() => {
1189
+ });
1190
+ }
1191
+ return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
1192
+ }
1193
+ await revalidate(config, counterpartyId).catch(() => {
1194
+ });
1195
+ return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
1196
+ }
1197
+
1198
+ // src/platform-signatures.ts
1199
+ var PLATFORM_AGENT_SIGNATURES = [
1200
+ {
1201
+ vendor: "claude",
1202
+ displayName: "Anthropic Claude",
1203
+ uaPatterns: [/claude/i, /anthropic/i],
1204
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
1205
+ apiKeyFormatPatterns: [/^sk-ant/i],
1206
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
1207
+ },
1208
+ {
1209
+ vendor: "chatgpt",
1210
+ displayName: "OpenAI ChatGPT",
1211
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
1212
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
1213
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
1214
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
1215
+ },
1216
+ {
1217
+ vendor: "gemini",
1218
+ displayName: "Google Gemini",
1219
+ uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
1220
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
1221
+ apiKeyFormatPatterns: [/^aiza/i],
1222
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
1223
+ },
1224
+ {
1225
+ vendor: "cursor",
1226
+ displayName: "Cursor",
1227
+ uaPatterns: [/cursor/i],
1228
+ agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
1229
+ },
1230
+ {
1231
+ vendor: "goose",
1232
+ displayName: "Block Goose",
1233
+ uaPatterns: [/goose/i, /block-goose/i],
1234
+ agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
1235
+ },
1236
+ {
1237
+ vendor: "perplexity",
1238
+ displayName: "Perplexity",
1239
+ uaPatterns: [/perplexity/i],
1240
+ agentCardPatterns: [/perplexity\.ai/i],
1241
+ apiKeyFormatPatterns: [/^pplx-/i]
1242
+ }
1243
+ ];
1244
+ function matchPlatformSignature(signatures, input) {
1245
+ const ua = input.userAgent ?? "";
1246
+ const cardUrl = input.agentCardUrl ?? "";
1247
+ const apiKeyFormat = input.apiKeyFormat ?? "";
1248
+ const headerLines = [];
1249
+ if (input.platformHeaders) {
1250
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
1251
+ headerLines.push(name, `${name}: ${value}`);
1252
+ }
1253
+ }
1254
+ for (const sig of signatures) {
1255
+ const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
1256
+ const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1257
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1258
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1259
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
1260
+ const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1261
+ const cardHost = (() => {
1262
+ try {
1263
+ return cardUrl ? new URL(cardUrl).host : "";
1264
+ } catch {
1265
+ return "";
1266
+ }
1267
+ })();
1268
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1269
+ return {
1270
+ vendor: sig.vendor,
1271
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
1272
+ displayName: sig.displayName
1273
+ };
1274
+ }
1275
+ }
1276
+ return void 0;
1277
+ }
1278
+ function detectPlatformFingerprint(input) {
1279
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
1280
+ }
1281
+
1282
+ // src/edge-core/classify.ts
1283
+ var STATIC_EXTENSIONS = /\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i;
1284
+ var BOT_UA_PATTERN = /curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i;
1285
+ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
1286
+ var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
1287
+
1288
+ // src/edge-core/events.ts
1289
+ function sampled(rate, random = Math.random) {
1290
+ if (rate <= 0) return false;
1291
+ if (rate >= 1) return true;
1292
+ return random() < rate;
1293
+ }
1294
+
1295
+ // src/adapters/anonymous-beacon.ts
1296
+ function readHeader(raw, name) {
1297
+ const value = raw[name];
1298
+ if (typeof value === "string") return value;
1299
+ if (Array.isArray(value)) return value[0];
1300
+ return void 0;
1301
+ }
1302
+ function classifyPassThroughTraffic(path, rawHeaders) {
1303
+ if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
1304
+ const credentials = extractCredentials(rawHeaders, {});
1305
+ if (credentials.astraId || credentials.apiKey || credentials.jwt) {
1306
+ return { kind: "never" };
1307
+ }
1308
+ const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
1309
+ const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1310
+ const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
1311
+ if (fingerprint) {
1312
+ return {
1313
+ kind: "platform",
1314
+ protocol: "platform-ua",
1315
+ evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
1316
+ };
1317
+ }
1318
+ if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
1319
+ return {
1320
+ kind: "anonymous-bot",
1321
+ protocol: "ua-heuristic",
1322
+ evidence: { userAgent: userAgent || "(none)" }
1323
+ };
1324
+ }
1325
+ if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
1326
+ return {
1327
+ kind: "anonymous-bot",
1328
+ protocol: "ua-heuristic",
1329
+ evidence: { userAgent, spoofedBrowser: true }
1330
+ };
1331
+ }
1332
+ return { kind: "never" };
1333
+ }
1334
+ function createAnonymousBeacon(config, options) {
1335
+ const counterpartyId = config.counterpartyId;
1336
+ if (!counterpartyId) return () => {
1337
+ };
1338
+ const random = options?.random ?? Math.random;
1339
+ let edgeConfig;
1340
+ let refreshing = false;
1341
+ const refreshConfig = () => {
1342
+ if (refreshing) return;
1343
+ refreshing = true;
1344
+ getEdgeConfig(config, counterpartyId).then((cfg) => {
1345
+ edgeConfig = cfg;
1346
+ }).catch(() => {
1347
+ }).finally(() => {
1348
+ refreshing = false;
1349
+ });
1350
+ };
1351
+ refreshConfig();
1352
+ return (input) => {
1353
+ try {
1354
+ refreshConfig();
1355
+ const cfg = edgeConfig;
1356
+ if (!cfg) return;
1357
+ const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
1358
+ if (traffic.kind === "never") return;
1359
+ if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
1360
+ return;
1361
+ }
1362
+ const userAgent = readHeader(input.rawHeaders, "user-agent");
1363
+ const xff = input.rawHeaders["x-forwarded-for"];
1364
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1365
+ const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
1366
+ reportUnregisteredAttempt(config, {
1367
+ ...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
1368
+ counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
1369
+ ...sourceIp && { sourceIp },
1370
+ ...userAgent && { userAgent },
1371
+ requestPath: input.requestPath,
1372
+ requestMethod: input.requestMethod,
1373
+ ...traffic.protocol && { protocol: traffic.protocol },
1374
+ ...traffic.evidence && { evidence: traffic.evidence },
1375
+ observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
1376
+ }).catch(() => {
1377
+ });
1378
+ } catch {
1379
+ }
1380
+ };
1381
+ }
950
1382
 
951
1383
  // src/adapters/express.ts
952
1384
  function defaultExtractCredentials(req) {
@@ -958,7 +1390,7 @@ function defaultExtractCredentials(req) {
958
1390
  function extractAstraSyncCredentials(req) {
959
1391
  return extractHttpCredentials(req.headers);
960
1392
  }
961
- function headerValue(value) {
1393
+ function headerValue2(value) {
962
1394
  if (typeof value === "string") return value;
963
1395
  if (Array.isArray(value)) return value[0];
964
1396
  return void 0;
@@ -966,9 +1398,9 @@ function headerValue(value) {
966
1398
  function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
967
1399
  return resolveHttpPdlss({
968
1400
  method: req.method,
969
- astraPurpose: headerValue(req.headers["x-astra-purpose"]),
970
- astraAction: headerValue(req.headers["x-astra-action"]),
971
- legacyPurpose: headerValue(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
1401
+ astraPurpose: headerValue2(req.headers["x-astra-purpose"]),
1402
+ astraAction: headerValue2(req.headers["x-astra-action"]),
1403
+ legacyPurpose: headerValue2(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
972
1404
  queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
973
1405
  routePurpose: routeConfig?.purpose,
974
1406
  routeAction: routeConfig?.action,
@@ -1016,6 +1448,20 @@ function dedupeFailures(result) {
1016
1448
  }
1017
1449
  }
1018
1450
  function defaultOnDenied(result, _req, res) {
1451
+ if (result.misconfigured) {
1452
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1453
+ res.status(503).json({
1454
+ success: false,
1455
+ error: {
1456
+ code: "MERCHANT_VERIFICATION_MISCONFIGURED",
1457
+ message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
1458
+ guidance: result.guidance,
1459
+ failures: result.failures,
1460
+ correlationId: result.correlationId
1461
+ }
1462
+ });
1463
+ return;
1464
+ }
1019
1465
  const statusCode = !result.identityVerified ? 401 : 403;
1020
1466
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
1021
1467
  res.status(statusCode).json({
@@ -1037,6 +1483,11 @@ function defaultOnDenied(result, _req, res) {
1037
1483
  }
1038
1484
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
1039
1485
  function createMiddleware(options) {
1486
+ if ("routes" in options) {
1487
+ console.warn(
1488
+ "[VerificationGateway] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
1489
+ );
1490
+ }
1040
1491
  const {
1041
1492
  extractCredentials: customExtractCredentials,
1042
1493
  extractPurpose: customExtractPurpose,
@@ -1048,12 +1499,25 @@ function createMiddleware(options) {
1048
1499
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
1049
1500
  failOnError = "open",
1050
1501
  caseInsensitiveRouteMatch = false,
1502
+ anonymousBeacon = true,
1051
1503
  ...config
1052
1504
  } = options;
1053
1505
  if (config.apiBaseUrl) {
1054
1506
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
1055
1507
  });
1056
1508
  }
1509
+ const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "api" }) : void 0;
1510
+ const beaconPassThrough = emitAnonymousBeacon ? (req) => {
1511
+ const xff = req.headers["x-forwarded-for"];
1512
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1513
+ emitAnonymousBeacon({
1514
+ requestPath: req.path,
1515
+ requestMethod: req.method,
1516
+ counterpartyUrl: config.counterpartyUrl || `${req.protocol}://${req.get("host")}`,
1517
+ sourceIp: req.ip ?? (xffStr ? xffStr.split(",")[0].trim() : void 0),
1518
+ rawHeaders: req.headers
1519
+ });
1520
+ } : void 0;
1057
1521
  let cachedRoutes = [];
1058
1522
  let lastFetchAt = 0;
1059
1523
  let refreshing = null;
@@ -1114,6 +1578,7 @@ function createMiddleware(options) {
1114
1578
  cachedRoutes.length === 0 ? "no-policy" : "no-match"
1115
1579
  );
1116
1580
  }
1581
+ beaconPassThrough?.(req);
1117
1582
  return next();
1118
1583
  }
1119
1584
  const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
@@ -1124,6 +1589,7 @@ function createMiddleware(options) {
1124
1589
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
1125
1590
  res.setHeader("X-Astra-Gateway-Reason", "route-none");
1126
1591
  }
1592
+ beaconPassThrough?.(req);
1127
1593
  return next();
1128
1594
  }
1129
1595
  const pdlssPair = resolveRequestPdlss(
@@ -1216,16 +1682,10 @@ function createMiddleware(options) {
1216
1682
  forwardedFor: forwardedForStr,
1217
1683
  agentCardUrl,
1218
1684
  // Workstream D — capture EVERY inbound header (SDK-only merchants
1219
- // have no edge to do it upstream). Local string work, secrets
1220
- // stripped; out of the verify cache key so it doesn't defeat caching.
1221
- observedMetadata: (() => {
1222
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
1223
- return {
1224
- headers,
1225
- ...apiKeyFormat && { apiKeyFormat },
1226
- ...platformHeaders && { platformHeaders }
1227
- };
1228
- })()
1685
+ // have no edge to do it upstream) plus the CDN-derived connection
1686
+ // block. Local string work, secrets stripped; out of the verify
1687
+ // cache key so it doesn't defeat caching.
1688
+ observedMetadata: buildSdkObservedMetadata(req.headers)
1229
1689
  }
1230
1690
  };
1231
1691
  let result = await verify(config, verificationRequest);