@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
@@ -92,7 +92,7 @@ function getTrustLevel(score) {
92
92
  }
93
93
 
94
94
  // src/version.ts
95
- var SDK_VERSION = "3.12.0";
95
+ var SDK_VERSION = "4.1.0";
96
96
 
97
97
  // src/well-known.ts
98
98
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -170,7 +170,7 @@ var DEFAULT_CONFIG = {
170
170
  };
171
171
  var initCheckPerformed = false;
172
172
  var deprecationWarningShown = false;
173
- async function performInitCheck(apiBaseUrl, debug, strictInit) {
173
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
174
174
  initCheckPerformed = true;
175
175
  try {
176
176
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -187,6 +187,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
187
187
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
188
188
  );
189
189
  }
190
+ if (apiKey) {
191
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
192
+ headers: { Authorization: `Bearer ${apiKey}` }
193
+ });
194
+ if (selfCheck.status === 401) {
195
+ const body = await selfCheck.json().catch(() => ({}));
196
+ 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}` : "") + `.`;
197
+ if (strictInit) {
198
+ throw new Error(`${message} (strictInit=true)`);
199
+ }
200
+ console.error(message);
201
+ } else if (selfCheck.ok) {
202
+ const body = await selfCheck.json().catch(() => ({}));
203
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
204
+ console.warn(
205
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
206
+ );
207
+ } else if (debug) {
208
+ console.log("[VerificationGateway] API key self-check passed");
209
+ }
210
+ }
211
+ }
190
212
  } catch (err) {
191
213
  if (strictInit) {
192
214
  throw err;
@@ -298,8 +320,17 @@ function extractCredentials(headers, query) {
298
320
  function createGuidanceResponse(_config, reason, options = {}) {
299
321
  const source = options.source ?? "no_credentials";
300
322
  const isApiError = source === "api_error";
323
+ const isMisconfig = source === "merchant_misconfig";
301
324
  const urls = options.urls;
302
- const guidance = isApiError ? {
325
+ const guidance = isMisconfig ? {
326
+ 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.",
327
+ registrationUrl: urls?.registrationUrl ?? "",
328
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
329
+ steps: [
330
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
331
+ "Do not retry: the failure is deterministic until the operator updates their key"
332
+ ]
333
+ } : isApiError ? {
303
334
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
304
335
  registrationUrl: urls?.registrationUrl ?? "",
305
336
  documentationUrl: urls?.documentationUrl ?? "",
@@ -337,13 +368,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
337
368
  // partners (and their custom onDenied handlers) can branch on
338
369
  // dimension. Without this, the synthesised stub was indistinguishable
339
370
  // from a real policy deny.
340
- failures: isApiError ? [
371
+ failures: isMisconfig ? [
372
+ {
373
+ dimension: "verify_access.merchant_misconfigured",
374
+ message: reason ?? "The integration API key was rejected by AstraSync",
375
+ guidance: guidance.message
376
+ }
377
+ ] : isApiError ? [
341
378
  {
342
379
  dimension: "verify_access.api_error",
343
380
  message: reason ?? "Verification temporarily unavailable",
344
381
  guidance: guidance.message
345
382
  }
346
383
  ] : void 0,
384
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
385
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
386
+ ...isMisconfig && { misconfigured: true },
347
387
  correlationId: options.correlationId,
348
388
  attemptId: options.attemptId,
349
389
  verifiedAt: /* @__PURE__ */ new Date()
@@ -398,7 +438,8 @@ async function callVerifyAccessAPI(config, request) {
398
438
  const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
399
439
  method: "POST",
400
440
  headers,
401
- body: JSON.stringify(body)
441
+ body: JSON.stringify(body),
442
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
402
443
  });
403
444
  const data = await response.json();
404
445
  if (response.status === 410) {
@@ -422,6 +463,17 @@ async function callVerifyAccessAPI(config, request) {
422
463
  };
423
464
  }
424
465
  if (!response.ok) {
466
+ if (response.status === 401) {
467
+ return {
468
+ success: false,
469
+ error: data.message || data.error || "Integration API key rejected",
470
+ misconfig: true,
471
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
472
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
473
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
474
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
475
+ };
476
+ }
425
477
  return {
426
478
  success: false,
427
479
  error: data.message || data.error || `API returned ${response.status}`,
@@ -443,9 +495,19 @@ async function verify(config, request, options) {
443
495
  const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
444
496
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
445
497
  if (mergedConfig.strictInit) {
446
- await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
498
+ await performInitCheck(
499
+ mergedConfig.apiBaseUrl,
500
+ mergedConfig.debug,
501
+ true,
502
+ mergedConfig.apiKey
503
+ );
447
504
  } else {
448
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
505
+ void performInitCheck(
506
+ mergedConfig.apiBaseUrl,
507
+ mergedConfig.debug,
508
+ false,
509
+ mergedConfig.apiKey
510
+ );
449
511
  }
450
512
  }
451
513
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
@@ -484,9 +546,10 @@ async function verify(config, request, options) {
484
546
  const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
485
547
  if (!apiResponse.success) {
486
548
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
487
- source: "api_error",
549
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
488
550
  correlationId: apiResponse.correlationId,
489
551
  attemptId: apiResponse.attemptId,
552
+ remediation: apiResponse.remediation,
490
553
  urls
491
554
  });
492
555
  }
@@ -641,6 +704,21 @@ async function fetchRoutes(config, counterpartyId) {
641
704
  return null;
642
705
  }
643
706
  }
707
+ async function reportUnregisteredAttempt(config, data) {
708
+ const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
709
+ const { counterpartyUrl, ...rest } = data;
710
+ const body = {
711
+ ...rest,
712
+ ...counterpartyUrl && { counterpartyUrl },
713
+ ...config.counterpartyId && { counterpartyId: config.counterpartyId }
714
+ };
715
+ await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
716
+ method: "POST",
717
+ headers: { "Content-Type": "application/json" },
718
+ body: JSON.stringify(body)
719
+ }).catch(() => {
720
+ });
721
+ }
644
722
  async function reportCounterpartyPreCheckFailure(config, data) {
645
723
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
646
724
  await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
@@ -838,6 +916,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
838
916
  }
839
917
 
840
918
  // src/metadata-capture.ts
919
+ var CAPTURE_SCHEMA_VERSION = 1;
841
920
  var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
842
921
  "cookie",
843
922
  "set-cookie",
@@ -974,6 +1053,359 @@ function sanitizeHeaders(raw) {
974
1053
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
975
1054
  return result;
976
1055
  }
1056
+ var CONNECTION_HEADER_SOURCES = [
1057
+ ["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
1058
+ [
1059
+ "country",
1060
+ ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
1061
+ ],
1062
+ ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
1063
+ ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
1064
+ ["asn", ["cloudfront-viewer-asn"]],
1065
+ ["tlsVersion", ["cloudfront-viewer-tls"]],
1066
+ ["httpVersion", ["cloudfront-viewer-http-version"]],
1067
+ // TLS fingerprints can only be computed where TLS terminates. When a CDN
1068
+ // terminates TLS in front of the merchant, its fingerprint headers ARE the
1069
+ // connection-layer truth — normalising them here is the permanent design.
1070
+ ["tlsFingerprint", ["cf-ja3-hash"]],
1071
+ ["ja4", ["cf-ja4"]]
1072
+ ];
1073
+ function deriveConnectionFromHeaders(headers) {
1074
+ if (!headers) return void 0;
1075
+ const lower = {};
1076
+ for (const name of Object.keys(headers)) {
1077
+ const value = headers[name];
1078
+ if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
1079
+ }
1080
+ const connection = {};
1081
+ for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
1082
+ for (const source of sources) {
1083
+ const value = lower[source]?.trim();
1084
+ if (value) {
1085
+ connection[key] = value;
1086
+ break;
1087
+ }
1088
+ }
1089
+ }
1090
+ if (!connection.ip) {
1091
+ const xff = lower["x-forwarded-for"];
1092
+ const client = xff?.split(",")[0]?.trim();
1093
+ if (client) connection.ip = client;
1094
+ }
1095
+ return Object.keys(connection).length > 0 ? connection : void 0;
1096
+ }
1097
+ function buildSdkObservedMetadata(rawHeaders) {
1098
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1099
+ const flat = {};
1100
+ if (rawHeaders) {
1101
+ for (const name of Object.keys(rawHeaders)) {
1102
+ const value = rawHeaders[name];
1103
+ if (value === void 0) continue;
1104
+ flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
1105
+ }
1106
+ }
1107
+ const connection = deriveConnectionFromHeaders(flat);
1108
+ return {
1109
+ headers,
1110
+ ...apiKeyFormat && { apiKeyFormat },
1111
+ ...platformHeaders && { platformHeaders },
1112
+ ...connection && { connection },
1113
+ schemaVersion: CAPTURE_SCHEMA_VERSION
1114
+ };
1115
+ }
1116
+
1117
+ // src/edge-config.ts
1118
+ var DEFAULT_EDGE_CONFIG = Object.freeze({
1119
+ version: 1,
1120
+ mode: "observe",
1121
+ depth: "classify",
1122
+ pathRules: [],
1123
+ sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
1124
+ failurePosture: "open"
1125
+ });
1126
+ var EDGE_MODES = ["observe", "enforce"];
1127
+ var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
1128
+ function isEdgeConfig(value) {
1129
+ if (typeof value !== "object" || value === null) return false;
1130
+ const v = value;
1131
+ if (v.version !== 1) return false;
1132
+ if (!EDGE_MODES.includes(v.mode)) return false;
1133
+ if (!EDGE_DEPTHS.includes(v.depth)) return false;
1134
+ if (!Array.isArray(v.pathRules)) return false;
1135
+ for (const rule of v.pathRules) {
1136
+ if (typeof rule !== "object" || rule === null) return false;
1137
+ const r = rule;
1138
+ if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
1139
+ if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
1140
+ if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
1141
+ }
1142
+ const sampling = v.sampling;
1143
+ if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
1144
+ return false;
1145
+ }
1146
+ return v.failurePosture === "open";
1147
+ }
1148
+ async function fetchEdgeConfig(config, counterpartyId, opts) {
1149
+ if (!counterpartyId) return null;
1150
+ const headers = { Accept: "application/json" };
1151
+ if (config.apiKey) {
1152
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
1153
+ headers["X-API-Key"] = config.apiKey;
1154
+ }
1155
+ if (opts?.etag) headers["If-None-Match"] = opts.etag;
1156
+ try {
1157
+ const response = await fetch(
1158
+ `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1159
+ { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1160
+ );
1161
+ if (response.status === 304) return { notModified: true };
1162
+ if (!response.ok) return null;
1163
+ const body = await response.json();
1164
+ const edgeConfig = body.data?.edgeConfig;
1165
+ if (!isEdgeConfig(edgeConfig)) return null;
1166
+ return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
1167
+ } catch {
1168
+ return null;
1169
+ }
1170
+ }
1171
+ var DEFAULT_TTL_MS = 60 * 1e3;
1172
+ var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
1173
+ var cache2 = /* @__PURE__ */ new Map();
1174
+ var inflight2 = /* @__PURE__ */ new Map();
1175
+ function cacheKey(apiBaseUrl, counterpartyId) {
1176
+ return `${apiBaseUrl}|${counterpartyId}`;
1177
+ }
1178
+ function degradeToObserve(config) {
1179
+ if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
1180
+ return config;
1181
+ }
1182
+ return {
1183
+ ...config,
1184
+ mode: "observe",
1185
+ pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
1186
+ };
1187
+ }
1188
+ async function revalidate(config, counterpartyId) {
1189
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1190
+ const existing = inflight2.get(key);
1191
+ if (existing) return existing;
1192
+ const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
1193
+ if (result === null) return;
1194
+ const now = Date.now();
1195
+ if ("notModified" in result) {
1196
+ const entry = cache2.get(key);
1197
+ if (entry) entry.lastSuccessAt = now;
1198
+ return;
1199
+ }
1200
+ cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
1201
+ }).finally(() => {
1202
+ inflight2.delete(key);
1203
+ });
1204
+ inflight2.set(key, promise);
1205
+ return promise;
1206
+ }
1207
+ async function getEdgeConfig(config, counterpartyId, opts) {
1208
+ const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
1209
+ const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
1210
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1211
+ const entry = cache2.get(key);
1212
+ if (entry) {
1213
+ const age = Date.now() - entry.lastSuccessAt;
1214
+ if (age > ttlMs) {
1215
+ revalidate(config, counterpartyId).catch(() => {
1216
+ });
1217
+ }
1218
+ return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
1219
+ }
1220
+ await revalidate(config, counterpartyId).catch(() => {
1221
+ });
1222
+ return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
1223
+ }
1224
+
1225
+ // src/platform-signatures.ts
1226
+ var PLATFORM_AGENT_SIGNATURES = [
1227
+ {
1228
+ vendor: "claude",
1229
+ displayName: "Anthropic Claude",
1230
+ uaPatterns: [/claude/i, /anthropic/i],
1231
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
1232
+ apiKeyFormatPatterns: [/^sk-ant/i],
1233
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
1234
+ },
1235
+ {
1236
+ vendor: "chatgpt",
1237
+ displayName: "OpenAI ChatGPT",
1238
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
1239
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
1240
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
1241
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
1242
+ },
1243
+ {
1244
+ vendor: "gemini",
1245
+ displayName: "Google Gemini",
1246
+ uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
1247
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
1248
+ apiKeyFormatPatterns: [/^aiza/i],
1249
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
1250
+ },
1251
+ {
1252
+ vendor: "cursor",
1253
+ displayName: "Cursor",
1254
+ uaPatterns: [/cursor/i],
1255
+ agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
1256
+ },
1257
+ {
1258
+ vendor: "goose",
1259
+ displayName: "Block Goose",
1260
+ uaPatterns: [/goose/i, /block-goose/i],
1261
+ agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
1262
+ },
1263
+ {
1264
+ vendor: "perplexity",
1265
+ displayName: "Perplexity",
1266
+ uaPatterns: [/perplexity/i],
1267
+ agentCardPatterns: [/perplexity\.ai/i],
1268
+ apiKeyFormatPatterns: [/^pplx-/i]
1269
+ }
1270
+ ];
1271
+ function matchPlatformSignature(signatures, input) {
1272
+ const ua = input.userAgent ?? "";
1273
+ const cardUrl = input.agentCardUrl ?? "";
1274
+ const apiKeyFormat = input.apiKeyFormat ?? "";
1275
+ const headerLines = [];
1276
+ if (input.platformHeaders) {
1277
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
1278
+ headerLines.push(name, `${name}: ${value}`);
1279
+ }
1280
+ }
1281
+ for (const sig of signatures) {
1282
+ const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
1283
+ const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1284
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1285
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1286
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
1287
+ const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1288
+ const cardHost = (() => {
1289
+ try {
1290
+ return cardUrl ? new URL(cardUrl).host : "";
1291
+ } catch {
1292
+ return "";
1293
+ }
1294
+ })();
1295
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1296
+ return {
1297
+ vendor: sig.vendor,
1298
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
1299
+ displayName: sig.displayName
1300
+ };
1301
+ }
1302
+ }
1303
+ return void 0;
1304
+ }
1305
+ function detectPlatformFingerprint(input) {
1306
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
1307
+ }
1308
+
1309
+ // src/edge-core/classify.ts
1310
+ 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;
1311
+ 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;
1312
+ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
1313
+ var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
1314
+
1315
+ // src/edge-core/events.ts
1316
+ function sampled(rate, random = Math.random) {
1317
+ if (rate <= 0) return false;
1318
+ if (rate >= 1) return true;
1319
+ return random() < rate;
1320
+ }
1321
+
1322
+ // src/adapters/anonymous-beacon.ts
1323
+ function readHeader(raw, name) {
1324
+ const value = raw[name];
1325
+ if (typeof value === "string") return value;
1326
+ if (Array.isArray(value)) return value[0];
1327
+ return void 0;
1328
+ }
1329
+ function classifyPassThroughTraffic(path, rawHeaders) {
1330
+ if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
1331
+ const credentials = extractCredentials(rawHeaders, {});
1332
+ if (credentials.astraId || credentials.apiKey || credentials.jwt) {
1333
+ return { kind: "never" };
1334
+ }
1335
+ const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
1336
+ const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1337
+ const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
1338
+ if (fingerprint) {
1339
+ return {
1340
+ kind: "platform",
1341
+ protocol: "platform-ua",
1342
+ evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
1343
+ };
1344
+ }
1345
+ if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
1346
+ return {
1347
+ kind: "anonymous-bot",
1348
+ protocol: "ua-heuristic",
1349
+ evidence: { userAgent: userAgent || "(none)" }
1350
+ };
1351
+ }
1352
+ if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
1353
+ return {
1354
+ kind: "anonymous-bot",
1355
+ protocol: "ua-heuristic",
1356
+ evidence: { userAgent, spoofedBrowser: true }
1357
+ };
1358
+ }
1359
+ return { kind: "never" };
1360
+ }
1361
+ function createAnonymousBeacon(config, options) {
1362
+ const counterpartyId = config.counterpartyId;
1363
+ if (!counterpartyId) return () => {
1364
+ };
1365
+ const random = options?.random ?? Math.random;
1366
+ let edgeConfig;
1367
+ let refreshing = false;
1368
+ const refreshConfig = () => {
1369
+ if (refreshing) return;
1370
+ refreshing = true;
1371
+ getEdgeConfig(config, counterpartyId).then((cfg) => {
1372
+ edgeConfig = cfg;
1373
+ }).catch(() => {
1374
+ }).finally(() => {
1375
+ refreshing = false;
1376
+ });
1377
+ };
1378
+ refreshConfig();
1379
+ return (input) => {
1380
+ try {
1381
+ refreshConfig();
1382
+ const cfg = edgeConfig;
1383
+ if (!cfg) return;
1384
+ const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
1385
+ if (traffic.kind === "never") return;
1386
+ if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
1387
+ return;
1388
+ }
1389
+ const userAgent = readHeader(input.rawHeaders, "user-agent");
1390
+ const xff = input.rawHeaders["x-forwarded-for"];
1391
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1392
+ const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
1393
+ reportUnregisteredAttempt(config, {
1394
+ ...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
1395
+ counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
1396
+ ...sourceIp && { sourceIp },
1397
+ ...userAgent && { userAgent },
1398
+ requestPath: input.requestPath,
1399
+ requestMethod: input.requestMethod,
1400
+ ...traffic.protocol && { protocol: traffic.protocol },
1401
+ ...traffic.evidence && { evidence: traffic.evidence },
1402
+ observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
1403
+ }).catch(() => {
1404
+ });
1405
+ } catch {
1406
+ }
1407
+ };
1408
+ }
977
1409
 
