@astrasyncai/verification-gateway 4.2.1 → 4.4.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 (138) hide show
  1. package/README.md +45 -21
  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 +145 -21
  7. package/dist/adapters/express.js.map +1 -1
  8. package/dist/adapters/express.mjs +145 -21
  9. package/dist/adapters/express.mjs.map +1 -1
  10. package/dist/adapters/mcp.d.mts +38 -30
  11. package/dist/adapters/mcp.d.ts +38 -30
  12. package/dist/adapters/mcp.js +72 -16
  13. package/dist/adapters/mcp.js.map +1 -1
  14. package/dist/adapters/mcp.mjs +72 -16
  15. package/dist/adapters/mcp.mjs.map +1 -1
  16. package/dist/adapters/nextjs.d.mts +2 -2
  17. package/dist/adapters/nextjs.d.ts +2 -2
  18. package/dist/adapters/nextjs.js +141 -19
  19. package/dist/adapters/nextjs.js.map +1 -1
  20. package/dist/adapters/nextjs.mjs +141 -19
  21. package/dist/adapters/nextjs.mjs.map +1 -1
  22. package/dist/adapters/sdk.d.mts +2 -2
  23. package/dist/adapters/sdk.d.ts +2 -2
  24. package/dist/adapters/sdk.js +24 -6
  25. package/dist/adapters/sdk.js.map +1 -1
  26. package/dist/adapters/sdk.mjs +24 -6
  27. package/dist/adapters/sdk.mjs.map +1 -1
  28. package/dist/agent/index.d.mts +2 -2
  29. package/dist/agent/index.d.ts +2 -2
  30. package/dist/agent/index.js +24 -3
  31. package/dist/agent/index.js.map +1 -1
  32. package/dist/agent/index.mjs +24 -3
  33. package/dist/agent/index.mjs.map +1 -1
  34. package/dist/bin/astrasync-claude-hook.js +22 -4
  35. package/dist/bin/astrasync-codex-hook.js +22 -4
  36. package/dist/bin/astrasync-guard.js +26 -8
  37. package/dist/bin/astrasync.js +76 -23
  38. package/dist/browser/background.js +22 -4
  39. package/dist/browser/background.js.map +1 -1
  40. package/dist/browser/background.mjs +22 -4
  41. package/dist/browser/background.mjs.map +1 -1
  42. package/dist/browser/browser-adapter.d.mts +2 -2
  43. package/dist/browser/browser-adapter.d.ts +2 -2
  44. package/dist/claude-code/claude-code-adapter.d.mts +2 -2
  45. package/dist/claude-code/claude-code-adapter.d.ts +2 -2
  46. package/dist/cli/index.d.mts +2 -2
  47. package/dist/cli/index.d.ts +2 -2
  48. package/dist/cli/index.js +23 -2
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/cli/index.mjs +23 -2
  51. package/dist/cli/index.mjs.map +1 -1
  52. package/dist/codex/index.d.mts +2 -2
  53. package/dist/codex/index.d.ts +2 -2
  54. package/dist/codex/index.js +22 -4
  55. package/dist/codex/index.js.map +1 -1
  56. package/dist/codex/index.mjs +22 -4
  57. package/dist/codex/index.mjs.map +1 -1
  58. package/dist/cursor/cursor-adapter.d.mts +2 -2
  59. package/dist/cursor/cursor-adapter.d.ts +2 -2
  60. package/dist/cursor/extension.d.mts +2 -2
  61. package/dist/cursor/extension.d.ts +2 -2
  62. package/dist/cursor/extension.js +22 -4
  63. package/dist/cursor/extension.js.map +1 -1
  64. package/dist/cursor/extension.mjs +22 -4
  65. package/dist/cursor/extension.mjs.map +1 -1
  66. package/dist/edge-config.d.mts +1 -1
  67. package/dist/edge-config.d.ts +1 -1
  68. package/dist/edge-config.js +24 -1
  69. package/dist/edge-config.js.map +1 -1
  70. package/dist/edge-config.mjs +22 -1
  71. package/dist/edge-config.mjs.map +1 -1
  72. package/dist/edge-core/index.d.mts +1 -1
  73. package/dist/edge-core/index.d.ts +1 -1
  74. package/dist/edge-core/index.js +121 -16
  75. package/dist/edge-core/index.js.map +1 -1
  76. package/dist/edge-core/index.mjs +121 -16
  77. package/dist/edge-core/index.mjs.map +1 -1
  78. package/dist/{express-D_4hTn5Z.d.mts → express-CR6F3njD.d.mts} +1 -1
  79. package/dist/{express-BVd1_3FE.d.ts → express-DaqL11tx.d.ts} +1 -1
  80. package/dist/gateway/gateway.d.mts +2 -2
  81. package/dist/gateway/gateway.d.ts +2 -2
  82. package/dist/gateway/gateway.js +22 -4
  83. package/dist/gateway/gateway.js.map +1 -1
  84. package/dist/gateway/gateway.mjs +22 -4
  85. package/dist/gateway/gateway.mjs.map +1 -1
  86. package/dist/git-trigger/git-hooks.d.mts +2 -2
  87. package/dist/git-trigger/git-hooks.d.ts +2 -2
  88. package/dist/{index-BqHKlLOU.d.ts → index-B5Ts1WtM.d.ts} +1 -1
  89. package/dist/{index-T1aBoUcc.d.ts → index-BfIxCYIZ.d.ts} +1 -1
  90. package/dist/{index-BTeQ9fgV.d.mts → index-Cub7lK-T.d.mts} +1 -1
  91. package/dist/{index-DQb5-_1X.d.mts → index-CwAh7Tln.d.mts} +1 -1
  92. package/dist/index.d.mts +58 -9
  93. package/dist/index.d.ts +58 -9
  94. package/dist/index.js +218 -54
  95. package/dist/index.js.map +1 -1
  96. package/dist/index.mjs +214 -54
  97. package/dist/index.mjs.map +1 -1
  98. package/dist/local-evaluator/evaluator.d.mts +2 -2
  99. package/dist/local-evaluator/evaluator.d.ts +2 -2
  100. package/dist/metadata-capture.d.mts +35 -5
  101. package/dist/metadata-capture.d.ts +35 -5
  102. package/dist/metadata-capture.js +44 -6
  103. package/dist/metadata-capture.js.map +1 -1
  104. package/dist/metadata-capture.mjs +43 -6
  105. package/dist/metadata-capture.mjs.map +1 -1
  106. package/dist/{nextjs-w7RTbP1P.d.ts → nextjs-DL6uE1ye.d.ts} +1 -1
  107. package/dist/{nextjs-WyeVr2Kp.d.mts → nextjs-DlX6xwbU.d.mts} +1 -1
  108. package/dist/platform-signatures.d.mts +18 -1
  109. package/dist/platform-signatures.d.ts +18 -1
  110. package/dist/platform-signatures.js +35 -2
  111. package/dist/platform-signatures.js.map +1 -1
  112. package/dist/platform-signatures.mjs +35 -2
  113. package/dist/platform-signatures.mjs.map +1 -1
  114. package/dist/registration/index.js +25 -4
  115. package/dist/registration/index.js.map +1 -1
  116. package/dist/registration/index.mjs +25 -4
  117. package/dist/registration/index.mjs.map +1 -1
  118. package/dist/{sdk-CbTNIkAa.d.mts → sdk-Bbh9B_dw.d.mts} +1 -1
  119. package/dist/{sdk-QeX0Z8Ho.d.ts → sdk-C6SASmsh.d.ts} +1 -1
  120. package/dist/transport/index.d.mts +2 -2
  121. package/dist/transport/index.d.ts +2 -2
  122. package/dist/transport/index.js +22 -1
  123. package/dist/transport/index.js.map +1 -1
  124. package/dist/transport/index.mjs +22 -1
  125. package/dist/transport/index.mjs.map +1 -1
  126. package/dist/{types-r850cOt0.d.mts → types-C0zfSXMB.d.mts} +22 -0
  127. package/dist/{types-DqfPU5Bl.d.mts → types-C7Rb-w0d.d.mts} +1 -1
  128. package/dist/{types-sMxBT-nM.d.ts → types-DwSfO7Sb.d.ts} +22 -0
  129. package/dist/{types-BLUx92FJ.d.ts → types-nIh3HtuA.d.ts} +1 -1
  130. package/dist/ui/index.d.mts +1 -1
  131. package/dist/ui/index.d.ts +1 -1
  132. package/dist/verify.d.mts +1 -1
  133. package/dist/verify.d.ts +1 -1
  134. package/dist/verify.js +29 -11
  135. package/dist/verify.js.map +1 -1
  136. package/dist/verify.mjs +29 -11
  137. package/dist/verify.mjs.map +1 -1
  138. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
