@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();
@@ -95,7 +113,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
95
113
  initCheckPerformed = true;
96
114
  try {
97
115
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
98
- const response = await fetch(probeUrl, { method: "HEAD" });
116
+ const response = await sdkFetch(probeUrl, { method: "HEAD" });
99
117
  const contentType = response.headers.get("content-type") ?? "";
100
118
  if (contentType.startsWith("text/html")) {
101
119
  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).`;
@@ -109,7 +127,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
109
127
  );
110
128
  }
111
129
  if (apiKey) {
112
- const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
130
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
113
131
  headers: { Authorization: `Bearer ${apiKey}` }
114
132
  });
115
133
  if (selfCheck.status === 401) {
@@ -356,7 +374,7 @@ async function callVerifyAccessAPI(config, request) {
356
374
  headers["X-API-Key"] = config.apiKey;
357
375
  }
358
376
  try {
359
- const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
377
+ const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {
360
378
  method: "POST",
361
379
  headers,
362
380
  body: JSON.stringify(body),
@@ -601,7 +619,7 @@ async function fetchRoutes(config, counterpartyId) {
601
619
  headers["X-API-Key"] = config.apiKey;
602
620
  }
603
621
  try {
604
- const response = await fetch(
622
+ const response = await sdkFetch(
605
623
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
606
624
  { method: "GET", headers }
607
625
  );
@@ -620,7 +638,7 @@ async function reportUnregisteredAttempt(config, data) {
620
638
  ...counterpartyUrl && { counterpartyUrl },
621
639
  ...config.counterpartyId && { counterpartyId: config.counterpartyId }
622
640
  };
623
- await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
641
+ await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
624
642
  method: "POST",
625
643
  headers: { "Content-Type": "application/json" },
626
644
  body: JSON.stringify(body)
@@ -629,7 +647,7 @@ async function reportUnregisteredAttempt(config, data) {
629
647
  }
630
648
  async function reportCounterpartyPreCheckFailure(config, data) {
631
649
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
632
- await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
650
+ await sdkFetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
633
651
  method: "POST",
634
652
  headers: { "Content-Type": "application/json" },
635
653
  body: JSON.stringify(data)
@@ -741,7 +759,9 @@ var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
741
759
  "x-astrasync-signature",
742
760
  "x-astrasync-secret",
743
761
  "x-hub-signature",
744
- "x-hub-signature-256"
762
+ "x-hub-signature-256",
763
+ // Short-lived verify dedupe token — must never persist in stored metadata.
764
+ "x-astra-verified-hop"
745
765
  ]);
746
766
  var SECRET_HEADER_PREFIXES = ["x-astrasync-", "kya-", "kya_"];
747
767
  var CREDENTIAL_HEADER_NAMES = /* @__PURE__ */ new Set([
@@ -766,7 +786,13 @@ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
766
786
  "x-agent-card-url",
767
787
  "x-agent-provider"
768
788
  ]);
769
- var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
789
+ var PLATFORM_HEADER_PREFIXES = [
790
+ "x-goog-",
791
+ "x-stainless-",
792
+ "x-anthropic-",
793
+ "x-openai-",
794
+ "sec-ch-"
795
+ ];
770
796
  var KEY_FORMAT_PREFIXES = [
771
797
  "sk-ant-api03",
772
798
  "sk-ant-api",
@@ -838,6 +864,7 @@ function sanitizeHeaders(raw) {
838
864
  const headers = {};
839
865
  const platformHeaders = {};
840
866
  let apiKeyFormat;
867
+ let cookieCount;
841
868
  let totalBytes = 0;
842
869
  let count = 0;
843
870
  if (!raw) return { headers };
@@ -852,6 +879,9 @@ function sanitizeHeaders(raw) {
852
879
  if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
853
880
  continue;
854
881
  }
882
+ if (name === "cookie") {
883
+ cookieCount = joined.split(";").filter((s) => s.trim().length > 0).length;
884
+ }
855
885
  if (isSecretHeader(name)) continue;
856
886
  let value = joined;
857
887
  if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
@@ -867,6 +897,7 @@ function sanitizeHeaders(raw) {
867
897
  const result = { headers };
868
898
  if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
869
899
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
900
+ if (cookieCount !== void 0) result.cookieCount = cookieCount;
870
901
  return result;
871
902
  }
872
903
  var CONNECTION_HEADER_SOURCES = [
@@ -876,16 +907,28 @@ var CONNECTION_HEADER_SOURCES = [
876
907
  ["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
877
908
  ],
878
909
  ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
910
+ ["countryRegionName", ["cloudfront-viewer-country-region-name"]],
879
911
  ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
912
+ ["latitude", ["x-vercel-ip-latitude", "cloudfront-viewer-latitude"]],
913
+ ["longitude", ["x-vercel-ip-longitude", "cloudfront-viewer-longitude"]],
914
+ ["postalCode", ["x-vercel-ip-postal-code", "cloudfront-viewer-postal-code"]],
880
915
  ["asn", ["cloudfront-viewer-asn"]],
881
916
  ["tlsVersion", ["cloudfront-viewer-tls"]],
882
917
  ["httpVersion", ["cloudfront-viewer-http-version"]],
918
+ ["timeZone", ["x-vercel-ip-timezone", "cloudfront-viewer-time-zone"]],
919
+ ["metroCode", ["cloudfront-viewer-metro-code"]],
883
920
  // TLS fingerprints can only be computed where TLS terminates. When a CDN
884
921
  // terminates TLS in front of the merchant, its fingerprint headers ARE the
885
922
  // connection-layer truth — normalising them here is the permanent design.
886
- ["tlsFingerprint", ["cf-ja3-hash"]],
887
- ["ja4", ["cf-ja4"]]
923
+ // CloudFront delivers JA3/JA4 only via an origin request policy and only
924
+ // for HTTPS viewer connections.
925
+ ["tlsFingerprint", ["cf-ja3-hash", "cloudfront-viewer-ja3-fingerprint"]],
926
+ ["ja4", ["cf-ja4", "cloudfront-viewer-ja4-fingerprint"]],
927
+ // Header-structure fingerprint: browser/SDK-distinctive header ordering.
928
+ ["headerOrder", ["cloudfront-viewer-header-order"]],
929
+ ["headerCount", ["cloudfront-viewer-header-count"]]
888
930
  ];
931
+ var MAX_CONNECTION_VALUE_CHARS = 256;
889
932
  function deriveConnectionFromHeaders(headers) {
890
933
  if (!headers) return void 0;
891
934
  const lower = {};
@@ -898,20 +941,30 @@ function deriveConnectionFromHeaders(headers) {
898
941
  for (const source of sources) {
899
942
  const value = lower[source]?.trim();
900
943
  if (value) {
901
- connection[key] = value;
944
+ connection[key] = value.slice(0, MAX_CONNECTION_VALUE_CHARS);
902
945
  break;
903
946
  }
904
947
  }
905
948
  }
949
+ {
950
+ const addr = lower["cloudfront-viewer-address"]?.trim();
951
+ const portMatch = addr?.match(/:(\d+)$/);
952
+ if (portMatch) connection.sourcePort = portMatch[1];
953
+ if (!connection.ip && addr) connection.ip = addr.replace(/:\d+$/, "");
954
+ }
906
955
  if (!connection.ip) {
907
956
  const xff = lower["x-forwarded-for"];
908
957
  const client = xff?.split(",")[0]?.trim();
909
958
  if (client) connection.ip = client;
910
959
  }
960
+ if (!connection.tlsCipher) {
961
+ const cipher = connection.tlsVersion?.split(":")[1];
962
+ if (cipher) connection.tlsCipher = cipher;
963
+ }
911
964
  return Object.keys(connection).length > 0 ? connection : void 0;
912
965
  }
913
966
  function buildSdkObservedMetadata(rawHeaders) {
914
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
967
+ const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(rawHeaders);
915
968
  const flat = {};
916
969
  if (rawHeaders) {
917
970
  for (const name of Object.keys(rawHeaders)) {
@@ -926,6 +979,7 @@ function buildSdkObservedMetadata(rawHeaders) {
926
979
  ...apiKeyFormat && { apiKeyFormat },
927
980
  ...platformHeaders && { platformHeaders },
928
981
  ...connection && { connection },
982
+ ...cookieCount !== void 0 && { cookieCount },
929
983
  schemaVersion: CAPTURE_SCHEMA_VERSION
930
984
  };
931
985
  }
@@ -970,7 +1024,7 @@ async function fetchEdgeConfig(config, counterpartyId, opts) {
970
1024
  }
971
1025
  if (opts?.etag) headers["If-None-Match"] = opts.etag;
972
1026
  try {
973
- const response = await fetch(
1027
+ const response = await sdkFetch(
974
1028
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
975
1029
  { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
976
1030
  );
@@ -1082,12 +1136,43 @@ var PLATFORM_AGENT_SIGNATURES = [
1082
1136
  uaPatterns: [/perplexity/i],
1083
1137
  agentCardPatterns: [/perplexity\.ai/i],
1084
1138
  apiKeyFormatPatterns: [/^pplx-/i]
1139
+ },
1140
+ // Agentic browsers — versioned-token patterns ONLY (a stock Chrome UA must
1141
+ // never match). Distinct vendor keys (not 'chatgpt'/'perplexity'): the
1142
+ // browser product is a different surface than the assistant service, and
1143
+ // the DB registry mirrors one row per vendor. These sit after the primary
1144
+ // vendor entries so a UA carrying an explicit vendor token keeps its
1145
+ // primary attribution; a browser-shaped UA with the agentic token is
1146
+ // caught here BEFORE classify's human tier, which is what makes these
1147
+ // visits observable at all. Token drift is real (some builds mimic Chrome
1148
+ // outright) — re-verify against captured events when a new browser ships,
1149
+ // and check `sec-ch-ua` platformHeaders for the real brand.
1150
+ {
1151
+ vendor: "chatgpt-atlas",
1152
+ displayName: "ChatGPT Atlas browser",
1153
+ uaPatterns: [/\batlas\/\d/i],
1154
+ headerPatterns: [/^sec-ch-ua: .*atlas/i]
1155
+ },
1156
+ {
1157
+ vendor: "perplexity-comet",
1158
+ displayName: "Perplexity Comet browser",
1159
+ uaPatterns: [/\bcomet\/\d/i],
1160
+ headerPatterns: [/^sec-ch-ua: .*comet/i]
1161
+ },
1162
+ // Our own SDK self-identifies (sdkFetch stamps this UA) — first-party
1163
+ // traffic must never pollute the unknown/anonymous pool it exists to audit.
1164
+ {
1165
+ vendor: "astrasync-sdk",
1166
+ displayName: "AstraSync SDK",
1167
+ uaPatterns: [/^astrasync-sdk\//i]
1085
1168
  }
1086
1169
  ];
1087
1170
  function matchPlatformSignature(signatures, input) {
1088
1171
  const ua = input.userAgent ?? "";
1089
1172
  const cardUrl = input.agentCardUrl ?? "";
1090
1173
  const apiKeyFormat = input.apiKeyFormat ?? "";
1174
+ const ja4 = input.ja4 ?? "";
1175
+ const asn = input.asn ?? "";
1091
1176
  const headerLines = [];
1092
1177
  if (input.platformHeaders) {
1093
1178
  for (const [name, value] of Object.entries(input.platformHeaders)) {
@@ -1099,7 +1184,9 @@ function matchPlatformSignature(signatures, input) {
1099
1184
  const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1100
1185
  const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1101
1186
  const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1102
- if (uaMatch || cardMatch || keyMatch || headerMatch) {
1187
+ const ja4Match = ja4.length > 0 && sig.ja4Patterns?.some((p) => p.test(ja4));
1188
+ const asnMatch = asn.length > 0 && sig.asnList?.includes(asn);
1189
+ if (uaMatch || cardMatch || keyMatch || headerMatch || ja4Match || asnMatch) {
1103
1190
  const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1104
1191
  const cardHost = (() => {
1105
1192
  try {
@@ -1108,7 +1195,7 @@ function matchPlatformSignature(signatures, input) {
1108
1195
  return "";
1109
1196
  }
1110
1197
  })();
1111
- const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1198
+ const keyBasis = uaBase || cardHost || apiKeyFormat || ja4 || asn && `asn${asn}` || "unknown";
1112
1199
  return {
1113
1200
  vendor: sig.vendor,
1114
1201
  fingerprintKey: `${sig.vendor}:${keyBasis}`,
@@ -1315,6 +1402,31 @@ function annotateApprovalRequired(result) {
1315
1402
  result.denialReasons = [APPROVAL_REASON, ...result.denialReasons ?? []];
1316
1403
  }
1317
1404
 
1405
+ // src/verified-hop.ts
1406
+ var MCP_VERIFIED_HOP_HEADER = "X-Astra-Verified-Hop";
1407
+ var MCP_VERIFIED_HOP_MAX_AGE_MS = 6e4;
1408
+ function parseVerifiedHop(value) {
1409
+ if (!value) return null;
1410
+ const parts = value.split(";");
1411
+ if (parts.length !== 3) return null;
1412
+ const [astraId, sessionId, checkedAtRaw] = parts;
1413
+ if (!astraId) return null;
1414
+ const checkedAt = Number(checkedAtRaw);
1415
+ if (!Number.isFinite(checkedAt) || checkedAt <= 0) return null;
1416
+ return {
1417
+ astraId,
1418
+ ...sessionId ? { sessionId } : {},
1419
+ checkedAt
1420
+ };
1421
+ }
1422
+ function isVerifiedHopValidFor(marker, expectedAstraId, opts = {}) {
1423
+ if (!marker) return false;
1424
+ if (marker.astraId !== expectedAstraId) return false;
1425
+ const maxAge = opts.maxAgeMs ?? MCP_VERIFIED_HOP_MAX_AGE_MS;
1426
+ const now = opts.now ?? Date.now();
1427
+ return now - marker.checkedAt <= maxAge && now >= marker.checkedAt;
1428
+ }
1429
+
1318
1430
  // src/adapters/nextjs.ts
1319
1431
  function escapeHtml(value) {
1320
1432
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -1553,6 +1665,8 @@ function createMiddleware(options) {
1553
1665
  enableRuntimeChallenge = true,
1554
1666
  routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
1555
1667
  anonymousBeacon = true,
1668
+ trustVerifiedHop = false,
1669
+ verifiedHopMaxAgeMs,
1556
1670
  ...config
1557
1671
  } = options;
1558
1672
  const interstitialEnabled = showInterstitial ?? showCommerceShield ?? true;
@@ -1615,6 +1729,14 @@ function createMiddleware(options) {
1615
1729
  return NextResponse.next();
1616
1730
  }
1617
1731
  const credentials = extractCredentialsFromNextRequest(request);
1732
+ if (trustVerifiedHop && credentials.astraId) {
1733
+ const marker = parseVerifiedHop(request.headers.get(MCP_VERIFIED_HOP_HEADER) ?? void 0);
1734
+ if (isVerifiedHopValidFor(marker, credentials.astraId, {
1735
+ ...verifiedHopMaxAgeMs !== void 0 ? { maxAgeMs: verifiedHopMaxAgeMs } : {}
1736
+ })) {
1737
+ return NextResponse.next();
1738
+ }
1739
+ }
1618
1740
  const counterpartyUrl = config.counterpartyUrl || request.nextUrl.origin;
1619
1741
  const pdlssPair = resolveNextPdlss(request, routeConfig);
1620
1742
  const purpose = pdlssPair.purpose;