@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;
@@ -91,7 +91,7 @@ var DEFAULT_CONFIG = {
91
91
  };
92
92
  var initCheckPerformed = false;
93
93
  var deprecationWarningShown = false;
94
- async function performInitCheck(apiBaseUrl, debug, strictInit) {
94
+ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
95
95
  initCheckPerformed = true;
96
96
  try {
97
97
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
@@ -108,6 +108,28 @@ async function performInitCheck(apiBaseUrl, debug, strictInit) {
108
108
  `[VerificationGateway] init check passed for ${apiBaseUrl} (content-type: ${contentType})`
109
109
  );
110
110
  }
111
+ if (apiKey) {
112
+ const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
113
+ headers: { Authorization: `Bearer ${apiKey}` }
114
+ });
115
+ if (selfCheck.status === 401) {
116
+ const body = await selfCheck.json().catch(() => ({}));
117
+ 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}` : "") + `.`;
118
+ if (strictInit) {
119
+ throw new Error(`${message} (strictInit=true)`);
120
+ }
121
+ console.error(message);
122
+ } else if (selfCheck.ok) {
123
+ const body = await selfCheck.json().catch(() => ({}));
124
+ if (typeof body.daysToExpiry === "number" && body.daysToExpiry <= 7) {
125
+ console.warn(
126
+ `[VerificationGateway] the configured AstraSync API key expires in ${body.daysToExpiry} day(s) \u2014 rotate it at Settings \u2192 Developer Tools before integrations start failing.`
127
+ );
128
+ } else if (debug) {
129
+ console.log("[VerificationGateway] API key self-check passed");
130
+ }
131
+ }
132
+ }
111
133
  } catch (err) {
112
134
  if (strictInit) {
113
135
  throw err;
@@ -182,11 +204,54 @@ function cacheResult(request, result, configuredTtl, counterpartyId) {
182
204
  expiresAt: Date.now() + ttlSeconds * 1e3
183
205
  });
184
206
  }
207
+ function extractCredentials(headers, query) {
208
+ const credentials = {};
209
+ const ASTRA_ID_PATTERN = /^ASTRAE?-[A-Za-z0-9_-]{1,64}$/;
210
+ 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"];
211
+ if (astraIdHeader) {
212
+ const raw = Array.isArray(astraIdHeader) ? astraIdHeader[0] : typeof astraIdHeader === "string" ? astraIdHeader : void 0;
213
+ if (typeof raw === "string" && ASTRA_ID_PATTERN.test(raw)) {
214
+ credentials.astraId = raw;
215
+ }
216
+ }
217
+ const apiKeyHeader = headers["x-api-key"] || headers["X-Api-Key"] || headers["X-API-KEY"];
218
+ if (apiKeyHeader) {
219
+ credentials.apiKey = Array.isArray(apiKeyHeader) ? apiKeyHeader[0] : apiKeyHeader;
220
+ }
221
+ const authHeader = headers["authorization"] || headers["Authorization"];
222
+ if (authHeader) {
223
+ const authValue = Array.isArray(authHeader) ? authHeader[0] : authHeader;
224
+ if (typeof authValue === "string") {
225
+ credentials.authorizationHeader = authValue;
226
+ if (authValue.startsWith("Bearer ")) {
227
+ credentials.jwt = authValue.slice(7);
228
+ }
229
+ }
230
+ }
231
+ if (query) {
232
+ if (query.astraId && !credentials.astraId) {
233
+ credentials.astraId = query.astraId;
234
+ }
235
+ if (query.apiKey && !credentials.apiKey) {
236
+ credentials.apiKey = query.apiKey;
237
+ }
238
+ }
239
+ return credentials;
240
+ }
185
241
  function createGuidanceResponse(_config, reason, options = {}) {
186
242
  const source = options.source ?? "no_credentials";
187
243
  const isApiError = source === "api_error";
244
+ const isMisconfig = source === "merchant_misconfig";
188
245
  const urls = options.urls;
189
- const guidance = isApiError ? {
246
+ const guidance = isMisconfig ? {
247
+ 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.",
248
+ registrationUrl: urls?.registrationUrl ?? "",
249
+ documentationUrl: options.remediation?.docsUrl ?? urls?.documentationUrl ?? "",
250
+ steps: [
251
+ "Report the failure to the site operator \u2014 their AstraSync API key needs rotating",
252
+ "Do not retry: the failure is deterministic until the operator updates their key"
253
+ ]
254
+ } : isApiError ? {
190
255
  message: "Verification is temporarily unavailable. Retry with exponential backoff; if the issue persists, contact support with the correlationId.",
191
256
  registrationUrl: urls?.registrationUrl ?? "",
192
257
  documentationUrl: urls?.documentationUrl ?? "",
@@ -224,13 +289,22 @@ function createGuidanceResponse(_config, reason, options = {}) {
224
289
  // partners (and their custom onDenied handlers) can branch on
225
290
  // dimension. Without this, the synthesised stub was indistinguishable
226
291
  // from a real policy deny.
227
- failures: isApiError ? [
292
+ failures: isMisconfig ? [
293
+ {
294
+ dimension: "verify_access.merchant_misconfigured",
295
+ message: reason ?? "The integration API key was rejected by AstraSync",
296
+ guidance: guidance.message
297
+ }
298
+ ] : isApiError ? [
228
299
  {
229
300
  dimension: "verify_access.api_error",
230
301
  message: reason ?? "Verification temporarily unavailable",
231
302
  guidance: guidance.message
232
303
  }
233
304
  ] : void 0,
305
+ // 4.0.0: typed marker so adapters render the misconfig envelope (503 +
306
+ // MERCHANT_VERIFICATION_MISCONFIGURED) without string-matching.
307
+ ...isMisconfig && { misconfigured: true },
234
308
  correlationId: options.correlationId,
235
309
  attemptId: options.attemptId,
236
310
  verifiedAt: /* @__PURE__ */ new Date()
@@ -285,7 +359,8 @@ async function callVerifyAccessAPI(config, request) {
285
359
  const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
286
360
  method: "POST",
287
361
  headers,
288
- body: JSON.stringify(body)
362
+ body: JSON.stringify(body),
363
+ signal: AbortSignal.timeout(config.verifyTimeoutMs ?? 1e4)
289
364
  });
290
365
  const data = await response.json();
291
366
  if (response.status === 410) {
@@ -309,6 +384,17 @@ async function callVerifyAccessAPI(config, request) {
309
384
  };
310
385
  }
311
386
  if (!response.ok) {
387
+ if (response.status === 401) {
388
+ return {
389
+ success: false,
390
+ error: data.message || data.error || "Integration API key rejected",
391
+ misconfig: true,
392
+ misconfigCode: typeof data?.code === "string" ? data.code : void 0,
393
+ remediation: data?.remediation && typeof data.remediation === "object" ? data.remediation : void 0,
394
+ correlationId: typeof data?.correlationId === "string" ? data.correlationId : void 0,
395
+ attemptId: typeof data?.attemptId === "string" ? data.attemptId : void 0
396
+ };
397
+ }
312
398
  return {
313
399
  success: false,
314
400
  error: data.message || data.error || `API returned ${response.status}`,
@@ -330,9 +416,19 @@ async function verify(config, request, options) {
330
416
  const urls = mergedConfig.apiBaseUrl ? getCachedWellKnownUrls(mergedConfig.apiBaseUrl) : void 0;
331
417
  if (!initCheckPerformed && !mergedConfig.disableInitChecks && mergedConfig.apiBaseUrl) {
332
418
  if (mergedConfig.strictInit) {
333
- await performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, true);
419
+ await performInitCheck(
420
+ mergedConfig.apiBaseUrl,
421
+ mergedConfig.debug,
422
+ true,
423
+ mergedConfig.apiKey
424
+ );
334
425
  } else {
335
- void performInitCheck(mergedConfig.apiBaseUrl, mergedConfig.debug, false);
426
+ void performInitCheck(
427
+ mergedConfig.apiBaseUrl,
428
+ mergedConfig.debug,
429
+ false,
430
+ mergedConfig.apiKey
431
+ );
336
432
  }
337
433
  }
338
434
  if (!deprecationWarningShown && (config.minTrustScore !== void 0 || config.minTrustScoreForFull !== void 0)) {
@@ -371,9 +467,10 @@ async function verify(config, request, options) {
371
467
  const serverDenialReasons = Array.isArray(rawServerDenialReasons) && rawServerDenialReasons.length > 0 ? rawServerDenialReasons : void 0;
372
468
  if (!apiResponse.success) {
373
469
  return createGuidanceResponse(mergedConfig, apiResponse.error, {
374
- source: "api_error",
470
+ source: apiResponse.misconfig ? "merchant_misconfig" : "api_error",
375
471
  correlationId: apiResponse.correlationId,
376
472
  attemptId: apiResponse.attemptId,
473
+ remediation: apiResponse.remediation,
377
474
  urls
378
475
  });
379
476
  }
@@ -515,6 +612,21 @@ async function fetchRoutes(config, counterpartyId) {
515
612
  return null;
516
613
  }
517
614
  }
615
+ async function reportUnregisteredAttempt(config, data) {
616
+ const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
617
+ const { counterpartyUrl, ...rest } = data;
618
+ const body = {
619
+ ...rest,
620
+ ...counterpartyUrl && { counterpartyUrl },
621
+ ...config.counterpartyId && { counterpartyId: config.counterpartyId }
622
+ };
623
+ await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
624
+ method: "POST",
625
+ headers: { "Content-Type": "application/json" },
626
+ body: JSON.stringify(body)
627
+ }).catch(() => {
628
+ });
629
+ }
518
630
  async function reportCounterpartyPreCheckFailure(config, data) {
519
631
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
520
632
  await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
@@ -620,6 +732,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
620
732
  }
621
733
 
622
734
  // src/metadata-capture.ts
735
+ var CAPTURE_SCHEMA_VERSION = 1;
623
736
  var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
624
737
  "cookie",
625
738
  "set-cookie",
@@ -756,6 +869,359 @@ function sanitizeHeaders(raw) {
756
869
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
757
870
  return result;
758
871
  }
872
+ var CONNECTION_HEADER_SOURCES = [
873
+ ["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
874
+ [
875
+ "country",
876
+ ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
877
+ ],
878
+ ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
879
+ ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
880
+ ["asn", ["cloudfront-viewer-asn"]],
881
+ ["tlsVersion", ["cloudfront-viewer-tls"]],
882
+ ["httpVersion", ["cloudfront-viewer-http-version"]],
883
+ // TLS fingerprints can only be computed where TLS terminates. When a CDN
884
+ // terminates TLS in front of the merchant, its fingerprint headers ARE the
885
+ // connection-layer truth — normalising them here is the permanent design.
886
+ ["tlsFingerprint", ["cf-ja3-hash"]],
887
+ ["ja4", ["cf-ja4"]]
888
+ ];
889
+ function deriveConnectionFromHeaders(headers) {
890
+ if (!headers) return void 0;
891
+ const lower = {};
892
+ for (const name of Object.keys(headers)) {
893
+ const value = headers[name];
894
+ if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
895
+ }
896
+ const connection = {};
897
+ for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
898
+ for (const source of sources) {
899
+ const value = lower[source]?.trim();
900
+ if (value) {
901
+ connection[key] = value;
902
+ break;
903
+ }
904
+ }
905
+ }
906
+ if (!connection.ip) {
907
+ const xff = lower["x-forwarded-for"];
908
+ const client = xff?.split(",")[0]?.trim();
909
+ if (client) connection.ip = client;
910
+ }
911
+ return Object.keys(connection).length > 0 ? connection : void 0;
912
+ }
913
+ function buildSdkObservedMetadata(rawHeaders) {
914
+ const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
915
+ const flat = {};
916
+ if (rawHeaders) {
917
+ for (const name of Object.keys(rawHeaders)) {
918
+ const value = rawHeaders[name];
919
+ if (value === void 0) continue;
920
+ flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
921
+ }
922
+ }
923
+ const connection = deriveConnectionFromHeaders(flat);
924
+ return {
925
+ headers,
926
+ ...apiKeyFormat && { apiKeyFormat },
927
+ ...platformHeaders && { platformHeaders },
928
+ ...connection && { connection },
929
+ schemaVersion: CAPTURE_SCHEMA_VERSION
930
+ };
931
+ }
932
+
933
+ // src/edge-config.ts
934
+ var DEFAULT_EDGE_CONFIG = Object.freeze({
935
+ version: 1,
936
+ mode: "observe",
937
+ depth: "classify",
938
+ pathRules: [],
939
+ sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
940
+ failurePosture: "open"
941
+ });
942
+ var EDGE_MODES = ["observe", "enforce"];
943
+ var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
944
+ function isEdgeConfig(value) {
945
+ if (typeof value !== "object" || value === null) return false;
946
+ const v = value;
947
+ if (v.version !== 1) return false;
948
+ if (!EDGE_MODES.includes(v.mode)) return false;
949
+ if (!EDGE_DEPTHS.includes(v.depth)) return false;
950
+ if (!Array.isArray(v.pathRules)) return false;
951
+ for (const rule of v.pathRules) {
952
+ if (typeof rule !== "object" || rule === null) return false;
953
+ const r = rule;
954
+ if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
955
+ if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
956
+ if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
957
+ }
958
+ const sampling = v.sampling;
959
+ if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
960
+ return false;
961
+ }
962
+ return v.failurePosture === "open";
963
+ }
964
+ async function fetchEdgeConfig(config, counterpartyId, opts) {
965
+ if (!counterpartyId) return null;
966
+ const headers = { Accept: "application/json" };
967
+ if (config.apiKey) {
968
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
969
+ headers["X-API-Key"] = config.apiKey;
970
+ }
971
+ if (opts?.etag) headers["If-None-Match"] = opts.etag;
972
+ try {
973
+ const response = await fetch(
974
+ `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
975
+ { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
976
+ );
977
+ if (response.status === 304) return { notModified: true };
978
+ if (!response.ok) return null;
979
+ const body = await response.json();
980
+ const edgeConfig = body.data?.edgeConfig;
981
+ if (!isEdgeConfig(edgeConfig)) return null;
982
+ return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
983
+ } catch {
984
+ return null;
985
+ }
986
+ }
987
+ var DEFAULT_TTL_MS = 60 * 1e3;
988
+ var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
989
+ var cache2 = /* @__PURE__ */ new Map();
990
+ var inflight = /* @__PURE__ */ new Map();
991
+ function cacheKey(apiBaseUrl, counterpartyId) {
992
+ return `${apiBaseUrl}|${counterpartyId}`;
993
+ }
994
+ function degradeToObserve(config) {
995
+ if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
996
+ return config;
997
+ }
998
+ return {
999
+ ...config,
1000
+ mode: "observe",
1001
+ pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
1002
+ };
1003
+ }
1004
+ async function revalidate(config, counterpartyId) {
1005
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1006
+ const existing = inflight.get(key);
1007
+ if (existing) return existing;
1008
+ const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
1009
+ if (result === null) return;
1010
+ const now = Date.now();
1011
+ if ("notModified" in result) {
1012
+ const entry = cache2.get(key);
1013
+ if (entry) entry.lastSuccessAt = now;
1014
+ return;
1015
+ }
1016
+ cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
1017
+ }).finally(() => {
1018
+ inflight.delete(key);
1019
+ });
1020
+ inflight.set(key, promise);
1021
+ return promise;
1022
+ }
1023
+ async function getEdgeConfig(config, counterpartyId, opts) {
1024
+ const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
1025
+ const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
1026
+ const key = cacheKey(config.apiBaseUrl, counterpartyId);
1027
+ const entry = cache2.get(key);
1028
+ if (entry) {
1029
+ const age = Date.now() - entry.lastSuccessAt;
1030
+ if (age > ttlMs) {
1031
+ revalidate(config, counterpartyId).catch(() => {
1032
+ });
1033
+ }
1034
+ return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
1035
+ }
1036
+ await revalidate(config, counterpartyId).catch(() => {
1037
+ });
1038
+ return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
1039
+ }
1040
+
1041
+ // src/platform-signatures.ts
1042
+ var PLATFORM_AGENT_SIGNATURES = [
1043
+ {
1044
+ vendor: "claude",
1045
+ displayName: "Anthropic Claude",
1046
+ uaPatterns: [/claude/i, /anthropic/i],
1047
+ agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
1048
+ apiKeyFormatPatterns: [/^sk-ant/i],
1049
+ headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
1050
+ },
1051
+ {
1052
+ vendor: "chatgpt",
1053
+ displayName: "OpenAI ChatGPT",
1054
+ uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
1055
+ agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
1056
+ apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
1057
+ headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
1058
+ },
1059
+ {
1060
+ vendor: "gemini",
1061
+ displayName: "Google Gemini",
1062
+ uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
1063
+ agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
1064
+ apiKeyFormatPatterns: [/^aiza/i],
1065
+ headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
1066
+ },
1067
+ {
1068
+ vendor: "cursor",
1069
+ displayName: "Cursor",
1070
+ uaPatterns: [/cursor/i],
1071
+ agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
1072
+ },
1073
+ {
1074
+ vendor: "goose",
1075
+ displayName: "Block Goose",
1076
+ uaPatterns: [/goose/i, /block-goose/i],
1077
+ agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
1078
+ },
1079
+ {
1080
+ vendor: "perplexity",
1081
+ displayName: "Perplexity",
1082
+ uaPatterns: [/perplexity/i],
1083
+ agentCardPatterns: [/perplexity\.ai/i],
1084
+ apiKeyFormatPatterns: [/^pplx-/i]
1085
+ }
1086
+ ];
1087
+ function matchPlatformSignature(signatures, input) {
1088
+ const ua = input.userAgent ?? "";
1089
+ const cardUrl = input.agentCardUrl ?? "";
1090
+ const apiKeyFormat = input.apiKeyFormat ?? "";
1091
+ const headerLines = [];
1092
+ if (input.platformHeaders) {
1093
+ for (const [name, value] of Object.entries(input.platformHeaders)) {
1094
+ headerLines.push(name, `${name}: ${value}`);
1095
+ }
1096
+ }
1097
+ for (const sig of signatures) {
1098
+ const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
1099
+ const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1100
+ const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1101
+ const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1102
+ if (uaMatch || cardMatch || keyMatch || headerMatch) {
1103
+ const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1104
+ const cardHost = (() => {
1105
+ try {
1106
+ return cardUrl ? new URL(cardUrl).host : "";
1107
+ } catch {
1108
+ return "";
1109
+ }
1110
+ })();
1111
+ const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1112
+ return {
1113
+ vendor: sig.vendor,
1114
+ fingerprintKey: `${sig.vendor}:${keyBasis}`,
1115
+ displayName: sig.displayName
1116
+ };
1117
+ }
1118
+ }
1119
+ return void 0;
1120
+ }
1121
+ function detectPlatformFingerprint(input) {
1122
+ return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
1123
+ }
1124
+
1125
+ // src/edge-core/classify.ts
1126
+ 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;
1127
+ 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;
1128
+ var BROWSER_UA_PATTERN = /mozilla\/\d/i;
1129
+ var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
1130
+
1131
+ // src/edge-core/events.ts
1132
+ function sampled(rate, random = Math.random) {
1133
+ if (rate <= 0) return false;
1134
+ if (rate >= 1) return true;
1135
+ return random() < rate;
1136
+ }
1137
+
1138
+ // src/adapters/anonymous-beacon.ts
1139
+ function readHeader(raw, name) {
1140
+ const value = raw[name];
1141
+ if (typeof value === "string") return value;
1142
+ if (Array.isArray(value)) return value[0];
1143
+ return void 0;
1144
+ }
1145
+ function classifyPassThroughTraffic(path, rawHeaders) {
1146
+ if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
1147
+ const credentials = extractCredentials(rawHeaders, {});
1148
+ if (credentials.astraId || credentials.apiKey || credentials.jwt) {
1149
+ return { kind: "never" };
1150
+ }
1151
+ const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
1152
+ const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1153
+ const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
1154
+ if (fingerprint) {
1155
+ return {
1156
+ kind: "platform",
1157
+ protocol: "platform-ua",
1158
+ evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
1159
+ };
1160
+ }
1161
+ if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
1162
+ return {
1163
+ kind: "anonymous-bot",
1164
+ protocol: "ua-heuristic",
1165
+ evidence: { userAgent: userAgent || "(none)" }
1166
+ };
1167
+ }
1168
+ if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
1169
+ return {
1170
+ kind: "anonymous-bot",
1171
+ protocol: "ua-heuristic",
1172
+ evidence: { userAgent, spoofedBrowser: true }
1173
+ };
1174
+ }
1175
+ return { kind: "never" };
1176
+ }
1177
+ function createAnonymousBeacon(config, options) {
1178
+ const counterpartyId = config.counterpartyId;
1179
+ if (!counterpartyId) return () => {
1180
+ };
1181
+ const random = options?.random ?? Math.random;
1182
+ let edgeConfig;
1183
+ let refreshing = false;
1184
+ const refreshConfig = () => {
1185
+ if (refreshing) return;
1186
+ refreshing = true;
1187
+ getEdgeConfig(config, counterpartyId).then((cfg) => {
1188
+ edgeConfig = cfg;
1189
+ }).catch(() => {
1190
+ }).finally(() => {
1191
+ refreshing = false;
1192
+ });
1193
+ };
1194
+ refreshConfig();
1195
+ return (input) => {
1196
+ try {
1197
+ refreshConfig();
1198
+ const cfg = edgeConfig;
1199
+ if (!cfg) return;
1200
+ const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
1201
+ if (traffic.kind === "never") return;
1202
+ if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
1203
+ return;
1204
+ }
1205
+ const userAgent = readHeader(input.rawHeaders, "user-agent");
1206
+ const xff = input.rawHeaders["x-forwarded-for"];
1207
+ const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
1208
+ const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
1209
+ reportUnregisteredAttempt(config, {
1210
+ ...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
1211
+ counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
1212
+ ...sourceIp && { sourceIp },
1213
+ ...userAgent && { userAgent },
1214
+ requestPath: input.requestPath,
1215
+ requestMethod: input.requestMethod,
1216
+ ...traffic.protocol && { protocol: traffic.protocol },
1217
+ ...traffic.evidence && { evidence: traffic.evidence },
1218
+ observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
1219
+ }).catch(() => {
1220
+ });
1221
+ } catch {
1222
+ }
1223
+ };
1224
+ }
759
1225
 