1
  import 'next/server';
2
- import '../types-r850cOt0.mjs';
3
- export { c as createMatcherConfig, a as createMiddleware } from '../nextjs-WyeVr2Kp.mjs';
2
+ import '../types-C0zfSXMB.mjs';
3
+ export { c as createMatcherConfig, a as createMiddleware } from '../nextjs-DlX6xwbU.mjs';
4
4
  import '../metadata-capture.mjs';
@@ -1,4 +1,4 @@
1
1
  import 'next/server';
2
- import '../types-sMxBT-nM.js';
3
- export { c as createMatcherConfig, a as createMiddleware } from '../nextjs-w7RTbP1P.js';
2
+ import '../types-DwSfO7Sb.js';
3
+ export { c as createMatcherConfig, a as createMiddleware } from '../nextjs-DL6uE1ye.js';
4
4
  import '../metadata-capture.js';
@@ -35,6 +35,27 @@ __export(nextjs_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(nextjs_exports);
37
37
 
38
+ // src/version.ts
39
+ var SDK_VERSION = "4.4.0";
40
+
41
+ // src/http.ts
42
+ var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
43
+ function withUserAgent(initHeaders) {
44
+ if (!initHeaders) return { "user-agent": SDK_USER_AGENT };
45
+ if (initHeaders instanceof Headers) {
46
+ const copy = new Headers(initHeaders);
47
+ if (!copy.has("user-agent")) copy.set("user-agent", SDK_USER_AGENT);
48
+ return copy;
49
+ }
50
+ if (Array.isArray(initHeaders)) {
51
+ const has2 = initHeaders.some(([name]) => name.toLowerCase() === "user-agent");
52
+ return has2 ? initHeaders : [...initHeaders, ["user-agent", SDK_USER_AGENT]];
53
+ }
54
+ const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === "user-agent");
55
+ return has ? initHeaders : { "user-agent": SDK_USER_AGENT, ...initHeaders };
56
+ }
57
+ var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
58
+
38
59
  // src/adapters/stepup-poll.ts
