@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.
- package/README.md +45 -21
- package/dist/adapter-interface/interface.d.mts +2 -2
- package/dist/adapter-interface/interface.d.ts +2 -2
- package/dist/adapters/express.d.mts +2 -2
- package/dist/adapters/express.d.ts +2 -2
- package/dist/adapters/express.js +145 -21
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +145 -21
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +38 -30
- package/dist/adapters/mcp.d.ts +38 -30
- package/dist/adapters/mcp.js +72 -16
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +72 -16
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.d.mts +2 -2
- package/dist/adapters/nextjs.d.ts +2 -2
- package/dist/adapters/nextjs.js +141 -19
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +141 -19
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.d.mts +2 -2
- package/dist/adapters/sdk.d.ts +2 -2
- package/dist/adapters/sdk.js +24 -6
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +24 -6
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/agent/index.d.mts +2 -2
- package/dist/agent/index.d.ts +2 -2
- package/dist/agent/index.js +24 -3
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/index.mjs +24 -3
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +22 -4
- package/dist/bin/astrasync-codex-hook.js +22 -4
- package/dist/bin/astrasync-guard.js +26 -8
- package/dist/bin/astrasync.js +76 -23
- package/dist/browser/background.js +22 -4
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +22 -4
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +2 -2
- package/dist/browser/browser-adapter.d.ts +2 -2
- package/dist/claude-code/claude-code-adapter.d.mts +2 -2
- package/dist/claude-code/claude-code-adapter.d.ts +2 -2
- package/dist/cli/index.d.mts +2 -2
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +23 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +23 -2
- package/dist/cli/index.mjs.map +1 -1
- package/dist/codex/index.d.mts +2 -2
- package/dist/codex/index.d.ts +2 -2
- package/dist/codex/index.js +22 -4
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +22 -4
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/cursor-adapter.d.mts +2 -2
- package/dist/cursor/cursor-adapter.d.ts +2 -2
- package/dist/cursor/extension.d.mts +2 -2
- package/dist/cursor/extension.d.ts +2 -2
- package/dist/cursor/extension.js +22 -4
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +22 -4
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +1 -1
- package/dist/edge-config.d.ts +1 -1
- package/dist/edge-config.js +24 -1
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs +22 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +1 -1
- package/dist/edge-core/index.d.ts +1 -1
- package/dist/edge-core/index.js +121 -16
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +121 -16
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/{express-D_4hTn5Z.d.mts → express-CR6F3njD.d.mts} +1 -1
- package/dist/{express-BVd1_3FE.d.ts → express-DaqL11tx.d.ts} +1 -1
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +22 -4
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +22 -4
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +2 -2
- package/dist/git-trigger/git-hooks.d.ts +2 -2
- package/dist/{index-BqHKlLOU.d.ts → index-B5Ts1WtM.d.ts} +1 -1
- package/dist/{index-T1aBoUcc.d.ts → index-BfIxCYIZ.d.ts} +1 -1
- package/dist/{index-BTeQ9fgV.d.mts → index-Cub7lK-T.d.mts} +1 -1
- package/dist/{index-DQb5-_1X.d.mts → index-CwAh7Tln.d.mts} +1 -1
- package/dist/index.d.mts +58 -9
- package/dist/index.d.ts +58 -9
- package/dist/index.js +218 -54
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +214 -54
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +2 -2
- package/dist/local-evaluator/evaluator.d.ts +2 -2
- package/dist/metadata-capture.d.mts +35 -5
- package/dist/metadata-capture.d.ts +35 -5
- package/dist/metadata-capture.js +44 -6
- package/dist/metadata-capture.js.map +1 -1
- package/dist/metadata-capture.mjs +43 -6
- package/dist/metadata-capture.mjs.map +1 -1
- package/dist/{nextjs-w7RTbP1P.d.ts → nextjs-DL6uE1ye.d.ts} +1 -1
- package/dist/{nextjs-WyeVr2Kp.d.mts → nextjs-DlX6xwbU.d.mts} +1 -1
- package/dist/platform-signatures.d.mts +18 -1
- package/dist/platform-signatures.d.ts +18 -1
- package/dist/platform-signatures.js +35 -2
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs +35 -2
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.js +25 -4
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs +25 -4
- package/dist/registration/index.mjs.map +1 -1
- package/dist/{sdk-CbTNIkAa.d.mts → sdk-Bbh9B_dw.d.mts} +1 -1
- package/dist/{sdk-QeX0Z8Ho.d.ts → sdk-C6SASmsh.d.ts} +1 -1
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js +22 -1
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs +22 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-r850cOt0.d.mts → types-C0zfSXMB.d.mts} +22 -0
- package/dist/{types-DqfPU5Bl.d.mts → types-C7Rb-w0d.d.mts} +1 -1
- package/dist/{types-sMxBT-nM.d.ts → types-DwSfO7Sb.d.ts} +22 -0
- package/dist/{types-BLUx92FJ.d.ts → types-nIh3HtuA.d.ts} +1 -1
- package/dist/ui/index.d.mts +1 -1
- package/dist/ui/index.d.ts +1 -1
- package/dist/verify.d.mts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/verify.js +29 -11
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +29 -11
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
package/dist/adapters/nextjs.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 = [
|
|
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
|
-
|
|
887
|
-
|
|
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
|
|
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
|
-
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -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;
|