760
1226
  // src/adapters/http-pdlss.ts
761
1227
  var HTTP_METHOD_ACTION_TABLE = {
@@ -919,7 +1385,7 @@ function resolveNextPdlss(request, routeConfig) {
919
1385
  routeAction: routeConfig?.action
920
1386
  });
921
1387
  }
922
- function generateCommerceShieldHtml(result, options) {
1388
+ function generateInterstitialHtml(result, options) {
923
1389
  const title = escapeHtml(options.commerceShield?.title || "AstraSync Agent Verification");
924
1390
  const message = escapeHtml(
925
1391
  options.commerceShield?.message || result.guidance?.message || "This site verifies AI agents before granting access. We noticed you're visiting without AstraSync credentials."
@@ -1075,13 +1541,31 @@ function generateCommerceShieldHtml(result, options) {
1075
1541
  }
1076
1542
  var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
1077
1543
  function createMiddleware(options) {
1544
+ if ("routes" in options) {
1545
+ console.warn(
1546
+ "[VerificationGateway/Next.js] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
1547
+ );
1548
+ }
1078
1549
  const {
1079
1550
  skipPaths = [],
1080
- showCommerceShield = true,
1551
+ showInterstitial,
1552
+ showCommerceShield,
1081
1553
  enableRuntimeChallenge = true,
1082
1554
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
1555
+ anonymousBeacon = true,
1083
1556
  ...config
1084
1557
  } = options;
1558
+ const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
1559
+ const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "website" }) : void 0;
1560
+ const beaconPassThrough = emitAnonymousBeacon ? (request, pathname) => {
1561
+ emitAnonymousBeacon({
1562
+ requestPath: pathname,
1563
+ requestMethod: request.method,
1564
+ counterpartyUrl: config.counterpartyUrl || request.nextUrl.origin,
1565
+ sourceIp: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || void 0,
1566
+ rawHeaders: Object.fromEntries(request.headers.entries())
1567
+ });
1568
+ } : void 0;
1085
1569
  let cachedRoutes = [];
1086
1570
  let lastFetchAt = 0;
1087
1571
  let refreshing = null;
@@ -1123,9 +1607,11 @@ function createMiddleware(options) {
1123
1607
  }
1124
1608
  const routeConfig = findRouteConfig(cachedRoutes, pathname, request.method);
1125
1609
  if (!routeConfig) {
1610
+ beaconPassThrough?.(request, pathname);
1126
1611
  return NextResponse.next();
1127
1612
  }
1128
1613
  if (routeConfig.minAccessLevel === "none") {
1614
+ beaconPassThrough?.(request, pathname);
1129
1615
  return NextResponse.next();
1130
1616
  }
1131
1617
  const credentials = extractCredentialsFromNextRequest(request);
@@ -1177,8 +1663,8 @@ function createMiddleware(options) {
1177
1663
  { status: 403 }
1178
1664
  );
1179
1665
  }
1180
- if (showCommerceShield) {
1181
- return new NextResponse(generateCommerceShieldHtml(preCheckResult, options), {
1666
+ if (interstitialEnabled) {
1667
+ return new NextResponse(generateInterstitialHtml(preCheckResult, options), {
1182
1668
  status: 200,
1183
1669
  headers: {
1184
1670
  "Content-Type": "text/html",
@@ -1206,17 +1692,9 @@ function createMiddleware(options) {
1206
1692
  host: request.headers.get("host") || void 0,
1207
1693
  forwardedFor,
1208
1694
  agentCardUrl: request.headers.get("x-astrasync-agent-card") || void 0,
1209
- // Workstream D — full sanitised header capture (Web Headers → plain map).
1210
- observedMetadata: (() => {
1211
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(
1212
- Object.fromEntries(request.headers.entries())
1213
- );
1214
- return {
1215
- headers,
1216
- ...apiKeyFormat && { apiKeyFormat },
1217
- ...platformHeaders && { platformHeaders }
1218
- };
1219
- })()
1695
+ // Workstream D — full sanitised header capture + CDN-derived
1696
+ // connection block (Web Headers → plain map).
1697
+ observedMetadata: buildSdkObservedMetadata(Object.fromEntries(request.headers.entries()))
1220
1698
  }
1221
1699
  };
1222
1700
  let result = await verify(config, verificationRequest);
@@ -1232,6 +1710,21 @@ function createMiddleware(options) {
1232
1710
  const approvalRequired = result.identityVerified && result.policyAllowed && requiresHumanApproval(result);
1233
1711
  if (approvalRequired) annotateApprovalRequired(result);
1234
1712
  if (!result.identityVerified || !result.policyAllowed || approvalRequired) {
1713
+ if (result.misconfigured && pathname.startsWith("/api/")) {
1714
+ return NextResponse.json(
1715
+ {
1716
+ success: false,
1717
+ error: {
1718
+ code: "MERCHANT_VERIFICATION_MISCONFIGURED",
1719
+ message: result.denialReasons?.[0] || "This site's AstraSync integration is misconfigured",
1720
+ guidance: result.guidance,
1721
+ failures: result.failures,
1722
+ correlationId: result.correlationId
1723
+ }
1724
+ },
1725
+ { status: 503 }
1726
+ );
1727
+ }
1235
1728
  if (pathname.startsWith("/api/")) {
1236
1729
  return NextResponse.json(
1237
1730
  {
@@ -1256,8 +1749,8 @@ function createMiddleware(options) {
1256
1749
  }
1257
1750
  );
1258
1751
  }
1259
- if (showCommerceShield) {
1260
- return new NextResponse(generateCommerceShieldHtml(result, options), {
1752
+ if (interstitialEnabled) {
1753
+ return new NextResponse(generateInterstitialHtml(result, options), {
1261
1754
  status: 200,
1262
1755
  headers: {
1263
1756
  "Content-Type": "text/html",