@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
@@ -102,7 +102,7 @@ function getTrustLevel(score) {
102
102
  }
103
103
 
104
104
  // src/version.ts
105
- var SDK_VERSION = "3.12.0";
105
+ var SDK_VERSION = "4.1.0";
106
106
 
107
107
  // src/well-known.ts
108
108
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -128,7 +128,7 @@ var DEFAULT_CONFIG = {
128
128
  };
129
129
  var initCheckPerformed = false;
130
130
  var deprecationWarningShown = false;
131
- async function performInitCheck(apiBaseUrl, debug, strictInit) {
131
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
132
132
  initCheckPerformed = true;
133
133
  try {
134
134
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -145,6 +145,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
145
145
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
146
146
  );
147
147
  }
148
+ if (apiKey) {
149
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
150
+ headers: { Authorization: `Bearer ${apiKey}` }
151
+ });
152
+ if (selfCheck.status === 401) {
153
+ const body = await selfCheck.json().catch(() => ({}));
154
+ 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}` : "") + `.`;
155
+ if (strictInit) {
156
+ throw new Error(`${message} (strictInit=true)`);
157
+ }
158
+ console.error(message);
159
+ } else if (selfCheck.ok) {
160
+ const body = await selfCheck.json().catch(() => ({}));
161
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
162
+ console.warn(
163
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
164
+ );
165
+ } else if (debug) {
166
+ console.log("[VerificationGateway] API key self-check passed");
167
+ }
168
+ }
169
+ }
148
170
  } catch (err) {
149
171
  if (strictInit) {
150
172
  throw err;
@@ -219,11 +241,54 @@ function cacheResult(request, result, configuredTtl, counterpartyId) {
219
241
  expiresAt: Date.now() + ttlSeconds * 1e3
220
242
  });
221
243
  }
244
+ function extractCredentials(headers, query) {
245
+ const credentials = {};
246
+ const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
247
+ const astraIdHeader = headers["x-astra-id"] || headers["X-Astra-Id"] || headers["X-ASTRA-ID"] || headers["x-astra-agentid"] || headers["X-Astra-AgentId"] || headers["x-astra-agent-id"] || headers["X-Astra-Agent-Id"] || headers["X-ASTRA-AGENT-ID"];
248
+ if (astraIdHeader) {
249
+ const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
250
+ if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
251
+ credentials.astraId = raw;
252
+ }
253
+ }
254
+ const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
255
+ if (apiKeyHeader) {
256
+ credentials.apiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;
257
+ }
258
+ const authHeader = headers["authorization"] || headers["Authorization"];
259
+ if (authHeader) {
260
+ const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
261
+ if (typeof authValue === "string") {
262
+ credentials.authorizationHeader = authValue;
263
+ if (authValue.startsWith("Bearer ")) {
264
+ credentials.jwt = authValue.slice(7);
265
+ }
266
+ }
267
+ }
268
+ if (query) {
269
+ if (query.astraId && !credentials.astraId) {
270
+ credentials.astraId = query.astraId;
271
+ }
272
+ if (query.apiKey && !credentials.apiKey) {
273
+ credentials.apiKey = query.apiKey;
274
+ }
275
+ }
276
+ return credentials;
277
+ }
222
278
  function createGuidanceResponse(_config, reason, options = {}) {
223
279
  const source = options.source ?? "no_credentials";
224
280
  const isApiError = source === "api_error";
281
+ const isMisconfig = source === "merchant_misconfig";
225
282
  const urls = options.urls;
226
- const guidance = isApiError ? {
283
+ const guidance = isMisconfig ? {
284
+ 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.",
285
+ registrationUrl: urls?.registrationUrl ?? "",
286
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
287
+ steps: [
288
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
289
+ "Do not retry: the failure is deterministic until the operator updates their key"
290
+ ]
291
+ } : isApiError ? {
227
292
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
228
293
  registrationUrl: urls?.registrationUrl ?? "",
229
294
  documentationUrl: urls?.documentationUrl ?? "",
@@ -261,13 +326,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
261
326
  // partners (and their custom onDenied handlers) can branch on
262
327
  // dimension. Without this, the synthesised stub was indistinguishable
263
328
  // from a real policy deny.
264
- failures: isApiError ? [
329
+ failures: isMisconfig ? [
330
+ {
331
+ dimension: "verify_access.merchant_misconfigured",
332
+ message: reason ?? "The integration API key was rejected by AstraSync",
333
+ guidance: guidance.message
334
+ }
335
+ ] : isApiError ? [
265
336
  {
266
337
  dimension: "verify_access.api_error",
267
338
  message: reason ?? "Verification temporarily unavailable",
268
339
  guidance: guidance.message
269
340
  }
270
341
  ] : void 0,
342
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
343
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
344
+ ...isMisconfig && { misconfigured: true },
271
345
  correlationId: options.correlationId,
272
346
  attemptId: options.attemptId,
273
347
  verifiedAt: /* @__PURE__ */ new Date()
@@ -322,7 +396,8 @@ async function callVerifyAccessAPI(config, request) {
322
396
  const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
323
397
  method: "POST",
324
398
  headers,
325
- body: JSON.stringify(body)
399
+ body: JSON.stringify(body),
400
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
326
401
  });
327
402
  const data = await response.json();
328
403
  if (response.status === 410) {
@@ -346,6 +421,17 @@ async function callVerifyAccessAPI(config, request) {
346
421
  };
347
422
  }
348
423
  if (!response.ok) {
424
+ if (response.status === 401) {
425
+ return {
426
+ success: false,
427
+ error: data.message || data.error || "Integration API key rejected",
428
+ misconfig: true,
429
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
430
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
431
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
432
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
433
+ };
434
+ }
349
435
  return {
350
436
  success: false,
351
437
  error: data.message || data.error || `API returned ${response.status}`,
@@ -367,9 +453,19 @@ async function verify(config, request, options) {
367
453
  const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
368
454
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
369
455
  if (mergedConfig.strictInit) {
370
- await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
456
+ await performInitCheck(
457
+ mergedConfig.apiBaseUrl,
458
+ mergedConfig.debug,
459
+ true,
460
+ mergedConfig.apiKey
461
+ );
371
462
  } else {
372
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
463
+ void performInitCheck(
464
+ mergedConfig.apiBaseUrl,
465
+ mergedConfig.debug,
466
+ false,
467
+ mergedConfig.apiKey
468
+ );
373
469
  }
374
470
  }
375
471
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
@@ -408,9 +504,10 @@ async function verify(config, request, options) {
408
504
  const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
409
505
  if (!apiResponse.success) {
410
506
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
411
- source: "api_error",
507
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
412
508
  correlationId: apiResponse.correlationId,
413
509
  attemptId: apiResponse.attemptId,
510
+ remediation: apiResponse.remediation,
414
511
  urls
415
512
  });
416
513
  }
@@ -552,6 +649,21 @@ async function fetchRoutes(config, counterpartyId) {
552
649
  return null;
553
650
  }
554
651
  }
652
+ async function reportUnregisteredAttempt(config, data) {
653
+ const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
654
+ const { counterpartyUrl, ...rest } = data;
655
+ const body = {
656
+ ...rest,
657
+ ...counterpartyUrl && { counterpartyUrl },
658
+ ...config.counterpartyId && { counterpartyId: config.counterpartyId }
659
+ };
660
+ await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
661
+ method: "POST",
662
+ headers: { "Content-Type": "application/json" },
663
+ body: JSON.stringify(body)
664
+ }).catch(() => {
665
+ });
666
+ }
555
667
  async function reportCounterpartyPreCheckFailure(config, data) {
556
668
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
557
669
  await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
@@ -657,6 +769,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
657
769
  }
658
770
 
659
771
  // src/metadata-capture.ts
772
+ var CAPTURE_SCHEMA_VERSION = 1;
660
773
  var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
661
774
  "cookie",
662
775
  "set-cookie",
@@ -793,6 +906,359 @@ function sanitizeHeaders(raw) {
793
906
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
794
907
  return result;
795
908
  }
909
+ var CONNECTION_HEADER_SOURCES = [
910
+ ["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
911
+ [
912
+ "country",
913
+ ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
914
+ ],
915
+ ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
916
+ ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
917
+ ["asn", ["cloudfront-viewer-asn"]],
918
+ ["tlsVersion", ["cloudfront-viewer-tls"]],
919
+ ["httpVersion", ["cloudfront-viewer-http-version"]],
920
+ // TLS fingerprints can only be computed where TLS terminates. When a CDN
921
+ // terminates TLS in front of the merchant, its fingerprint headers ARE the
922
+ // connection-layer truth — normalising them here is the permanent design.
923
+ ["tlsFingerprint", ["cf-ja3-hash"]],
924
+ ["ja4", ["cf-ja4"]]
925
+ ];
926
+ function deriveConnectionFromHeaders(headers) {
927
+ if (!headers) return void 0;
928
+ const lower = {};
929
+ for (const name of Object.keys(headers)) {
930
+ const value = headers[name];
931
+ if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
932
+ }
933
+ const connection = {};
934
+ for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
935
+ for (const source of sources) {
936
+ const value = lower[source]?.trim();
937
+ if (value) {
938
+ connection[key] = value;
939
+ break;
940
+ }
941
+ }
942
+ }
943
+ if (!connection.ip) {
944
+ const xff = lower["x-forwarded-for"];
945
+ const client = xff?.split(",")[0]?.trim();
946
+ if (client) connection.ip = client;
947
+ }
948
+ return Object.keys(connection).length > 0 ? connection : void 0;
949
+ }
950
+ function buildSdkObservedMetadata(rawHeaders) {
951
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
952
+ const flat = {};
953
+ if (rawHeaders) {
954
+ for (const name of Object.keys(rawHeaders)) {
955
+ const value = rawHeaders[name];
956
+ if (value === void 0) continue;
957
+ flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
958
+ }
959
+ }
960
+ const connection = deriveConnectionFromHeaders(flat);
961
+ return {
962
+ headers,
963
+ ...apiKeyFormat && { apiKeyFormat },
964
+ ...platformHeaders && { platformHeaders },
965
+ ...connection && { connection },
966
+ schemaVersion: CAPTURE_SCHEMA_VERSION
967
+ };
968
+ }
969
+
970
+ // src/edge-config.ts
971
+ var DEFAULT_EDGE_CONFIG = Object.freeze({
972
+ version: 1,
973
+ mode: "observe",
974
+ depth: "classify",
975
+ pathRules: [],
976
+ sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
977
+ failurePosture: "open"
978
+ });
979
+ var EDGE_MODES = ["observe", "enforce"];
980
+ var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
981
+ function isEdgeConfig(value) {
982
+ if (typeof value !== "object" || value === null) return false;
983
+ const v = value;
984
+ if (v.version !== 1) return false;
985
+ if (!EDGE_MODES.includes(v.mode)) return false;
986
+ if (!EDGE_DEPTHS.includes(v.depth)) return false;
987
+ if (!Array.isArray(v.pathRules)) return false;
988
+ for (const rule of v.pathRules) {
989
+ if (typeof rule !== "object" || rule === null) return false;
990
+ const r = rule;
991
+ if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
992
+ if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
993
+ if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
994
+ }
995
+ const sampling = v.sampling;
996
+ if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
997
+ return false;
998
+ }
999
+ return v.failurePosture === "open";
1000
+ }
1001
+ async function fetchEdgeConfig(config, counterpartyId, opts) {
1002
+ if (!counterpartyId) return null;
1003
+ const headers = { Accept: "application/json" };
1004
+ if (config.apiKey) {
1005
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
1006
+ headers["X-API-Key"] = config.apiKey;
1007
+ }
1008
+ if (opts?.etag) headers["If-None-Match"] = opts.etag;
1009
+ try {
1010
+ const response = await fetch(
1011
+ `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1012
+ { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1013
+ );
1014
+ if (response.status === 304) return { notModified: true };
1015
+ if (!response.ok) return null;
1016
+ const body = await response.json();
1017
+ const edgeConfig = body.data?.edgeConfig;
1018
+ if (!isEdgeConfig(edgeConfig)) return null;
1019
+ return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
1020
+ } catch {
1021
+ return null;
1022
+ }
1023
+ }
1024
+ var DEFAULT_TTL_MS = 60 * 1e3;
1025
+ var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
1026
+ var cache2 = /* @__PURE__ */ new Map();
1027
+ var inflight = /* @__PURE__ */ new Map();
1028
+ function cacheKey(apiBaseUrl, counterpartyId) {
1029
+ return `${apiBaseUrl}|${counterpartyId}`;
1030
+ }
1031
+ function degradeToObserve(config) {
1032
+ if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
1033
+ return config;
1034
+ }
1035
+ return {
1036
+ ...config,
1037
+ mode: "observe",
1038
+ pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
1039
+ };
1040
+ }
1041
+ async function revalidate(config, counterpartyId) {
1042
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1043
+ const existing = inflight.get(key);
1044
+ if (existing) return existing;
1045
+ const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
1046
+ if (result === null) return;
1047
+ const now = Date.now();
1048
+ if ("notModified" in result) {
1049
+ const entry = cache2.get(key);
1050
+ if (entry) entry.lastSuccessAt = now;
1051
+ return;
1052
+ }
1053
+ cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
1054
+ }).finally(() => {
1055
+ inflight.delete(key);
1056
+ });
1057
+ inflight.set(key, promise);
1058
+ return promise;
1059
+ }
1060
+ async function getEdgeConfig(config, counterpartyId, opts) {
1061
+ const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
1062
+ const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
1063
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1064
+ const entry = cache2.get(key);
1065
+ if (entry) {
1066
+ const age = Date.now() - entry.lastSuccessAt;
1067
+ if (age > ttlMs) {
1068
+ revalidate(config, counterpartyId).catch(() => {
1069
+ });
1070
+ }
1071
+ return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
1072
+ }
1073
+ await revalidate(config, counterpartyId).catch(() => {
1074
+ });
1075
+ return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
1076
+ }
1077
+
1078
+ // src/platform-signatures.ts
1079
+ var PLATFORM_AGENT_SIGNATURES = [
1080
+ {
1081
+ vendor: "claude",
1082
+ displayName: "Anthropic Claude",
1083
+ uaPatterns: [/claude/i, /anthropic/i],
1084
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
1085
+ apiKeyFormatPatterns: [/^sk-ant/i],
1086
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
1087
+ },
1088
+ {
1089
+ vendor: "chatgpt",
1090
+ displayName: "OpenAI ChatGPT",
1091
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
1092
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
1093
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
1094
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
1095
+ },
1096
+ {
1097
+ vendor: "gemini",
1098
+ displayName: "Google Gemini",
1099
+ uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
1100
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
1101
+ apiKeyFormatPatterns: [/^aiza/i],
1102
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
1103
+ },
1104
+ {
1105
+ vendor: "cursor",
1106
+ displayName: "Cursor",
1107
+ uaPatterns: [/cursor/i],
1108
+ agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
1109
+ },
1110
+ {
1111
+ vendor: "goose",
1112
+ displayName: "Block Goose",
1113
+ uaPatterns: [/goose/i, /block-goose/i],
1114
+ agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
1115
+ },
1116
+ {
1117
+ vendor: "perplexity",
1118
+ displayName: "Perplexity",
1119
+ uaPatterns: [/perplexity/i],
1120
+ agentCardPatterns: [/perplexity\.ai/i],
1121
+ apiKeyFormatPatterns: [/^pplx-/i]
1122
+ }
1123
+ ];
1124
+ function matchPlatformSignature(signatures, input) {
1125
+ const ua = input.userAgent ?? "";
1126
+ const cardUrl = input.agentCardUrl ?? "";
1127
+ const apiKeyFormat = input.apiKeyFormat ?? "";
1128
+ const headerLines = [];
1129
+ if (input.platformHeaders) {
1130
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
1131
+ headerLines.push(name, `${name}: ${value}`);
1132
+ }
1133
+ }
1134
+ for (const sig of signatures) {
1135
+ const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
1136
+ const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1137
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1138
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1139
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
1140
+ const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1141
+ const cardHost = (() => {
1142
+ try {
1143
+ return cardUrl ? new URL(cardUrl).host : "";
1144
+ } catch {
1145
+ return "";
1146
+ }
1147
+ })();
1148
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1149
+ return {
1150
+ vendor: sig.vendor,
1151
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
1152
+ displayName: sig.displayName
1153
+ };
1154
+ }
1155
+ }
1156
+ return void 0;
1157
+ }
1158
+ function detectPlatformFingerprint(input) {
1159
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
1160
+ }
1161
+
1162
+ // src/edge-core/classify.ts
1163
+ 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;
1164
+ 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;
1165
+ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
1166
+ var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
1167
+
1168
+ // src/edge-core/events.ts
1169
+ function sampled(rate, random = Math.random) {
1170
+ if (rate <= 0) return false;
1171
+ if (rate >= 1) return true;
1172
+ return random() < rate;
1173
+ }
1174
+
1175
+ // src/adapters/anonymous-beacon.ts
1176
+ function readHeader(raw, name) {
1177
+ const value = raw[name];
1178
+ if (typeof value === "string") return value;
1179
+ if (Array.isArray(value)) return value[0];
1180
+ return void 0;
1181
+ }
1182
+ function classifyPassThroughTraffic(path, rawHeaders) {
1183
+ if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
1184
+ const credentials = extractCredentials(rawHeaders, {});
1185
+ if (credentials.astraId || credentials.apiKey || credentials.jwt) {
1186
+ return { kind: "never" };
1187
+ }
1188
+ const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
1189
+ const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1190
+ const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
1191
+ if (fingerprint) {
1192
+ return {
1193
+ kind: "platform",
1194
+ protocol: "platform-ua",
1195
+ evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
1196
+ };
1197
+ }
1198
+ if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
1199
+ return {
1200
+ kind: "anonymous-bot",
1201
+ protocol: "ua-heuristic",
1202
+ evidence: { userAgent: userAgent || "(none)" }
1203
+ };
1204
+ }
1205
+ if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
1206
+ return {
1207
+ kind: "anonymous-bot",
1208
+ protocol: "ua-heuristic",
1209
+ evidence: { userAgent, spoofedBrowser: true }
1210
+ };
1211
+ }
1212
+ return { kind: "never" };
1213
+ }
1214
+ function createAnonymousBeacon(config, options) {
1215
+ const counterpartyId = config.counterpartyId;
1216
+ if (!counterpartyId) return () => {
1217
+ };
1218
+ const random = options?.random ?? Math.random;
1219
+ let edgeConfig;
1220
+ let refreshing = false;
1221
+ const refreshConfig = () => {
1222
+ if (refreshing) return;
1223
+ refreshing = true;
1224
+ getEdgeConfig(config, counterpartyId).then((cfg) => {
1225
+ edgeConfig = cfg;
1226
+ }).catch(() => {
1227
+ }).finally(() => {
1228
+ refreshing = false;
1229
+ });
1230
+ };
1231
+ refreshConfig();
1232
+ return (input) => {
1233
+ try {
1234
+ refreshConfig();
1235
+ const cfg = edgeConfig;
1236
+ if (!cfg) return;
1237
+ const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
1238
+ if (traffic.kind === "never") return;
1239
+ if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
1240
+ return;
1241
+ }
1242
+ const userAgent = readHeader(input.rawHeaders, "user-agent");
1243
+ const xff = input.rawHeaders["x-forwarded-for"];
1244
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1245
+ const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
1246
+ reportUnregisteredAttempt(config, {
1247
+ ...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
1248
+ counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
1249
+ ...sourceIp && { sourceIp },
1250
+ ...userAgent && { userAgent },
1251
+ requestPath: input.requestPath,
1252
+ requestMethod: input.requestMethod,
1253
+ ...traffic.protocol && { protocol: traffic.protocol },
1254
+ ...traffic.evidence && { evidence: traffic.evidence },
1255
+ observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
1256
+ }).catch(() => {
1257
+ });
1258
+ } catch {
1259
+ }
1260
+ };
1261
+ }
796
1262
 