39
60
  var DEFAULT_POLL_INTERVAL_MS = 3e3;
40
61
  var MIN_POLL_INTERVAL_MS = 250;
@@ -49,7 +70,7 @@ function resolvePollUrl(config, pollUrl) {
49
70
  async function pollStepUpStatus(config, approval) {
50
71
  if (!approval.pollUrl) return null;
51
72
  try {
52
- const res = await fetch(resolvePollUrl(config, approval.pollUrl), {
73
+ const res = await sdkFetch(resolvePollUrl(config, approval.pollUrl), {
53
74
  method: "GET",
54
75
  signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS)
55
76
  });
@@ -101,9 +122,6 @@ function getTrustLevel(score) {
101
122
  return "BRONZE";
102
123
  }
103
124
 
104
- // src/version.ts
105
- var SDK_VERSION = "4.1.0";
106
-
107
125
  // src/well-known.ts
108
126
  var CACHE_TTL_MS = 60 * 60 * 1e3;
109
127
  var cache = /* @__PURE__ */ new Map();
@@ -132,7 +150,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
132
150
  initCheckPerformed = true;
133
151
  try {
134
152
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
135
- const response = await fetch(probeUrl, { method: "HEAD" });
153
+ const response = await sdkFetch(probeUrl, { method: "HEAD" });
136
154
  const contentType = response.headers.get("content-type") ?? "";
137
155
  if (contentType.startsWith("text/html")) {
138
156
  const message = `[VerificationGateway] apiBaseUrl '${apiBaseUrl}' returned HTML (content-type: ${contentType}). This usually means apiBaseUrl is pointing at a marketing site instead of the API. Expected: 'https://astrasync.ai/api' (prod) or 'https://staging.astrasync.ai/api' (staging).`;
@@ -146,7 +164,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
146
164
  );
147
165
  }
148
166
  if (apiKey) {
149
- const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
167
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
150
168
  headers: { Authorization: `Bearer ${apiKey}` }
151
169
  });
152
170
  if (selfCheck.status === 401) {
@@ -393,7 +411,7 @@ async function callVerifyAccessAPI(config, request) {
393
411
  headers["X-API-Key"] = config.apiKey;
394
412
  }
395
413
  try {
396
- const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
414
+ const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {
397
415
  method: "POST",
398
416
  headers,
399
417
  body: JSON.stringify(body),
@@ -638,7 +656,7 @@ async function fetchRoutes(config, counterpartyId) {
638
656
  headers["X-API-Key"] = config.apiKey;
639
657
  }
640
658
  try {
641
- const response = await fetch(
659
+ const response = await sdkFetch(
642
660
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
643
661
  { method: "GET", headers }
644
662
  );
@@ -657,7 +675,7 @@ async function reportUnregisteredAttempt(config, data) {
657
675
  ...counterpartyUrl && { counterpartyUrl },
658
676
  ...config.counterpartyId && { counterpartyId: config.counterpartyId }
659
677
  };
660
- await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
678
+ await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
661
679
  method: "POST",
662
680
  headers: { "Content-Type": "application/json" },
663
681
  body: JSON.stringify(body)
@@ -666,7 +684,7 @@ async function reportUnregisteredAttempt(config, data) {
666
684
  }
667
685
  async function reportCounterpartyPreCheckFailure(config, data) {
668
686
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
669
- await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
687
+ await sdkFetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
670
688
  method: "POST",
671
689
  headers: { "Content-Type": "application/json" },
672
690
  body: JSON.stringify(data)
@@ -778,7 +796,9 @@ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
778
796
  "x-astrasync-signature",
779
797
  "x-astrasync-secret",
780
798
  "x-hub-signature",
781
- "x-hub-signature-256"
799
+ "x-hub-signature-256",
800
+ // Short-lived verify dedupe token — must never persist in stored metadata.
801
+ "x-astra-verified-hop"
782
802
  ]);
783
803
  var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
784
804
  var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -803,7 +823,13 @@ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
803
823
  "x-agent-card-url",
804
824
  "x-agent-provider"
805
825
  ]);
806
- var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
826
+ var PLATFORM_HEADER_PREFIXES = [
827
+ "x-goog-",
828
+ "x-stainless-",
829
+ "x-anthropic-",
830
+ "x-openai-",
831
+ "sec-ch-"
832
+ ];
807
833
  var KEY_FORMAT_PREFIXES = [
808
834
  "sk-ant-api03",
809
835
  "sk-ant-api",
@@ -875,6 +901,7 @@ function sanitizeHeaders(raw) {
875
901
  const headers = {};
876
902
  const platformHeaders = {};
877
903
  let apiKeyFormat;
904
+ let cookieCount;
878
905
  let totalBytes = 0;
879
906
  let count = 0;
880
907
  if (!raw) return { headers };
@@ -889,6 +916,9 @@ function sanitizeHeaders(raw) {
889
916
  if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
890
917
  continue;
891
918
  }
919
+ if (name === "cookie") {
920
+ cookieCount = joined.split(";").filter((s) => s.trim().length > 0).length;
921
+ }
892
922
  if (isSecretHeader(name)) continue;
893
923
  let value = joined;
894
924
  if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
@@ -904,6 +934,7 @@ function sanitizeHeaders(raw) {
904
934
  const result = { headers };
905
935
  if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
906
936
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
937
+ if (cookieCount !== void 0) result.cookieCount = cookieCount;
907
938
  return result;
908
939
  }
909
940
  var CONNECTION_HEADER_SOURCES = [
@@ -913,16 +944,28 @@ var CONNECTION_HEADER_SOURCES = [
913
944
  ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
914
945
  ],
915
946
  ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
947
+ ["countryRegionName", ["cloudfront-viewer-country-region-name"]],
916
948
  ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
949
+ ["latitude", ["x-vercel-ip-latitude", "cloudfront-viewer-latitude"]],
950
+ ["longitude", ["x-vercel-ip-longitude", "cloudfront-viewer-longitude"]],
951
+ ["postalCode", ["x-vercel-ip-postal-code", "cloudfront-viewer-postal-code"]],
917
952
  ["asn", ["cloudfront-viewer-asn"]],
918
953
  ["tlsVersion", ["cloudfront-viewer-tls"]],
919
954
  ["httpVersion", ["cloudfront-viewer-http-version"]],
955
+ ["timeZone", ["x-vercel-ip-timezone", "cloudfront-viewer-time-zone"]],
956
+ ["metroCode", ["cloudfront-viewer-metro-code"]],
920
957
  // TLS fingerprints can only be computed where TLS terminates. When a CDN
921
958
  // terminates TLS in front of the merchant, its fingerprint headers ARE the
922
959
  // connection-layer truth — normalising them here is the permanent design.
923
- ["tlsFingerprint", ["cf-ja3-hash"]],
924
- ["ja4", ["cf-ja4"]]
960
+ // CloudFront delivers JA3/JA4 only via an origin request policy and only
961
+ // for HTTPS viewer connections.
962
+ ["tlsFingerprint", ["cf-ja3-hash", "cloudfront-viewer-ja3-fingerprint"]],
963
+ ["ja4", ["cf-ja4", "cloudfront-viewer-ja4-fingerprint"]],
964
+ // Header-structure fingerprint: browser/SDK-distinctive header ordering.
965
+ ["headerOrder", ["cloudfront-viewer-header-order"]],
966
+ ["headerCount", ["cloudfront-viewer-header-count"]]
925
967
  ];
968
+ var MAX_CONNECTION_VALUE_CHARS = 256;
926
969
  function deriveConnectionFromHeaders(headers) {
927
970
  if (!headers) return void 0;
928
971
  const lower = {};
@@ -935,20 +978,30 @@ function deriveConnectionFromHeaders(headers) {
935
978
  for (const source of sources) {
936
979
  const value = lower[source]?.trim();
937
980
  if (value) {
938
- connection[key] = value;
981
+ connection[key] = value.slice(0, MAX_CONNECTION_VALUE_CHARS);
939
982
  break;
940
983
  }
941
984
  }
942
985
  }
986
+ {
987
+ const addr = lower["cloudfront-viewer-address"]?.trim();
988
+ const portMatch = addr?.match(/:(\d+)$/);
989
+ if (portMatch) connection.sourcePort = portMatch[1];
990
+ if (!connection.ip && addr) connection.ip = addr.replace(/:\d+$/, "");
991
+ }
943
992
  if (!connection.ip) {
944
993
  const xff = lower["x-forwarded-for"];
945
994
  const client = xff?.split(",")[0]?.trim();
946
995
  if (client) connection.ip = client;
947
996
  }
997
+ if (!connection.tlsCipher) {
998
+ const cipher = connection.tlsVersion?.split(":")[1];
999
+ if (cipher) connection.tlsCipher = cipher;
1000
+ }
948
1001
  return Object.keys(connection).length > 0 ? connection : void 0;
949
1002
  }
950
1003
  function buildSdkObservedMetadata(rawHeaders) {
951
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1004
+ const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(rawHeaders);
952
1005
  const flat = {};
953
1006
  if (rawHeaders) {
954
1007
  for (const name of Object.keys(rawHeaders)) {
@@ -963,6 +1016,7 @@ function buildSdkObservedMetadata(rawHeaders) {
963
1016
  ...apiKeyFormat && { apiKeyFormat },
964
1017
  ...platformHeaders && { platformHeaders },
965
1018
  ...connection && { connection },
1019
+ ...cookieCount !== void 0 && { cookieCount },
966
1020
  schemaVersion: CAPTURE_SCHEMA_VERSION
967
1021
  };
968
1022
  }
@@ -1007,7 +1061,7 @@ async function fetchEdgeConfig(config, counterpartyId, opts) {
1007
1061
  }
1008
1062
  if (opts?.etag) headers["If-None-Match"] = opts.etag;
1009
1063
  try {
1010
- const response = await fetch(
1064
+ const response = await sdkFetch(
1011
1065
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1012
1066
  { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1013
1067
  );
@@ -1119,12 +1173,43 @@ var PLATFORM_AGENT_SIGNATURES = [
1119
1173
  uaPatterns: [/perplexity/i],
1120
1174
  agentCardPatterns: [/perplexity\.ai/i],
1121
1175
  apiKeyFormatPatterns: [/^pplx-/i]
1176
+ },
1177
+ // Agentic browsers — versioned-token patterns ONLY (a stock Chrome UA must
1178
+ // never match). Distinct vendor keys (not 'chatgpt'/'perplexity'): the
1179
+ // browser product is a different surface than the assistant service, and
1180
+ // the DB registry mirrors one row per vendor. These sit after the primary
1181
+ // vendor entries so a UA carrying an explicit vendor token keeps its
1182
+ // primary attribution; a browser-shaped UA with the agentic token is
1183
+ // caught here BEFORE classify's human tier, which is what makes these
1184
+ // visits observable at all. Token drift is real (some builds mimic Chrome
1185
+ // outright) — re-verify against captured events when a new browser ships,
1186
+ // and check `sec-ch-ua` platformHeaders for the real brand.
1187
+ {
1188
+ vendor: "chatgpt-atlas",
1189
+ displayName: "ChatGPT Atlas browser",
1190
+ uaPatterns: [/\batlas\/\d/i],
1191
+ headerPatterns: [/^sec-ch-ua: .*atlas/i]
1192
+ },
1193
+ {
1194
+ vendor: "perplexity-comet",
1195
+ displayName: "Perplexity Comet browser",
1196
+ uaPatterns: [/\bcomet\/\d/i],
1197
+ headerPatterns: [/^sec-ch-ua: .*comet/i]
1198
+ },
1199
+ // Our own SDK self-identifies (sdkFetch stamps this UA) — first-party
1200
+ // traffic must never pollute the unknown/anonymous pool it exists to audit.
1201
+ {
1202
+ vendor: "astrasync-sdk",
1203
+ displayName: "AstraSync SDK",
1204
+ uaPatterns: [/^astrasync-sdk\//i]
1122
1205
  }
1123
1206
  ];
1124
1207
  function matchPlatformSignature(signatures, input) {
1125
1208
  const ua = input.userAgent ?? "";
1126
1209
  const cardUrl = input.agentCardUrl ?? "";
1127
1210
  const apiKeyFormat = input.apiKeyFormat ?? "";
1211
+ const ja4 = input.ja4 ?? "";
1212
+ const asn = input.asn ?? "";
1128
1213
  const headerLines = [];
1129
1214
  if (input.platformHeaders) {
1130
1215
  for (const [name, value] of Object.entries(input.platformHeaders)) {
@@ -1136,7 +1221,9 @@ function matchPlatformSignature(signatures, input) {
1136
1221
  const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1137
1222
  const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1138
1223
  const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1139
- if (uaMatch || cardMatch || keyMatch || headerMatch) {
1224
+ const ja4Match = ja4.length > 0 && sig.ja4Patterns?.some((p) => p.test(ja4));
1225
+ const asnMatch = asn.length > 0 && sig.asnList?.includes(asn);
1226
+ if (uaMatch || cardMatch || keyMatch || headerMatch || ja4Match || asnMatch) {
1140
1227
  const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1141
1228
  const cardHost = (() => {
1142
1229
  try {
@@ -1145,7 +1232,7 @@ function matchPlatformSignature(signatures, input) {
1145
1232
  return "";
1146
1233
  }
1147
1234
  })();
1148
- const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1235
+ const keyBasis = uaBase || cardHost || apiKeyFormat || ja4 || asn && `asn${asn}` || "unknown";
1149
1236
  return {
1150
1237
  vendor: sig.vendor,
1151
1238
  fingerprintKey: `${sig.vendor}:${keyBasis}`,
@@ -1352,6 +1439,31 @@ function annotateApprovalRequired(result) {
1352
1439
  result.denialReasons = [APPROVAL_REASON, ...result.denialReasons ?? []];
1353
1440
  }
1354
1441
 
1442
+ // src/verified-hop.ts
1443
+ var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
1444
+ var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
1445
+ function parseVerifiedHop(value) {
1446
+ if (!value) return null;
1447
+ const parts = value.split(";");
1448
+ if (parts.length !== 3) return null;
1449
+ const [astraId, sessionId, checkedAtRaw] = parts;
1450
+ if (!astraId) return null;
1451
+ const checkedAt = Number(checkedAtRaw);
1452
+ if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
1453
+ return {
1454
+ astraId,
1455
+ ...sessionId ? { sessionId } : {},
1456
+ checkedAt
1457
+ };
1458
+ }
1459
+ function isVerifiedHopValidFor(marker, expectedAstraId, opts = {}) {
1460
+ if (!marker) return false;
1461
+ if (marker.astraId !== expectedAstraId) return false;
1462
+ const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;
1463
+ const now = opts.now ?? Date.now();
1464
+ return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;
1465
+ }
1466
+
1355
1467
  // src/adapters/nextjs.ts
1356
1468
  function escapeHtml(value) {
1357
1469
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -1590,6 +1702,8 @@ function createMiddleware(options) {
1590
1702
  enableRuntimeChallenge = true,
1591
1703
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
1592
1704
  anonymousBeacon = true,
1705
+ trustVerifiedHop = false,
1706
+ verifiedHopMaxAgeMs,
1593
1707
  ...config
1594
1708
  } = options;
1595
1709
  const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
@@ -1652,6 +1766,14 @@ function createMiddleware(options) {
1652
1766
  return NextResponse.next();
1653
1767
  }
1654
1768
  const credentials = extractCredentialsFromNextRequest(request);
1769
+ if (trustVerifiedHop && credentials.astraId) {
1770
+ const marker = parseVerifiedHop(request.headers.get(MCP_VERIFIED_HOP_HEADER) ?? void 0);
1771
+ if (isVerifiedHopValidFor(marker, credentials.astraId, {
1772
+ ...verifiedHopMaxAgeMs !== void 0 ? { maxAgeMs: verifiedHopMaxAgeMs } : {}
1773
+ })) {
1774
+ return NextResponse.next();
1775
+ }
1776
+ }
1655
1777
  const counterpartyUrl = config.counterpartyUrl || request.nextUrl.origin;
1656
1778
  const pdlssPair = resolveNextPdlss(request, routeConfig);
1657
1779
  const purpose = pdlssPair.purpose;