@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,3 +1,24 @@
1
+ // src/version.ts
2
+ var SDK_VERSION = "4.4.0";
3
+
4
+ // src/http.ts
5
+ var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
6
+ function withUserAgent(initHeaders) {
7
+ if (!initHeaders) return { "user-agent": SDK_USER_AGENT };
8
+ if (initHeaders instanceof Headers) {
9
+ const copy = new Headers(initHeaders);
10
+ if (!copy.has("user-agent")) copy.set("user-agent", SDK_USER_AGENT);
11
+ return copy;
12
+ }
13
+ if (Array.isArray(initHeaders)) {
14
+ const has2 = initHeaders.some(([name]) => name.toLowerCase() === "user-agent");
15
+ return has2 ? initHeaders : [...initHeaders, ["user-agent", SDK_USER_AGENT]];
16
+ }
17
+ const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === "user-agent");
18
+ return has ? initHeaders : { "user-agent": SDK_USER_AGENT, ...initHeaders };
19
+ }
20
+ var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
21
+
1
22
  // src/adapters/stepup-poll.ts
2
23
  var DEFAULT_POLL_INTERVAL_MS = 3e3;
3
24
  var MIN_POLL_INTERVAL_MS = 250;
@@ -12,7 +33,7 @@ function resolvePollUrl(config, pollUrl) {
12
33
  async function pollStepUpStatus(config, approval) {
13
34
  if (!approval.pollUrl) return null;
14
35
  try {
15
- const res = await fetch(resolvePollUrl(config, approval.pollUrl), {
36
+ const res = await sdkFetch(resolvePollUrl(config, approval.pollUrl), {
16
37
  method: "GET",
17
38
  signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS)
18
39
  });
@@ -64,9 +85,6 @@ function getTrustLevel(score) {
64
85
  return "BRONZE";
65
86
  }
66
87
 
67
- // src/version.ts
68
- var SDK_VERSION = "4.1.0";
69
-
70
88
  // src/well-known.ts
71
89
  var CACHE_TTL_MS = 60 * 60 * 1e3;
72
90
  var cache = /* @__PURE__ */ new Map();
@@ -77,7 +95,7 @@ function wellKnownUrl(apiBaseUrl) {
77
95
  }
78
96
  async function fetchWellKnown(apiBaseUrl) {
79
97
  const url = wellKnownUrl(apiBaseUrl);
80
- const response = await fetch(url, {
98
+ const response = await sdkFetch(url, {
81
99
  method: "GET",
82
100
  headers: { Accept: "application/json" },
83
101
  signal: AbortSignal.timeout(5e3)
@@ -147,7 +165,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
147
165
  initCheckPerformed = true;
148
166
  try {
149
167
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
150
- const response = await fetch(probeUrl, { method: "HEAD" });
168
+ const response = await sdkFetch(probeUrl, { method: "HEAD" });
151
169
  const contentType = response.headers.get("content-type") ?? "";
152
170
  if (contentType.startsWith("text/html")) {
153
171
  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).`;
@@ -161,7 +179,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
161
179
  );
162
180
  }
163
181
  if (apiKey) {
164
- const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
182
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
165
183
  headers: { Authorization: `Bearer ${apiKey}` }
166
184
  });
167
185
  if (selfCheck.status === 401) {
@@ -408,7 +426,7 @@ async function callVerifyAccessAPI(config, request) {
408
426
  headers["X-API-Key"] = config.apiKey;
409
427
  }
410
428
  try {
411
- const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
429
+ const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {
412
430
  method: "POST",
413
431
  headers,
414
432
  body: JSON.stringify(body),
@@ -651,7 +669,7 @@ async function recordDecision(config, sessionId, decision, reason) {
651
669
  headers["Authorization"] = `Bearer ${config.apiKey}`;
652
670
  headers["X-API-Key"] = config.apiKey;
653
671
  }
654
- await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
672
+ await sdkFetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
655
673
  method: "POST",
656
674
  headers,
657
675
  body: JSON.stringify({ decision, reason })
@@ -666,7 +684,7 @@ async function fetchRoutes(config, counterpartyId) {
666
684
  headers["X-API-Key"] = config.apiKey;
667
685
  }
668
686
  try {
669
- const response = await fetch(
687
+ const response = await sdkFetch(
670
688
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
671
689
  { method: "GET", headers }
672
690
  );
@@ -685,7 +703,7 @@ async function reportUnregisteredAttempt(config, data) {
685
703
  ...counterpartyUrl && { counterpartyUrl },
686
704
  ...config.counterpartyId && { counterpartyId: config.counterpartyId }
687
705
  };
688
- await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
706
+ await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
689
707
  method: "POST",
690
708
  headers: { "Content-Type": "application/json" },
691
709
  body: JSON.stringify(body)
@@ -694,7 +712,7 @@ async function reportUnregisteredAttempt(config, data) {
694
712
  }
695
713
  async function reportCounterpartyPreCheckFailure(config, data) {
696
714
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
697
- await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
715
+ await sdkFetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
698
716
  method: "POST",
699
717
  headers: { "Content-Type": "application/json" },
700
718
  body: JSON.stringify(data)
@@ -748,6 +766,31 @@ function extractHttpCredentials(headers) {
748
766
  return credentials;
749
767
  }
750
768
 
769
+ // src/verified-hop.ts
770
+ var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
771
+ var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
772
+ function parseVerifiedHop(value) {
773
+ if (!value) return null;
774
+ const parts = value.split(";");
775
+ if (parts.length !== 3) return null;
776
+ const [astraId, sessionId, checkedAtRaw] = parts;
777
+ if (!astraId) return null;
778
+ const checkedAt = Number(checkedAtRaw);
779
+ if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
780
+ return {
781
+ astraId,
782
+ ...sessionId ? { sessionId } : {},
783
+ checkedAt
784
+ };
785
+ }
786
+ function isVerifiedHopValidFor(marker, expectedAstraId, opts = {}) {
787
+ if (!marker) return false;
788
+ if (marker.astraId !== expectedAstraId) return false;
789
+ const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;
790
+ const now = opts.now ?? Date.now();
791
+ return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;
792
+ }
793
+
751
794
  // src/adapters/http-pdlss.ts
752
795
  var HTTP_METHOD_ACTION_TABLE = {
753
796
  GET: "data.read",
@@ -898,7 +941,9 @@ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
898
941
  "x-astrasync-signature",
899
942
  "x-astrasync-secret",
900
943
  "x-hub-signature",
901
- "x-hub-signature-256"
944
+ "x-hub-signature-256",
945
+ // Short-lived verify dedupe token — must never persist in stored metadata.
946
+ "x-astra-verified-hop"
902
947
  ]);
903
948
  var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
904
949
  var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -923,7 +968,13 @@ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
923
968
  "x-agent-card-url",
924
969
  "x-agent-provider"
925
970
  ]);
926
- var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
971
+ var PLATFORM_HEADER_PREFIXES = [
972
+ "x-goog-",
973
+ "x-stainless-",
974
+ "x-anthropic-",
975
+ "x-openai-",
976
+ "sec-ch-"
977
+ ];
927
978
  var KEY_FORMAT_PREFIXES = [
928
979
  "sk-ant-api03",
929
980
  "sk-ant-api",
@@ -995,6 +1046,7 @@ function sanitizeHeaders(raw) {
995
1046
  const headers = {};
996
1047
  const platformHeaders = {};
997
1048
  let apiKeyFormat;
1049
+ let cookieCount;
998
1050
  let totalBytes = 0;
999
1051
  let count = 0;
1000
1052
  if (!raw) return { headers };
@@ -1009,6 +1061,9 @@ function sanitizeHeaders(raw) {
1009
1061
  if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
1010
1062
  continue;
1011
1063
  }
1064
+ if (name === "cookie") {
1065
+ cookieCount = joined.split(";").filter((s) => s.trim().length > 0).length;
1066
+ }
1012
1067
  if (isSecretHeader(name)) continue;
1013
1068
  let value = joined;
1014
1069
  if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
@@ -1024,6 +1079,7 @@ function sanitizeHeaders(raw) {
1024
1079
  const result = { headers };
1025
1080
  if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
1026
1081
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
1082
+ if (cookieCount !== void 0) result.cookieCount = cookieCount;
1027
1083
  return result;
1028
1084
  }
1029
1085
  var CONNECTION_HEADER_SOURCES = [
@@ -1033,16 +1089,28 @@ var CONNECTION_HEADER_SOURCES = [
1033
1089
  ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
1034
1090
  ],
1035
1091
  ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
1092
+ ["countryRegionName", ["cloudfront-viewer-country-region-name"]],
1036
1093
  ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
1094
+ ["latitude", ["x-vercel-ip-latitude", "cloudfront-viewer-latitude"]],
1095
+ ["longitude", ["x-vercel-ip-longitude", "cloudfront-viewer-longitude"]],
1096
+ ["postalCode", ["x-vercel-ip-postal-code", "cloudfront-viewer-postal-code"]],
1037
1097
  ["asn", ["cloudfront-viewer-asn"]],
1038
1098
  ["tlsVersion", ["cloudfront-viewer-tls"]],
1039
1099
  ["httpVersion", ["cloudfront-viewer-http-version"]],
1100
+ ["timeZone", ["x-vercel-ip-timezone", "cloudfront-viewer-time-zone"]],
1101
+ ["metroCode", ["cloudfront-viewer-metro-code"]],
1040
1102
  // TLS fingerprints can only be computed where TLS terminates. When a CDN
1041
1103
  // terminates TLS in front of the merchant, its fingerprint headers ARE the
1042
1104
  // connection-layer truth — normalising them here is the permanent design.
1043
- ["tlsFingerprint", ["cf-ja3-hash"]],
1044
- ["ja4", ["cf-ja4"]]
1105
+ // CloudFront delivers JA3/JA4 only via an origin request policy and only
1106
+ // for HTTPS viewer connections.
1107
+ ["tlsFingerprint", ["cf-ja3-hash", "cloudfront-viewer-ja3-fingerprint"]],
1108
+ ["ja4", ["cf-ja4", "cloudfront-viewer-ja4-fingerprint"]],
1109
+ // Header-structure fingerprint: browser/SDK-distinctive header ordering.
1110
+ ["headerOrder", ["cloudfront-viewer-header-order"]],
1111
+ ["headerCount", ["cloudfront-viewer-header-count"]]
1045
1112
  ];
1113
+ var MAX_CONNECTION_VALUE_CHARS = 256;
1046
1114
  function deriveConnectionFromHeaders(headers) {
1047
1115
  if (!headers) return void 0;
1048
1116
  const lower = {};
@@ -1055,20 +1123,30 @@ function deriveConnectionFromHeaders(headers) {
1055
1123
  for (const source of sources) {
1056
1124
  const value = lower[source]?.trim();
1057
1125
  if (value) {
1058
- connection[key] = value;
1126
+ connection[key] = value.slice(0, MAX_CONNECTION_VALUE_CHARS);
1059
1127
  break;
1060
1128
  }
1061
1129
  }
1062
1130
  }
1131
+ {
1132
+ const addr = lower["cloudfront-viewer-address"]?.trim();
1133
+ const portMatch = addr?.match(/:(\d+)$/);
1134
+ if (portMatch) connection.sourcePort = portMatch[1];
1135
+ if (!connection.ip && addr) connection.ip = addr.replace(/:\d+$/, "");
1136
+ }
1063
1137
  if (!connection.ip) {
1064
1138
  const xff = lower["x-forwarded-for"];
1065
1139
  const client = xff?.split(",")[0]?.trim();
1066
1140
  if (client) connection.ip = client;
1067
1141
  }
1142
+ if (!connection.tlsCipher) {
1143
+ const cipher = connection.tlsVersion?.split(":")[1];
1144
+ if (cipher) connection.tlsCipher = cipher;
1145
+ }
1068
1146
  return Object.keys(connection).length > 0 ? connection : void 0;
1069
1147
  }
1070
1148
  function buildSdkObservedMetadata(rawHeaders) {
1071
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1149
+ const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(rawHeaders);
1072
1150
  const flat = {};
1073
1151
  if (rawHeaders) {
1074
1152
  for (const name of Object.keys(rawHeaders)) {
@@ -1083,6 +1161,7 @@ function buildSdkObservedMetadata(rawHeaders) {
1083
1161
  ...apiKeyFormat && { apiKeyFormat },
1084
1162
  ...platformHeaders && { platformHeaders },
1085
1163
  ...connection && { connection },
1164
+ ...cookieCount !== void 0 && { cookieCount },
1086
1165
  schemaVersion: CAPTURE_SCHEMA_VERSION
1087
1166
  };
1088
1167
  }
@@ -1127,7 +1206,7 @@ async function fetchEdgeConfig(config, counterpartyId, opts) {
1127
1206
  }
1128
1207
  if (opts?.etag) headers["If-None-Match"] = opts.etag;
1129
1208
  try {
1130
- const response = await fetch(
1209
+ const response = await sdkFetch(
1131
1210
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1132
1211
  { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1133
1212
  );
@@ -1239,12 +1318,43 @@ var PLATFORM_AGENT_SIGNATURES = [
1239
1318
  uaPatterns: [/perplexity/i],
1240
1319
  agentCardPatterns: [/perplexity\.ai/i],
1241
1320
  apiKeyFormatPatterns: [/^pplx-/i]
1321
+ },
1322
+ // Agentic browsers — versioned-token patterns ONLY (a stock Chrome UA must
1323
+ // never match). Distinct vendor keys (not 'chatgpt'/'perplexity'): the
1324
+ // browser product is a different surface than the assistant service, and
1325
+ // the DB registry mirrors one row per vendor. These sit after the primary
1326
+ // vendor entries so a UA carrying an explicit vendor token keeps its
1327
+ // primary attribution; a browser-shaped UA with the agentic token is
1328
+ // caught here BEFORE classify's human tier, which is what makes these
1329
+ // visits observable at all. Token drift is real (some builds mimic Chrome
1330
+ // outright) — re-verify against captured events when a new browser ships,
1331
+ // and check `sec-ch-ua` platformHeaders for the real brand.
1332
+ {
1333
+ vendor: "chatgpt-atlas",
1334
+ displayName: "ChatGPT Atlas browser",
1335
+ uaPatterns: [/\batlas\/\d/i],
1336
+ headerPatterns: [/^sec-ch-ua: .*atlas/i]
1337
+ },
1338
+ {
1339
+ vendor: "perplexity-comet",
1340
+ displayName: "Perplexity Comet browser",
1341
+ uaPatterns: [/\bcomet\/\d/i],
1342
+ headerPatterns: [/^sec-ch-ua: .*comet/i]
1343
+ },
1344
+ // Our own SDK self-identifies (sdkFetch stamps this UA) — first-party
1345
+ // traffic must never pollute the unknown/anonymous pool it exists to audit.
1346
+ {
1347
+ vendor: "astrasync-sdk",
1348
+ displayName: "AstraSync SDK",
1349
+ uaPatterns: [/^astrasync-sdk\//i]
1242
1350
  }
1243
1351
  ];
1244
1352
  function matchPlatformSignature(signatures, input) {
1245
1353
  const ua = input.userAgent ?? "";
1246
1354
  const cardUrl = input.agentCardUrl ?? "";
1247
1355
  const apiKeyFormat = input.apiKeyFormat ?? "";
1356
+ const ja4 = input.ja4 ?? "";
1357
+ const asn = input.asn ?? "";
1248
1358
  const headerLines = [];
1249
1359
  if (input.platformHeaders) {
1250
1360
  for (const [name, value] of Object.entries(input.platformHeaders)) {
@@ -1256,7 +1366,9 @@ function matchPlatformSignature(signatures, input) {
1256
1366
  const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1257
1367
  const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1258
1368
  const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1259
- if (uaMatch || cardMatch || keyMatch || headerMatch) {
1369
+ const ja4Match = ja4.length > 0 && sig.ja4Patterns?.some((p) => p.test(ja4));
1370
+ const asnMatch = asn.length > 0 && sig.asnList?.includes(asn);
1371
+ if (uaMatch || cardMatch || keyMatch || headerMatch || ja4Match || asnMatch) {
1260
1372
  const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1261
1373
  const cardHost = (() => {
1262
1374
  try {
@@ -1265,7 +1377,7 @@ function matchPlatformSignature(signatures, input) {
1265
1377
  return "";
1266
1378
  }
1267
1379
  })();
1268
- const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1380
+ const keyBasis = uaBase || cardHost || apiKeyFormat || ja4 || asn && `asn${asn}` || "unknown";
1269
1381
  return {
1270
1382
  vendor: sig.vendor,
1271
1383
  fingerprintKey: `${sig.vendor}:${keyBasis}`,
@@ -1500,6 +1612,8 @@ function createMiddleware(options) {
1500
1612
  failOnError = "open",
1501
1613
  caseInsensitiveRouteMatch = false,
1502
1614
  anonymousBeacon = true,
1615
+ trustVerifiedHop = false,
1616
+ verifiedHopMaxAgeMs,
1503
1617
  ...config
1504
1618
  } = options;
1505
1619
  if (config.apiBaseUrl) {
@@ -1592,6 +1706,16 @@ function createMiddleware(options) {
1592
1706
  beaconPassThrough?.(req);
1593
1707
  return next();
1594
1708
  }
1709
+ if (trustVerifiedHop && credentials.astraId) {
1710
+ const hopRaw = req.headers[MCP_VERIFIED_HOP_HEADER.toLowerCase()];
1711
+ const hopValue = typeof hopRaw === "string" ? hopRaw : Array.isArray(hopRaw) ? hopRaw[0] : void 0;
1712
+ const marker = parseVerifiedHop(hopValue);
1713
+ if (isVerifiedHopValidFor(marker, credentials.astraId, {
1714
+ ...verifiedHopMaxAgeMs !== void 0 ? { maxAgeMs: verifiedHopMaxAgeMs } : {}
1715
+ })) {
1716
+ return next();
1717
+ }
1718
+ }
1595
1719
  const pdlssPair = resolveRequestPdlss(
1596
1720
  req,
1597
1721
  routeConfig,