978
1410
  // src/adapters/express.ts
979
1411
  function defaultExtractCredentials(req) {
@@ -985,7 +1417,7 @@ function defaultExtractCredentials(req) {
985
1417
  function extractAstraSyncCredentials(req) {
986
1418
  return extractHttpCredentials(req.headers);
987
1419
  }
988
- function headerValue(value) {
1420
+ function headerValue2(value) {
989
1421
  if (typeof value === "string") return value;
990
1422
  if (Array.isArray(value)) return value[0];
991
1423
  return void 0;
@@ -993,9 +1425,9 @@ function headerValue(value) {
993
1425
  function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
994
1426
  return resolveHttpPdlss({
995
1427
  method: req.method,
996
- astraPurpose: headerValue(req.headers["x-astra-purpose"]),
997
- astraAction: headerValue(req.headers["x-astra-action"]),
998
- legacyPurpose: headerValue(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
1428
+ astraPurpose: headerValue2(req.headers["x-astra-purpose"]),
1429
+ astraAction: headerValue2(req.headers["x-astra-action"]),
1430
+ legacyPurpose: headerValue2(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
999
1431
  queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
1000
1432
  routePurpose: routeConfig?.purpose,
1001
1433
  routeAction: routeConfig?.action,
@@ -1043,6 +1475,20 @@ function dedupeFailures(result) {
1043
1475
  }
1044
1476
  }
1045
1477
  function defaultOnDenied(result, _req, res) {
1478
+ if (result.misconfigured) {
1479
+ res.setHeader("X-Astra-Gateway-Mode", "enforced");
1480
+ res.status(503).json({
1481
+ success: false,
1482
+ error: {
1483
+ code: "MERCHANT_VERIFICATION_MISCONFIGURED",
1484
+ message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
1485
+ guidance: result.guidance,
1486
+ failures: result.failures,
1487
+ correlationId: result.correlationId
1488
+ }
1489
+ });
1490
+ return;
1491
+ }
1046
1492
  const statusCode = !result.identityVerified ? 401 : 403;
1047
1493
  res.setHeader("X-Astra-Gateway-Mode", "enforced");
1048
1494
  res.status(statusCode).json({
@@ -1064,6 +1510,11 @@ function defaultOnDenied(result, _req, res) {
1064
1510
  }
1065
1511
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
1066
1512
  function createMiddleware(options) {
1513
+ if ("routes" in options) {
1514
+ console.warn(
1515
+ "[VerificationGateway] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
1516
+ );
1517
+ }
1067
1518
  const {
1068
1519
  extractCredentials: customExtractCredentials,
1069
1520
  extractPurpose: customExtractPurpose,
@@ -1075,12 +1526,25 @@ function createMiddleware(options) {
1075
1526
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
1076
1527
  failOnError = "open",
1077
1528
  caseInsensitiveRouteMatch = false,
1529
+ anonymousBeacon = true,
1078
1530
  ...config
1079
1531
  } = options;
1080
1532
  if (config.apiBaseUrl) {
1081
1533
  prefetchWellKnown(config.apiBaseUrl).catch(() => {
1082
1534
  });
1083
1535
  }
1536
+ const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "api" }) : void 0;
1537
+ const beaconPassThrough = emitAnonymousBeacon ? (req) => {
1538
+ const xff = req.headers["x-forwarded-for"];
1539
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1540
+ emitAnonymousBeacon({
1541
+ requestPath: req.path,
1542
+ requestMethod: req.method,
1543
+ counterpartyUrl: config.counterpartyUrl || `${req.protocol}://${req.get("host")}`,
1544
+ sourceIp: req.ip ?? (xffStr ? xffStr.split(",")[0].trim() : void 0),
1545
+ rawHeaders: req.headers
1546
+ });
1547
+ } : void 0;
1084
1548
  let cachedRoutes = [];
1085
1549
  let lastFetchAt = 0;
1086
1550
  let refreshing = null;
@@ -1141,6 +1605,7 @@ function createMiddleware(options) {
1141
1605
  cachedRoutes.length === 0 ? "no-policy" : "no-match"
1142
1606
  );
1143
1607
  }
1608
+ beaconPassThrough?.(req);
1144
1609
  return next();
1145
1610
  }
1146
1611
  const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
@@ -1151,6 +1616,7 @@ function createMiddleware(options) {
1151
1616
  res.setHeader("X-Astra-Gateway-Mode", "unenforced");
1152
1617
  res.setHeader("X-Astra-Gateway-Reason", "route-none");
1153
1618
  }
1619
+ beaconPassThrough?.(req);
1154
1620
  return next();
1155
1621
  }
1156
1622
  const pdlssPair = resolveRequestPdlss(
@@ -1243,16 +1709,10 @@ function createMiddleware(options) {
1243
1709
  forwardedFor: forwardedForStr,
1244
1710
  agentCardUrl,
1245
1711
  // Workstream D — capture EVERY inbound header (SDK-only merchants
1246
- // have no edge to do it upstream). Local string work, secrets
1247
- // stripped; out of the verify cache key so it doesn't defeat caching.
1248
- observedMetadata: (() => {
1249
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
1250
- return {
1251
- headers,
1252
- ...apiKeyFormat && { apiKeyFormat },
1253
- ...platformHeaders && { platformHeaders }
1254
- };
1255
- })()
1712
+ // have no edge to do it upstream) plus the CDN-derived connection
1713
+ // block. Local string work, secrets stripped; out of the verify
1714
+ // cache key so it doesn't defeat caching.
1715
+ observedMetadata: buildSdkObservedMetadata(req.headers)
1256
1716
  }
1257
1717
  };
1258
1718
  let result = await verify(config, verificationRequest);