@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
package/dist/index.js CHANGED
@@ -45,9 +45,11 @@ __export(src_exports, {
45
45
  MAX_HEADER_VALUE_BYTES: () => MAX_HEADER_VALUE_BYTES,
46
46
  MAX_TOTAL_BYTES: () => MAX_TOTAL_BYTES,
47
47
  PLATFORM_AGENT_SIGNATURES: () => PLATFORM_AGENT_SIGNATURES,
48
+ RUNTIME_SIGNATURES: () => RUNTIME_SIGNATURES,
48
49
  RegistrationDeniedError: () => RegistrationDeniedError,
49
50
  RegistrationExpiredError: () => RegistrationExpiredError,
50
51
  RegistrationTimeoutError: () => RegistrationTimeoutError,
52
+ SDK_USER_AGENT: () => SDK_USER_AGENT,
51
53
  TRUST_LEVEL_RANGES: () => TRUST_LEVEL_RANGES,
52
54
  VERSION: () => SDK_VERSION,
53
55
  _resetEdgeConfigCache: () => _resetEdgeConfigCache,
@@ -61,6 +63,7 @@ __export(src_exports, {
61
63
  degradeToObserve: () => degradeToObserve,
62
64
  deriveConnectionFromHeaders: () => deriveConnectionFromHeaders,
63
65
  detectPlatformFingerprint: () => detectPlatformFingerprint,
66
+ detectRuntime: () => detectRuntime,
64
67
  determineAccessLevel: () => determineAccessLevel,
65
68
  express: () => express_exports,
66
69
  extractApiKeyFormat: () => extractApiKeyFormat,
@@ -89,6 +92,7 @@ __export(src_exports, {
89
92
  reportUnregisteredAttempt: () => reportUnregisteredAttempt,
90
93
  sanitizeHeaders: () => sanitizeHeaders,
91
94
  sdk: () => sdk_exports,
95
+ sdkFetch: () => sdkFetch,
92
96
  setMcpMeta: () => setMcpMeta,
93
97
  transport: () => transport_exports,
94
98
  verify: () => verify
@@ -217,7 +221,25 @@ function getCapabilities(accessLevel) {
217
221
  }
218
222
 
219
223
  // src/version.ts
220
- var SDK_VERSION = "4.1.0";
224
+ var SDK_VERSION = "4.4.0";
225
+
226
+ // src/http.ts
227
+ var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
228
+ function withUserAgent(initHeaders) {
229
+ if (!initHeaders) return { "user-agent": SDK_USER_AGENT };
230
+ if (initHeaders instanceof Headers) {
231
+ const copy = new Headers(initHeaders);
232
+ if (!copy.has("user-agent")) copy.set("user-agent", SDK_USER_AGENT);
233
+ return copy;
234
+ }
235
+ if (Array.isArray(initHeaders)) {
236
+ const has2 = initHeaders.some(([name]) => name.toLowerCase() === "user-agent");
237
+ return has2 ? initHeaders : [...initHeaders, ["user-agent", SDK_USER_AGENT]];
238
+ }
239
+ const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === "user-agent");
240
+ return has ? initHeaders : { "user-agent": SDK_USER_AGENT, ...initHeaders };
241
+ }
242
+ var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
221
243
 
222
244
  // src/well-known.ts
223
245
  var CACHE_TTL_MS = 60 * 60 * 1e3;
@@ -229,7 +251,7 @@ function wellKnownUrl(apiBaseUrl) {
229
251
  }
230
252
  async function fetchWellKnown(apiBaseUrl) {
231
253
  const url = wellKnownUrl(apiBaseUrl);
232
- const response = await fetch(url, {
254
+ const response = await sdkFetch(url, {
233
255
  method: "GET",
234
256
  headers: { Accept: "application/json" },
235
257
  signal: AbortSignal.timeout(5e3)
@@ -299,7 +321,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
299
321
  initCheckPerformed = true;
300
322
  try {
301
323
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
302
- const response = await fetch(probeUrl, { method: "HEAD" });
324
+ const response = await sdkFetch(probeUrl, { method: "HEAD" });
303
325
  const contentType = response.headers.get("content-type") ?? "";
304
326
  if (contentType.startsWith("text/html")) {
305
327
  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).`;
@@ -313,7 +335,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
313
335
  );
314
336
  }
315
337
  if (apiKey) {
316
- const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
338
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
317
339
  headers: { Authorization: `Bearer ${apiKey}` }
318
340
  });
319
341
  if (selfCheck.status === 401) {
@@ -566,7 +588,7 @@ async function callVerifyAccessAPI(config, request) {
566
588
  headers["X-API-Key"] = config.apiKey;
567
589
  }
568
590
  try {
569
- const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
591
+ const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {
570
592
  method: "POST",
571
593
  headers,
572
594
  body: JSON.stringify(body),
@@ -809,7 +831,7 @@ async function recordDecision(config, sessionId, decision, reason) {
809
831
  headers["Authorization"] = `Bearer ${config.apiKey}`;
810
832
  headers["X-API-Key"] = config.apiKey;
811
833
  }
812
- await fetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
834
+ await sdkFetch(`${config.apiBaseUrl}/agents/verify-access/${sessionId}/decision`, {
813
835
  method: "POST",
814
836
  headers,
815
837
  body: JSON.stringify({ decision, reason })
@@ -827,7 +849,7 @@ async function reportAttempt(config, report) {
827
849
  ...report,
828
850
  ...!report.counterpartyId && config.counterpartyId && { counterpartyId: config.counterpartyId }
829
851
  };
830
- await fetch(`${apiBaseUrl}/agents/verify-access/attempt-report`, {
852
+ await sdkFetch(`${apiBaseUrl}/agents/verify-access/attempt-report`, {
831
853
  method: "POST",
832
854
  headers,
833
855
  body: JSON.stringify(body)
@@ -842,7 +864,7 @@ async function fetchRoutes(config, counterpartyId) {
842
864
  headers["X-API-Key"] = config.apiKey;
843
865
  }
844
866
  try {
845
- const response = await fetch(
867
+ const response = await sdkFetch(
846
868
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
847
869
  { method: "GET", headers }
848
870
  );
@@ -861,7 +883,7 @@ async function reportUnregisteredAttempt(config, data) {
861
883
  ...counterpartyUrl && { counterpartyUrl },
862
884
  ...config.counterpartyId && { counterpartyId: config.counterpartyId }
863
885
  };
864
- await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
886
+ await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
865
887
  method: "POST",
866
888
  headers: { "Content-Type": "application/json" },
867
889
  body: JSON.stringify(body)
@@ -870,7 +892,7 @@ async function reportUnregisteredAttempt(config, data) {
870
892
  }
871
893
  async function reportCounterpartyPreCheckFailure(config, data) {
872
894
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
873
- await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
895
+ await sdkFetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
874
896
  method: "POST",
875
897
  headers: { "Content-Type": "application/json" },
876
898
  body: JSON.stringify(data)
@@ -904,7 +926,7 @@ function resolvePollUrl(config, pollUrl) {
904
926
  async function pollStepUpStatus(config, approval) {
905
927
  if (!approval.pollUrl) return null;
906
928
  try {
907
- const res = await fetch(resolvePollUrl(config, approval.pollUrl), {
929
+ const res = await sdkFetch(resolvePollUrl(config, approval.pollUrl), {
908
930
  method: "GET",
909
931
  signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS)
910
932
  });
@@ -1087,6 +1109,34 @@ function extractHttpCredentials(headers) {
1087
1109
  return credentials;
1088
1110
  }
1089
1111
 
1112
+ // src/verified-hop.ts
1113
+ var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
1114
+ var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
1115
+ function serializeVerifiedHop(marker) {
1116
+ return `${marker.astraId};${marker.sessionId ?? ""};${marker.checkedAt}`;
1117
+ }
1118
+ function parseVerifiedHop(value) {
1119
+ if (!value) return null;
1120
+ const parts = value.split(";");
1121
+ if (parts.length !== 3) return null;
1122
+ const [astraId, sessionId, checkedAtRaw] = parts;
1123
+ if (!astraId) return null;
1124
+ const checkedAt = Number(checkedAtRaw);
1125
+ if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
1126
+ return {
1127
+ astraId,
1128
+ ...sessionId ? { sessionId } : {},
1129
+ checkedAt
1130
+ };
1131
+ }
1132
+ function isVerifiedHopValidFor(marker, expectedAstraId, opts = {}) {
1133
+ if (!marker) return false;
1134
+ if (marker.astraId !== expectedAstraId) return false;
1135
+ const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;
1136
+ const now = opts.now ?? Date.now();
1137
+ return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;
1138
+ }
1139
+
1090
1140
  // src/adapters/http-pdlss.ts
1091
1141
  var HTTP_METHOD_ACTION_TABLE = {
1092
1142
  GET: "data.read",
@@ -1224,7 +1274,9 @@ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
1224
1274
  "x-astrasync-signature",
1225
1275
  "x-astrasync-secret",
1226
1276
  "x-hub-signature",
1227
- "x-hub-signature-256"
1277
+ "x-hub-signature-256",
1278
+ // Short-lived verify dedupe token — must never persist in stored metadata.
1279
+ "x-astra-verified-hop"
1228
1280
  ]);
1229
1281
  var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
1230
1282
  var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -1249,7 +1301,13 @@ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
1249
1301
  "x-agent-card-url",
1250
1302
  "x-agent-provider"
1251
1303
  ]);
1252
- var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
1304
+ var PLATFORM_HEADER_PREFIXES = [
1305
+ "x-goog-",
1306
+ "x-stainless-",
1307
+ "x-anthropic-",
1308
+ "x-openai-",
1309
+ "sec-ch-"
1310
+ ];
1253
1311
  var KEY_FORMAT_PREFIXES = [
1254
1312
  "sk-ant-api03",
1255
1313
  "sk-ant-api",
@@ -1321,6 +1379,7 @@ function sanitizeHeaders(raw) {
1321
1379
  const headers = {};
1322
1380
  const platformHeaders = {};
1323
1381
  let apiKeyFormat;
1382
+ let cookieCount;
1324
1383
  let totalBytes = 0;
1325
1384
  let count = 0;
1326
1385
  if (!raw) return { headers };
@@ -1335,6 +1394,9 @@ function sanitizeHeaders(raw) {
1335
1394
  if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
1336
1395
  continue;
1337
1396
  }
1397
+ if (name === "cookie") {
1398
+ cookieCount = joined.split(";").filter((s) => s.trim().length > 0).length;
1399
+ }
1338
1400
  if (isSecretHeader(name)) continue;
1339
1401
  let value = joined;
1340
1402
  if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
@@ -1350,6 +1412,7 @@ function sanitizeHeaders(raw) {
1350
1412
  const result = { headers };
1351
1413
  if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
1352
1414
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
1415
+ if (cookieCount !== void 0) result.cookieCount = cookieCount;
1353
1416
  return result;
1354
1417
  }
1355
1418
  function extractPlatformHeaders(sanitized) {
@@ -1367,16 +1430,28 @@ var CONNECTION_HEADER_SOURCES = [
1367
1430
  ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
1368
1431
  ],
1369
1432
  ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
1433
+ ["countryRegionName", ["cloudfront-viewer-country-region-name"]],
1370
1434
  ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
1435
+ ["latitude", ["x-vercel-ip-latitude", "cloudfront-viewer-latitude"]],
1436
+ ["longitude", ["x-vercel-ip-longitude", "cloudfront-viewer-longitude"]],
1437
+ ["postalCode", ["x-vercel-ip-postal-code", "cloudfront-viewer-postal-code"]],
1371
1438
  ["asn", ["cloudfront-viewer-asn"]],
1372
1439
  ["tlsVersion", ["cloudfront-viewer-tls"]],
1373
1440
  ["httpVersion", ["cloudfront-viewer-http-version"]],
1441
+ ["timeZone", ["x-vercel-ip-timezone", "cloudfront-viewer-time-zone"]],
1442
+ ["metroCode", ["cloudfront-viewer-metro-code"]],
1374
1443
  // TLS fingerprints can only be computed where TLS terminates. When a CDN
1375
1444
  // terminates TLS in front of the merchant, its fingerprint headers ARE the
1376
1445
  // connection-layer truth — normalising them here is the permanent design.
1377
- ["tlsFingerprint", ["cf-ja3-hash"]],
1378
- ["ja4", ["cf-ja4"]]
1446
+ // CloudFront delivers JA3/JA4 only via an origin request policy and only
1447
+ // for HTTPS viewer connections.
1448
+ ["tlsFingerprint", ["cf-ja3-hash", "cloudfront-viewer-ja3-fingerprint"]],
1449
+ ["ja4", ["cf-ja4", "cloudfront-viewer-ja4-fingerprint"]],
1450
+ // Header-structure fingerprint: browser/SDK-distinctive header ordering.
1451
+ ["headerOrder", ["cloudfront-viewer-header-order"]],
1452
+ ["headerCount", ["cloudfront-viewer-header-count"]]
1379
1453
  ];
1454
+ var MAX_CONNECTION_VALUE_CHARS = 256;
1380
1455
  function deriveConnectionFromHeaders(headers) {
1381
1456
  if (!headers) return void 0;
1382
1457
  const lower = {};
@@ -1389,20 +1464,30 @@ function deriveConnectionFromHeaders(headers) {
1389
1464
  for (const source of sources) {
1390
1465
  const value = lower[source]?.trim();
1391
1466
  if (value) {
1392
- connection[key] = value;
1467
+ connection[key] = value.slice(0, MAX_CONNECTION_VALUE_CHARS);
1393
1468
  break;
1394
1469
  }
1395
1470
  }
1396
1471
  }
1472
+ {
1473
+ const addr = lower["cloudfront-viewer-address"]?.trim();
1474
+ const portMatch = addr?.match(/:(\d+)$/);
1475
+ if (portMatch) connection.sourcePort = portMatch[1];
1476
+ if (!connection.ip && addr) connection.ip = addr.replace(/:\d+$/, "");
1477
+ }
1397
1478
  if (!connection.ip) {
1398
1479
  const xff = lower["x-forwarded-for"];
1399
1480
  const client = xff?.split(",")[0]?.trim();
1400
1481
  if (client) connection.ip = client;
1401
1482
  }
1483
+ if (!connection.tlsCipher) {
1484
+ const cipher = connection.tlsVersion?.split(":")[1];
1485
+ if (cipher) connection.tlsCipher = cipher;
1486
+ }
1402
1487
  return Object.keys(connection).length > 0 ? connection : void 0;
1403
1488
  }
1404
1489
  function buildSdkObservedMetadata(rawHeaders) {
1405
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1490
+ const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(rawHeaders);
1406
1491
  const flat = {};
1407
1492
  if (rawHeaders) {
1408
1493
  for (const name of Object.keys(rawHeaders)) {
@@ -1417,6 +1502,7 @@ function buildSdkObservedMetadata(rawHeaders) {
1417
1502
  ...apiKeyFormat && { apiKeyFormat },
1418
1503
  ...platformHeaders && { platformHeaders },
1419
1504
  ...connection && { connection },
1505
+ ...cookieCount !== void 0 && { cookieCount },
1420
1506
  schemaVersion: CAPTURE_SCHEMA_VERSION
1421
1507
  };
1422
1508
  }
@@ -1461,7 +1547,7 @@ async function fetchEdgeConfig(config, counterpartyId, opts) {
1461
1547
  }
1462
1548
  if (opts?.etag) headers["If-None-Match"] = opts.etag;
1463
1549
  try {
1464
- const response = await fetch(
1550
+ const response = await sdkFetch(
1465
1551
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1466
1552
  { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1467
1553
  );
@@ -1587,12 +1673,43 @@ var PLATFORM_AGENT_SIGNATURES = [
1587
1673
  uaPatterns: [/perplexity/i],
1588
1674
  agentCardPatterns: [/perplexity\.ai/i],
1589
1675
  apiKeyFormatPatterns: [/^pplx-/i]
1676
+ },
1677
+ // Agentic browsers — versioned-token patterns ONLY (a stock Chrome UA must
1678
+ // never match). Distinct vendor keys (not 'chatgpt'/'perplexity'): the
1679
+ // browser product is a different surface than the assistant service, and
1680
+ // the DB registry mirrors one row per vendor. These sit after the primary
1681
+ // vendor entries so a UA carrying an explicit vendor token keeps its
1682
+ // primary attribution; a browser-shaped UA with the agentic token is
1683
+ // caught here BEFORE classify's human tier, which is what makes these
1684
+ // visits observable at all. Token drift is real (some builds mimic Chrome
1685
+ // outright) — re-verify against captured events when a new browser ships,
1686
+ // and check `sec-ch-ua` platformHeaders for the real brand.
1687
+ {
1688
+ vendor: "chatgpt-atlas",
1689
+ displayName: "ChatGPT Atlas browser",
1690
+ uaPatterns: [/\batlas\/\d/i],
1691
+ headerPatterns: [/^sec-ch-ua: .*atlas/i]
1692
+ },
1693
+ {
1694
+ vendor: "perplexity-comet",
1695
+ displayName: "Perplexity Comet browser",
1696
+ uaPatterns: [/\bcomet\/\d/i],
1697
+ headerPatterns: [/^sec-ch-ua: .*comet/i]
1698
+ },
1699
+ // Our own SDK self-identifies (sdkFetch stamps this UA) — first-party
1700
+ // traffic must never pollute the unknown/anonymous pool it exists to audit.
1701
+ {
1702
+ vendor: "astrasync-sdk",
1703
+ displayName: "AstraSync SDK",
1704
+ uaPatterns: [/^astrasync-sdk\//i]
1590
1705
  }
1591
1706
  ];
1592
1707
  function matchPlatformSignature(signatures, input) {
1593
1708
  const ua = input.userAgent ?? "";
1594
1709
  const cardUrl = input.agentCardUrl ?? "";
1595
1710
  const apiKeyFormat = input.apiKeyFormat ?? "";
1711
+ const ja4 = input.ja4 ?? "";
1712
+ const asn = input.asn ?? "";
1596
1713
  const headerLines = [];
1597
1714
  if (input.platformHeaders) {
1598
1715
  for (const [name, value] of Object.entries(input.platformHeaders)) {
@@ -1604,7 +1721,9 @@ function matchPlatformSignature(signatures, input) {
1604
1721
  const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1605
1722
  const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1606
1723
  const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1607
- if (uaMatch || cardMatch || keyMatch || headerMatch) {
1724
+ const ja4Match = ja4.length > 0 && sig.ja4Patterns?.some((p) => p.test(ja4));
1725
+ const asnMatch = asn.length > 0 && sig.asnList?.includes(asn);
1726
+ if (uaMatch || cardMatch || keyMatch || headerMatch || ja4Match || asnMatch) {
1608
1727
  const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1609
1728
  const cardHost = (() => {
1610
1729
  try {
@@ -1613,7 +1732,7 @@ function matchPlatformSignature(signatures, input) {
1613
1732
  return "";
1614
1733
  }
1615
1734
  })();
1616
- const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1735
+ const keyBasis = uaBase || cardHost || apiKeyFormat || ja4 || asn && `asn${asn}` || "unknown";
1617
1736
  return {
1618
1737
  vendor: sig.vendor,
1619
1738
  fingerprintKey: `${sig.vendor}:${keyBasis}`,
@@ -1848,6 +1967,8 @@ function createMiddleware(options) {
1848
1967
  failOnError = "open",
1849
1968
  caseInsensitiveRouteMatch = false,
1850
1969
  anonymousBeacon = true,
1970
+ trustVerifiedHop = false,
1971
+ verifiedHopMaxAgeMs,
1851
1972
  ...config
1852
1973
  } = options;
1853
1974
  if (config.apiBaseUrl) {
@@ -1940,6 +2061,16 @@ function createMiddleware(options) {
1940
2061
  beaconPassThrough?.(req);
1941
2062
  return next();
1942
2063
  }
2064
+ if (trustVerifiedHop && credentials.astraId) {
2065
+ const hopRaw = req.headers[MCP_VERIFIED_HOP_HEADER.toLowerCase()];
2066
+ const hopValue = typeof hopRaw === "string" ? hopRaw : Array.isArray(hopRaw) ? hopRaw[0] : void 0;
2067
+ const marker = parseVerifiedHop(hopValue);
2068
+ if (isVerifiedHopValidFor(marker, credentials.astraId, {
2069
+ ...verifiedHopMaxAgeMs !== void 0 ? { maxAgeMs: verifiedHopMaxAgeMs } : {}
2070
+ })) {
2071
+ return next();
2072
+ }
2073
+ }
1943
2074
  const pdlssPair = resolveRequestPdlss(
1944
2075
  req,
1945
2076
  routeConfig,
@@ -2395,6 +2526,8 @@ function createMiddleware2(options) {
2395
2526
  enableRuntimeChallenge = true,
2396
2527
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS2,
2397
2528
  anonymousBeacon = true,
2529
+ trustVerifiedHop = false,
2530
+ verifiedHopMaxAgeMs,
2398
2531
  ...config
2399
2532
  } = options;
2400
2533
  const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
@@ -2457,6 +2590,14 @@ function createMiddleware2(options) {
2457
2590
  return NextResponse.next();
2458
2591
  }
2459
2592
  const credentials = extractCredentialsFromNextRequest(request);
2593
+ if (trustVerifiedHop && credentials.astraId) {
2594
+ const marker = parseVerifiedHop(request.headers.get(MCP_VERIFIED_HOP_HEADER) ?? void 0);
2595
+ if (isVerifiedHopValidFor(marker, credentials.astraId, {
2596
+ ...verifiedHopMaxAgeMs !== void 0 ? { maxAgeMs: verifiedHopMaxAgeMs } : {}
2597
+ })) {
2598
+ return NextResponse.next();
2599
+ }
2600
+ }
2460
2601
  const counterpartyUrl = config.counterpartyUrl || request.nextUrl.origin;
2461
2602
  const pdlssPair = resolveNextPdlss(request, routeConfig);
2462
2603
  const purpose = pdlssPair.purpose;
@@ -3422,7 +3563,7 @@ async function fetchUCPManifest(manifestUrl, options = {}) {
3422
3563
  const controller = new AbortController();
3423
3564
  const timer = setTimeout(() => controller.abort(), timeoutMs);
3424
3565
  try {
3425
- const res = await fetch(manifestUrl, { signal: controller.signal });
3566
+ const res = await sdkFetch(manifestUrl, { signal: controller.signal });
3426
3567
  if (!res.ok) return null;
3427
3568
  return await res.json();
3428
3569
  } catch {
@@ -5345,32 +5486,6 @@ function extractCredentialsFromProtocol(protocol, context) {
5345
5486
  }
5346
5487
 
5347
5488
  // src/transport/mcp-server.ts
5348
- var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
5349
- var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
5350
- function serializeVerifiedHop(marker) {
5351
- return `${marker.astraId};${marker.sessionId ?? ""};${marker.checkedAt}`;
5352
- }
5353
- function parseVerifiedHop(value) {
5354
- if (!value) return null;
5355
- const parts = value.split(";");
5356
- if (parts.length !== 3) return null;
5357
- const [astraId, sessionId, checkedAtRaw] = parts;
5358
- if (!astraId) return null;
5359
- const checkedAt = Number(checkedAtRaw);
5360
- if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
5361
- return {
5362
- astraId,
5363
- ...sessionId ? { sessionId } : {},
5364
- checkedAt
5365
- };
5366
- }
5367
- function isVerifiedHopValidFor(marker, expectedAstraId, opts = {}) {
5368
- if (!marker) return false;
5369
- if (marker.astraId !== expectedAstraId) return false;
5370
- const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;
5371
- const now = opts.now ?? Date.now();
5372
- return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;
5373
- }
5374
5489
  function parseMcpJsonRpc(body) {
5375
5490
  if (!body || typeof body !== "object" || Array.isArray(body)) return null;
5376
5491
  const obj = body;
@@ -5993,7 +6108,7 @@ var AstraSync = class {
5993
6108
  */
5994
6109
  async pollRegistration(requestId) {
5995
6110
  const url = `${this.baseUrl}/api/agents/request-registration/${requestId}`;
5996
- const res = await fetch(url, { headers: { Accept: "application/json" } });
6111
+ const res = await sdkFetch(url, { headers: { Accept: "application/json" } });
5997
6112
  if (!res.ok) {
5998
6113
  const errBody = await res.json().catch(() => ({}));
5999
6114
  throw new AstraSyncError(
@@ -6050,7 +6165,7 @@ var AstraSync = class {
6050
6165
  * Check API health.
6051
6166
  */
6052
6167
  async health() {
6053
- const res = await fetch(`${this.baseUrl}/api/health/`);
6168
+ const res = await sdkFetch(`${this.baseUrl}/api/health/`);
6054
6169
  if (!res.ok) {
6055
6170
  throw new AstraSyncError(`Health check failed: ${res.status}`, res.status);
6056
6171
  }
@@ -6077,7 +6192,7 @@ var AstraSync = class {
6077
6192
  const signature = await this.signRequest(method, endpoint, body || {});
6078
6193
  headers["X-AstraSync-Signature"] = signature;
6079
6194
  }
6080
- const res = await fetch(url, {
6195
+ const res = await sdkFetch(url, {
6081
6196
  method,
6082
6197
  headers,
6083
6198
  ...body ? { body: JSON.stringify(body) } : {}
@@ -6102,7 +6217,7 @@ var AstraSync = class {
6102
6217
  if (this.cachedJwt && this.jwtExpiresAt && Date.now() < this.jwtExpiresAt) {
6103
6218
  return this.cachedJwt;
6104
6219
  }
6105
- const res = await fetch(`${this.baseUrl}/api/auth/login`, {
6220
+ const res = await sdkFetch(`${this.baseUrl}/api/auth/login`, {
6106
6221
  method: "POST",
6107
6222
  headers: { "Content-Type": "application/json" },
6108
6223
  body: JSON.stringify({ email: this.email, password: this.password })
@@ -6245,7 +6360,7 @@ var AgentClient = class _AgentClient {
6245
6360
  const url = `${this.apiBaseUrl.replace(/\/+$/, "")}/api/agents/${encodeURIComponent(
6246
6361
  this.credentials.agentId
6247
6362
  )}/ownership`;
6248
- const resp = await fetch(url, {
6363
+ const resp = await sdkFetch(url, {
6249
6364
  method: "GET",
6250
6365
  headers: {
6251
6366
  Authorization: `Bearer ${this.apiKey}`,
@@ -6283,7 +6398,7 @@ var AgentClient = class _AgentClient {
6283
6398
  }
6284
6399
  }
6285
6400
  const enrichedHeaders = setHttpHeaders(existingHeaders, creds);
6286
- return fetch(url, {
6401
+ return sdkFetch(url, {
6287
6402
  ...fetchOptions,
6288
6403
  headers: enrichedHeaders
6289
6404
  });
@@ -6472,7 +6587,7 @@ async function recordDecision2(config, params) {
6472
6587
  if (config.customHeaders) {
6473
6588
  Object.assign(headers, config.customHeaders);
6474
6589
  }
6475
- const response = await fetch(url, {
6590
+ const response = await sdkFetch(url, {
6476
6591
  method: "POST",
6477
6592
  headers,
6478
6593
  body: JSON.stringify(body)
@@ -6489,6 +6604,51 @@ async function recordDecision2(config, params) {
6489
6604
  blockchainTxHash: result.blockchainTxHash
6490
6605
  };
6491
6606
  }
6607
+
6608
+ // src/runtime-signatures.ts
6609
+ var RUNTIME_SIGNATURES = [
6610
+ {
6611
+ runtime: "browser-automation",
6612
+ displayName: "Browser automation",
6613
+ uaPatterns: [/headless/i, /phantomjs/i, /selenium/i, /playwright/i, /puppeteer/i]
6614
+ },
6615
+ {
6616
+ runtime: "nodejs",
6617
+ displayName: "Node.js runtime",
6618
+ // `node` exactly is the global-fetch default UA; the token forms cover
6619
+ // the common Node HTTP libraries.
6620
+ uaPatterns: [/^node$/i, /^undici/i, /node-fetch/i, /^axios/i, /^got\b/i, /needle/i]
6621
+ },
6622
+ {
6623
+ runtime: "python",
6624
+ displayName: "Python runtime",
6625
+ uaPatterns: [/python-requests/i, /python-urllib/i, /^python\b/i, /httpx/i, /aiohttp/i]
6626
+ },
6627
+ { runtime: "go", displayName: "Go runtime", uaPatterns: [/go-http-client/i] },
6628
+ {
6629
+ runtime: "java",
6630
+ displayName: "Java runtime",
6631
+ uaPatterns: [/^java\//i, /okhttp/i, /apache-httpclient/i]
6632
+ },
6633
+ { runtime: "curl", displayName: "curl", uaPatterns: [/^curl\//i] },
6634
+ { runtime: "wget", displayName: "wget", uaPatterns: [/^wget\//i] },
6635
+ { runtime: "ruby", displayName: "Ruby runtime", uaPatterns: [/^ruby\b/i, /faraday/i] },
6636
+ {
6637
+ runtime: "php",
6638
+ displayName: "PHP runtime",
6639
+ uaPatterns: [/^php\b/i, /guzzlehttp/i, /^symfony/i]
6640
+ },
6641
+ { runtime: "dotnet", displayName: ".NET runtime", uaPatterns: [/^dotnet/i, /httpclient/i] },
6642
+ { runtime: "dart", displayName: "Dart runtime", uaPatterns: [/^dart/i] }
6643
+ ];
6644
+ function detectRuntime(userAgent) {
6645
+ if (!userAgent) return void 0;
6646
+ const ua = userAgent.trim();
6647
+ for (const sig of RUNTIME_SIGNATURES) {
6648
+ if (sig.uaPatterns.some((p) => p.test(ua))) return sig.displayName;
6649
+ }
6650
+ return void 0;
6651
+ }
6492
6652
  // Annotate the CommonJS export names for ESM import in node:
6493
6653
  0 && (module.exports = {
6494
6654
  ACCESS_LEVEL_DESCRIPTIONS,
@@ -6506,9 +6666,11 @@ async function recordDecision2(config, params) {
6506
6666
  MAX_HEADER_VALUE_BYTES,
6507
6667
  MAX_TOTAL_BYTES,
6508
6668
  PLATFORM_AGENT_SIGNATURES,
6669
+ RUNTIME_SIGNATURES,
6509
6670
  RegistrationDeniedError,
6510
6671
  RegistrationExpiredError,
6511
6672
  RegistrationTimeoutError,
6673
+ SDK_USER_AGENT,
6512
6674
  TRUST_LEVEL_RANGES,
6513
6675
  VERSION,
6514
6676
  _resetEdgeConfigCache,
@@ -6522,6 +6684,7 @@ async function recordDecision2(config, params) {
6522
6684
  degradeToObserve,
6523
6685
  deriveConnectionFromHeaders,
6524
6686
  detectPlatformFingerprint,
6687
+ detectRuntime,
6525
6688
  determineAccessLevel,
6526
6689
  express,
6527
6690
  extractApiKeyFormat,
@@ -6550,6 +6713,7 @@ async function recordDecision2(config, params) {
6550
6713
  reportUnregisteredAttempt,
6551
6714
  sanitizeHeaders,
6552
6715
  sdk,
6716
+ sdkFetch,
6553
6717
  setMcpMeta,
6554
6718
  transport,
6555
6719
  verify