797
1263
  // src/adapters/http-pdlss.ts
798
1264
  var HTTP_METHOD_ACTION_TABLE = {
@@ -956,7 +1422,7 @@ function resolveNextPdlss(request, routeConfig) {
956
1422
  routeAction: routeConfig?.action
957
1423
  });
958
1424
  }
959
- function generateCommerceShieldHtml(result, options) {
1425
+ function generateInterstitialHtml(result, options) {
960
1426
  const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
961
1427
  const message = escapeHtml(
962
1428
  options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
@@ -1112,13 +1578,31 @@ function generateCommerceShieldHtml(result, options) {
1112
1578
  }
1113
1579
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
1114
1580
  function createMiddleware(options) {
1581
+ if ("routes" in options) {
1582
+ console.warn(
1583
+ "[VerificationGateway/Next.js] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
1584
+ );
1585
+ }
1115
1586
  const {
1116
1587
  skipPaths = [],
1117
- showCommerceShield = true,
1588
+ showInterstitial,
1589
+ showCommerceShield,
1118
1590
  enableRuntimeChallenge = true,
1119
1591
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
1592
+ anonymousBeacon = true,
1120
1593
  ...config
1121
1594
  } = options;
1595
+ const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
1596
+ const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "website" }) : void 0;
1597
+ const beaconPassThrough = emitAnonymousBeacon ? (request, pathname) => {
1598
+ emitAnonymousBeacon({
1599
+ requestPath: pathname,
1600
+ requestMethod: request.method,
1601
+ counterpartyUrl: config.counterpartyUrl || request.nextUrl.origin,
1602
+ sourceIp: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || void 0,
1603
+ rawHeaders: Object.fromEntries(request.headers.entries())
1604
+ });
1605
+ } : void 0;
1122
1606
  let cachedRoutes = [];
1123
1607
  let lastFetchAt = 0;
1124
1608
  let refreshing = null;
@@ -1160,9 +1644,11 @@ function createMiddleware(options) {
1160
1644
  }
1161
1645
  const routeConfig = findRouteConfig(cachedRoutes, pathname, request.method);
1162
1646
  if (!routeConfig) {
1647
+ beaconPassThrough?.(request, pathname);
1163
1648
  return NextResponse.next();
1164
1649
  }
1165
1650
  if (routeConfig.minAccessLevel === "none") {
1651
+ beaconPassThrough?.(request, pathname);
1166
1652
  return NextResponse.next();
1167
1653
  }
1168
1654
  const credentials = extractCredentialsFromNextRequest(request);
@@ -1214,8 +1700,8 @@ function createMiddleware(options) {
1214
1700
  { status: 403 }
1215
1701
  );
1216
1702
  }
1217
- if (showCommerceShield) {
1218
- return new NextResponse(generateCommerceShieldHtml(preCheckResult, options), {
1703
+ if (interstitialEnabled) {
1704
+ return new NextResponse(generateInterstitialHtml(preCheckResult, options), {
1219
1705
  status: 200,
1220
1706
  headers: {
1221
1707
  "Content-Type": "text/html",
@@ -1243,17 +1729,9 @@ function createMiddleware(options) {
1243
1729
  host: request.headers.get("host") || void 0,
1244
1730
  forwardedFor,
1245
1731
  agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0,
1246
- // Workstream D — full sanitised header capture (Web Headers → plain map).
1247
- observedMetadata: (() => {
1248
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(
1249
- Object.fromEntries(request.headers.entries())
1250
- );
1251
- return {
1252
- headers,
1253
- ...apiKeyFormat && { apiKeyFormat },
1254
- ...platformHeaders && { platformHeaders }
1255
- };
1256
- })()
1732
+ // Workstream D — full sanitised header capture + CDN-derived
1733
+ // connection block (Web Headers → plain map).
1734
+ observedMetadata: buildSdkObservedMetadata(Object.fromEntries(request.headers.entries()))
1257
1735
  }
1258
1736
  };
1259
1737
  let result = await verify(config, verificationRequest);
@@ -1269,6 +1747,21 @@ function createMiddleware(options) {
1269
1747
  const approvalRequired = result.identityVerified && result.policyAllowed && requiresHumanApproval(result);
1270
1748
  if (approvalRequired) annotateApprovalRequired(result);
1271
1749
  if (!result.identityVerified || !result.policyAllowed || approvalRequired) {
1750
+ if (result.misconfigured && pathname.startsWith("/api/")) {
1751
+ return NextResponse.json(
1752
+ {
1753
+ success: false,
1754
+ error: {
1755
+ code: "MERCHANT_VERIFICATION_MISCONFIGURED",
1756
+ message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
1757
+ guidance: result.guidance,
1758
+ failures: result.failures,
1759
+ correlationId: result.correlationId
1760
+ }
1761
+ },
1762
+ { status: 503 }
1763
+ );
1764
+ }
1272
1765
  if (pathname.startsWith("/api/")) {
1273
1766
  return NextResponse.json(
1274
1767
  {
@@ -1293,8 +1786,8 @@ function createMiddleware(options) {
1293
1786
  }
1294
1787
  );
1295
1788
  }
1296
- if (showCommerceShield) {
1297
- return new NextResponse(generateCommerceShieldHtml(result, options), {
1789
+ if (interstitialEnabled) {
1790
+ return new NextResponse(generateInterstitialHtml(result, options), {
1298
1791
  status: 200,
1299
1792
  headers: {
1300
1793
  "Content-Type": "text/html",