@astrasyncai/verification-gateway 4.0.0 → 4.1.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 +106 -29
- 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 +398 -15
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +398 -15
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/http-pdlss.d.mts +3 -3
- package/dist/adapters/http-pdlss.d.ts +3 -3
- package/dist/adapters/http-pdlss.js.map +1 -1
- package/dist/adapters/http-pdlss.mjs.map +1 -1
- package/dist/adapters/mcp.d.mts +56 -36
- package/dist/adapters/mcp.d.ts +56 -36
- package/dist/adapters/mcp.js +220 -3
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +220 -3
- 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 +433 -18
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +433 -18
- 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 +2 -2
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +2 -2
- 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.map +1 -1
- package/dist/agent/index.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +13 -13
- package/dist/bin/astrasync-codex-hook.js +13 -13
- package/dist/bin/astrasync-guard.js +13 -13
- package/dist/bin/astrasync.js +80 -26
- package/dist/browser/background.js +13 -13
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +13 -13
- 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/codex/index.d.mts +3 -3
- package/dist/codex/index.d.ts +3 -3
- package/dist/codex/index.js +13 -13
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +13 -13
- 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 +13 -13
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +13 -13
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +22 -7
- package/dist/edge-config.d.ts +22 -7
- package/dist/edge-config.js.map +1 -1
- package/dist/edge-config.mjs.map +1 -1
- package/dist/edge-core/index.d.mts +425 -0
- package/dist/edge-core/index.d.ts +425 -0
- package/dist/edge-core/index.js +1482 -0
- package/dist/edge-core/index.js.map +1 -0
- package/dist/edge-core/index.mjs +1437 -0
- package/dist/edge-core/index.mjs.map +1 -0
- package/dist/{express-BZajRs4i.d.mts → express-BVd1_3FE.d.ts} +9 -7
- package/dist/{express-CjCUB1k7.d.ts → express-D_4hTn5Z.d.mts} +9 -7
- package/dist/gateway/gateway.d.mts +2 -2
- package/dist/gateway/gateway.d.ts +2 -2
- package/dist/gateway/gateway.js +13 -13
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +13 -13
- 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-adgKhujR.d.ts → index-B0YHu_SP.d.ts} +30 -27
- package/dist/{index-B0cyMZDL.d.mts → index-BPEBlOsE.d.mts} +30 -27
- package/dist/{index-BRYteOqO.d.mts → index-DQb5-_1X.d.mts} +1 -1
- package/dist/{index-BZQdVqNw.d.ts → index-T1aBoUcc.d.ts} +1 -1
- package/dist/index.d.mts +11 -11
- package/dist/index.d.ts +11 -11
- package/dist/index.js +477 -265
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +474 -265
- 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 +63 -4
- package/dist/metadata-capture.d.ts +63 -4
- package/dist/metadata-capture.js +67 -0
- package/dist/metadata-capture.js.map +1 -1
- package/dist/metadata-capture.mjs +64 -0
- package/dist/metadata-capture.mjs.map +1 -1
- package/dist/{nextjs-DeUTnOBd.d.mts → nextjs-WyeVr2Kp.d.mts} +3 -3
- package/dist/{nextjs-D_iFf5k-.d.ts → nextjs-w7RTbP1P.d.ts} +3 -3
- package/dist/platform-signatures.d.mts +5 -6
- package/dist/platform-signatures.d.ts +5 -6
- package/dist/platform-signatures.js.map +1 -1
- package/dist/platform-signatures.mjs.map +1 -1
- package/dist/registration/index.d.mts +10 -11
- package/dist/registration/index.d.ts +10 -11
- package/dist/registration/index.js.map +1 -1
- package/dist/registration/index.mjs.map +1 -1
- package/dist/{sdk-BM3mwEAq.d.ts → sdk-CbTNIkAa.d.mts} +4 -4
- package/dist/{sdk-_il1Q41f.d.mts → sdk-QeX0Z8Ho.d.ts} +4 -4
- package/dist/transport/index.d.mts +2 -2
- package/dist/transport/index.d.ts +2 -2
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/index.mjs.map +1 -1
- package/dist/{types-CHKCgDFC.d.ts → types-BLUx92FJ.d.ts} +1 -1
- package/dist/{types-DfnP7JiM.d.mts → types-DqfPU5Bl.d.mts} +1 -1
- package/dist/{types-BcDf9Kk0.d.mts → types-r850cOt0.d.mts} +106 -88
- package/dist/{types-fTQD448H.d.ts → types-sMxBT-nM.d.ts} +106 -88
- package/dist/ui/index.d.mts +11 -7
- package/dist/ui/index.d.ts +11 -7
- package/dist/ui/index.js +13 -9
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/index.mjs +10 -8
- package/dist/ui/index.mjs.map +1 -1
- package/dist/verify.d.mts +8 -7
- package/dist/verify.d.ts +8 -7
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +1 -1
- package/dist/verify.mjs.map +1 -1
- package/package.json +8 -2
|
@@ -65,7 +65,7 @@ function getTrustLevel(score) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
// src/version.ts
|
|
68
|
-
var SDK_VERSION = "4.
|
|
68
|
+
var SDK_VERSION = "4.1.0";
|
|
69
69
|
|
|
70
70
|
// src/well-known.ts
|
|
71
71
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -677,6 +677,21 @@ async function fetchRoutes(config, counterpartyId) {
|
|
|
677
677
|
return null;
|
|
678
678
|
}
|
|
679
679
|
}
|
|
680
|
+
async function reportUnregisteredAttempt(config, data) {
|
|
681
|
+
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
682
|
+
const { counterpartyUrl, ...rest } = data;
|
|
683
|
+
const body = {
|
|
684
|
+
...rest,
|
|
685
|
+
...counterpartyUrl && { counterpartyUrl },
|
|
686
|
+
...config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
687
|
+
};
|
|
688
|
+
await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
689
|
+
method: "POST",
|
|
690
|
+
headers: { "Content-Type": "application/json" },
|
|
691
|
+
body: JSON.stringify(body)
|
|
692
|
+
}).catch(() => {
|
|
693
|
+
});
|
|
694
|
+
}
|
|
680
695
|
async function reportCounterpartyPreCheckFailure(config, data) {
|
|
681
696
|
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
682
697
|
await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
|
|
@@ -874,6 +889,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
|
|
|
874
889
|
}
|
|
875
890
|
|
|
876
891
|
// src/metadata-capture.ts
|
|
892
|
+
var CAPTURE_SCHEMA_VERSION = 1;
|
|
877
893
|
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
878
894
|
"cookie",
|
|
879
895
|
"set-cookie",
|
|
@@ -1010,6 +1026,359 @@ function sanitizeHeaders(raw) {
|
|
|
1010
1026
|
if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
|
|
1011
1027
|
return result;
|
|
1012
1028
|
}
|
|
1029
|
+
var CONNECTION_HEADER_SOURCES = [
|
|
1030
|
+
["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
|
|
1031
|
+
[
|
|
1032
|
+
"country",
|
|
1033
|
+
["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
|
|
1034
|
+
],
|
|
1035
|
+
["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
|
|
1036
|
+
["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
|
|
1037
|
+
["asn", ["cloudfront-viewer-asn"]],
|
|
1038
|
+
["tlsVersion", ["cloudfront-viewer-tls"]],
|
|
1039
|
+
["httpVersion", ["cloudfront-viewer-http-version"]],
|
|
1040
|
+
// TLS fingerprints can only be computed where TLS terminates. When a CDN
|
|
1041
|
+
// terminates TLS in front of the merchant, its fingerprint headers ARE the
|
|
1042
|
+
// connection-layer truth — normalising them here is the permanent design.
|
|
1043
|
+
["tlsFingerprint", ["cf-ja3-hash"]],
|
|
1044
|
+
["ja4", ["cf-ja4"]]
|
|
1045
|
+
];
|
|
1046
|
+
function deriveConnectionFromHeaders(headers) {
|
|
1047
|
+
if (!headers) return void 0;
|
|
1048
|
+
const lower = {};
|
|
1049
|
+
for (const name of Object.keys(headers)) {
|
|
1050
|
+
const value = headers[name];
|
|
1051
|
+
if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
|
|
1052
|
+
}
|
|
1053
|
+
const connection = {};
|
|
1054
|
+
for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
|
|
1055
|
+
for (const source of sources) {
|
|
1056
|
+
const value = lower[source]?.trim();
|
|
1057
|
+
if (value) {
|
|
1058
|
+
connection[key] = value;
|
|
1059
|
+
break;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
if (!connection.ip) {
|
|
1064
|
+
const xff = lower["x-forwarded-for"];
|
|
1065
|
+
const client = xff?.split(",")[0]?.trim();
|
|
1066
|
+
if (client) connection.ip = client;
|
|
1067
|
+
}
|
|
1068
|
+
return Object.keys(connection).length > 0 ? connection : void 0;
|
|
1069
|
+
}
|
|
1070
|
+
function buildSdkObservedMetadata(rawHeaders) {
|
|
1071
|
+
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1072
|
+
const flat = {};
|
|
1073
|
+
if (rawHeaders) {
|
|
1074
|
+
for (const name of Object.keys(rawHeaders)) {
|
|
1075
|
+
const value = rawHeaders[name];
|
|
1076
|
+
if (value === void 0) continue;
|
|
1077
|
+
flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
const connection = deriveConnectionFromHeaders(flat);
|
|
1081
|
+
return {
|
|
1082
|
+
headers,
|
|
1083
|
+
...apiKeyFormat && { apiKeyFormat },
|
|
1084
|
+
...platformHeaders && { platformHeaders },
|
|
1085
|
+
...connection && { connection },
|
|
1086
|
+
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// src/edge-config.ts
|
|
1091
|
+
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
1092
|
+
version: 1,
|
|
1093
|
+
mode: "observe",
|
|
1094
|
+
depth: "classify",
|
|
1095
|
+
pathRules: [],
|
|
1096
|
+
sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
|
|
1097
|
+
failurePosture: "open"
|
|
1098
|
+
});
|
|
1099
|
+
var EDGE_MODES = ["observe", "enforce"];
|
|
1100
|
+
var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
|
|
1101
|
+
function isEdgeConfig(value) {
|
|
1102
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1103
|
+
const v = value;
|
|
1104
|
+
if (v.version !== 1) return false;
|
|
1105
|
+
if (!EDGE_MODES.includes(v.mode)) return false;
|
|
1106
|
+
if (!EDGE_DEPTHS.includes(v.depth)) return false;
|
|
1107
|
+
if (!Array.isArray(v.pathRules)) return false;
|
|
1108
|
+
for (const rule of v.pathRules) {
|
|
1109
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
1110
|
+
const r = rule;
|
|
1111
|
+
if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
|
|
1112
|
+
if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
|
|
1113
|
+
if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
|
|
1114
|
+
}
|
|
1115
|
+
const sampling = v.sampling;
|
|
1116
|
+
if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
|
|
1117
|
+
return false;
|
|
1118
|
+
}
|
|
1119
|
+
return v.failurePosture === "open";
|
|
1120
|
+
}
|
|
1121
|
+
async function fetchEdgeConfig(config, counterpartyId, opts) {
|
|
1122
|
+
if (!counterpartyId) return null;
|
|
1123
|
+
const headers = { Accept: "application/json" };
|
|
1124
|
+
if (config.apiKey) {
|
|
1125
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1126
|
+
headers["X-API-Key"] = config.apiKey;
|
|
1127
|
+
}
|
|
1128
|
+
if (opts?.etag) headers["If-None-Match"] = opts.etag;
|
|
1129
|
+
try {
|
|
1130
|
+
const response = await fetch(
|
|
1131
|
+
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
|
|
1132
|
+
{ method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
|
|
1133
|
+
);
|
|
1134
|
+
if (response.status === 304) return { notModified: true };
|
|
1135
|
+
if (!response.ok) return null;
|
|
1136
|
+
const body = await response.json();
|
|
1137
|
+
const edgeConfig = body.data?.edgeConfig;
|
|
1138
|
+
if (!isEdgeConfig(edgeConfig)) return null;
|
|
1139
|
+
return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
|
|
1140
|
+
} catch {
|
|
1141
|
+
return null;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
1145
|
+
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
1146
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
1147
|
+
var inflight2 = /* @__PURE__ */ new Map();
|
|
1148
|
+
function cacheKey(apiBaseUrl, counterpartyId) {
|
|
1149
|
+
return `${apiBaseUrl}|${counterpartyId}`;
|
|
1150
|
+
}
|
|
1151
|
+
function degradeToObserve(config) {
|
|
1152
|
+
if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
|
|
1153
|
+
return config;
|
|
1154
|
+
}
|
|
1155
|
+
return {
|
|
1156
|
+
...config,
|
|
1157
|
+
mode: "observe",
|
|
1158
|
+
pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
async function revalidate(config, counterpartyId) {
|
|
1162
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1163
|
+
const existing = inflight2.get(key);
|
|
1164
|
+
if (existing) return existing;
|
|
1165
|
+
const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
|
|
1166
|
+
if (result === null) return;
|
|
1167
|
+
const now = Date.now();
|
|
1168
|
+
if ("notModified" in result) {
|
|
1169
|
+
const entry = cache2.get(key);
|
|
1170
|
+
if (entry) entry.lastSuccessAt = now;
|
|
1171
|
+
return;
|
|
1172
|
+
}
|
|
1173
|
+
cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
|
|
1174
|
+
}).finally(() => {
|
|
1175
|
+
inflight2.delete(key);
|
|
1176
|
+
});
|
|
1177
|
+
inflight2.set(key, promise);
|
|
1178
|
+
return promise;
|
|
1179
|
+
}
|
|
1180
|
+
async function getEdgeConfig(config, counterpartyId, opts) {
|
|
1181
|
+
const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
1182
|
+
const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
|
|
1183
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1184
|
+
const entry = cache2.get(key);
|
|
1185
|
+
if (entry) {
|
|
1186
|
+
const age = Date.now() - entry.lastSuccessAt;
|
|
1187
|
+
if (age > ttlMs) {
|
|
1188
|
+
revalidate(config, counterpartyId).catch(() => {
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
|
|
1192
|
+
}
|
|
1193
|
+
await revalidate(config, counterpartyId).catch(() => {
|
|
1194
|
+
});
|
|
1195
|
+
return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/platform-signatures.ts
|
|
1199
|
+
var PLATFORM_AGENT_SIGNATURES = [
|
|
1200
|
+
{
|
|
1201
|
+
vendor: "claude",
|
|
1202
|
+
displayName: "Anthropic Claude",
|
|
1203
|
+
uaPatterns: [/claude/i, /anthropic/i],
|
|
1204
|
+
agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
|
|
1205
|
+
apiKeyFormatPatterns: [/^sk-ant/i],
|
|
1206
|
+
headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
vendor: "chatgpt",
|
|
1210
|
+
displayName: "OpenAI ChatGPT",
|
|
1211
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
|
|
1212
|
+
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1213
|
+
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1214
|
+
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
1215
|
+
},
|
|
1216
|
+
{
|
|
1217
|
+
vendor: "gemini",
|
|
1218
|
+
displayName: "Google Gemini",
|
|
1219
|
+
uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
|
|
1220
|
+
agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
|
|
1221
|
+
apiKeyFormatPatterns: [/^aiza/i],
|
|
1222
|
+
headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
vendor: "cursor",
|
|
1226
|
+
displayName: "Cursor",
|
|
1227
|
+
uaPatterns: [/cursor/i],
|
|
1228
|
+
agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
|
|
1229
|
+
},
|
|
1230
|
+
{
|
|
1231
|
+
vendor: "goose",
|
|
1232
|
+
displayName: "Block Goose",
|
|
1233
|
+
uaPatterns: [/goose/i, /block-goose/i],
|
|
1234
|
+
agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
|
|
1235
|
+
},
|
|
1236
|
+
{
|
|
1237
|
+
vendor: "perplexity",
|
|
1238
|
+
displayName: "Perplexity",
|
|
1239
|
+
uaPatterns: [/perplexity/i],
|
|
1240
|
+
agentCardPatterns: [/perplexity\.ai/i],
|
|
1241
|
+
apiKeyFormatPatterns: [/^pplx-/i]
|
|
1242
|
+
}
|
|
1243
|
+
];
|
|
1244
|
+
function matchPlatformSignature(signatures, input) {
|
|
1245
|
+
const ua = input.userAgent ?? "";
|
|
1246
|
+
const cardUrl = input.agentCardUrl ?? "";
|
|
1247
|
+
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
1248
|
+
const headerLines = [];
|
|
1249
|
+
if (input.platformHeaders) {
|
|
1250
|
+
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
1251
|
+
headerLines.push(name, `${name}: ${value}`);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
for (const sig of signatures) {
|
|
1255
|
+
const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
|
|
1256
|
+
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
1257
|
+
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
1258
|
+
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
1259
|
+
if (uaMatch || cardMatch || keyMatch || headerMatch) {
|
|
1260
|
+
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
1261
|
+
const cardHost = (() => {
|
|
1262
|
+
try {
|
|
1263
|
+
return cardUrl ? new URL(cardUrl).host : "";
|
|
1264
|
+
} catch {
|
|
1265
|
+
return "";
|
|
1266
|
+
}
|
|
1267
|
+
})();
|
|
1268
|
+
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
1269
|
+
return {
|
|
1270
|
+
vendor: sig.vendor,
|
|
1271
|
+
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
1272
|
+
displayName: sig.displayName
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return void 0;
|
|
1277
|
+
}
|
|
1278
|
+
function detectPlatformFingerprint(input) {
|
|
1279
|
+
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
// src/edge-core/classify.ts
|
|
1283
|
+
var STATIC_EXTENSIONS = /\.(css|js|mjs|map|png|jpe?g|gif|webp|avif|svg|ico|woff2?|ttf|otf|eot|mp4|webm|pdf|txt|xml)$/i;
|
|
1284
|
+
var BOT_UA_PATTERN = /curl|wget|python-requests|python-urllib|node-fetch|axios|got|okhttp|go-http-client|libwww|httpie|java\/|ruby|php|scrapy|headless|phantomjs|selenium|playwright|puppeteer|bot|crawler|spider|slurp/i;
|
|
1285
|
+
var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
1286
|
+
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
|
|
1287
|
+
|
|
1288
|
+
// src/edge-core/events.ts
|
|
1289
|
+
function sampled(rate, random = Math.random) {
|
|
1290
|
+
if (rate <= 0) return false;
|
|
1291
|
+
if (rate >= 1) return true;
|
|
1292
|
+
return random() < rate;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// src/adapters/anonymous-beacon.ts
|
|
1296
|
+
function readHeader(raw, name) {
|
|
1297
|
+
const value = raw[name];
|
|
1298
|
+
if (typeof value === "string") return value;
|
|
1299
|
+
if (Array.isArray(value)) return value[0];
|
|
1300
|
+
return void 0;
|
|
1301
|
+
}
|
|
1302
|
+
function classifyPassThroughTraffic(path, rawHeaders) {
|
|
1303
|
+
if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
|
|
1304
|
+
const credentials = extractCredentials(rawHeaders, {});
|
|
1305
|
+
if (credentials.astraId || credentials.apiKey || credentials.jwt) {
|
|
1306
|
+
return { kind: "never" };
|
|
1307
|
+
}
|
|
1308
|
+
const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
|
|
1309
|
+
const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1310
|
+
const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
|
|
1311
|
+
if (fingerprint) {
|
|
1312
|
+
return {
|
|
1313
|
+
kind: "platform",
|
|
1314
|
+
protocol: "platform-ua",
|
|
1315
|
+
evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
|
|
1316
|
+
};
|
|
1317
|
+
}
|
|
1318
|
+
if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
|
|
1319
|
+
return {
|
|
1320
|
+
kind: "anonymous-bot",
|
|
1321
|
+
protocol: "ua-heuristic",
|
|
1322
|
+
evidence: { userAgent: userAgent || "(none)" }
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
|
|
1326
|
+
return {
|
|
1327
|
+
kind: "anonymous-bot",
|
|
1328
|
+
protocol: "ua-heuristic",
|
|
1329
|
+
evidence: { userAgent, spoofedBrowser: true }
|
|
1330
|
+
};
|
|
1331
|
+
}
|
|
1332
|
+
return { kind: "never" };
|
|
1333
|
+
}
|
|
1334
|
+
function createAnonymousBeacon(config, options) {
|
|
1335
|
+
const counterpartyId = config.counterpartyId;
|
|
1336
|
+
if (!counterpartyId) return () => {
|
|
1337
|
+
};
|
|
1338
|
+
const random = options?.random ?? Math.random;
|
|
1339
|
+
let edgeConfig;
|
|
1340
|
+
let refreshing = false;
|
|
1341
|
+
const refreshConfig = () => {
|
|
1342
|
+
if (refreshing) return;
|
|
1343
|
+
refreshing = true;
|
|
1344
|
+
getEdgeConfig(config, counterpartyId).then((cfg) => {
|
|
1345
|
+
edgeConfig = cfg;
|
|
1346
|
+
}).catch(() => {
|
|
1347
|
+
}).finally(() => {
|
|
1348
|
+
refreshing = false;
|
|
1349
|
+
});
|
|
1350
|
+
};
|
|
1351
|
+
refreshConfig();
|
|
1352
|
+
return (input) => {
|
|
1353
|
+
try {
|
|
1354
|
+
refreshConfig();
|
|
1355
|
+
const cfg = edgeConfig;
|
|
1356
|
+
if (!cfg) return;
|
|
1357
|
+
const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
|
|
1358
|
+
if (traffic.kind === "never") return;
|
|
1359
|
+
if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
const userAgent = readHeader(input.rawHeaders, "user-agent");
|
|
1363
|
+
const xff = input.rawHeaders["x-forwarded-for"];
|
|
1364
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1365
|
+
const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
|
|
1366
|
+
reportUnregisteredAttempt(config, {
|
|
1367
|
+
...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
|
|
1368
|
+
counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
|
|
1369
|
+
...sourceIp && { sourceIp },
|
|
1370
|
+
...userAgent && { userAgent },
|
|
1371
|
+
requestPath: input.requestPath,
|
|
1372
|
+
requestMethod: input.requestMethod,
|
|
1373
|
+
...traffic.protocol && { protocol: traffic.protocol },
|
|
1374
|
+
...traffic.evidence && { evidence: traffic.evidence },
|
|
1375
|
+
observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
|
|
1376
|
+
}).catch(() => {
|
|
1377
|
+
});
|
|
1378
|
+
} catch {
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1013
1382
|
|
|
1014
1383
|
// src/adapters/express.ts
|
|
1015
1384
|
function defaultExtractCredentials(req) {
|
|
@@ -1021,7 +1390,7 @@ function defaultExtractCredentials(req) {
|
|
|
1021
1390
|
function extractAstraSyncCredentials(req) {
|
|
1022
1391
|
return extractHttpCredentials(req.headers);
|
|
1023
1392
|
}
|
|
1024
|
-
function
|
|
1393
|
+
function headerValue2(value) {
|
|
1025
1394
|
if (typeof value === "string") return value;
|
|
1026
1395
|
if (Array.isArray(value)) return value[0];
|
|
1027
1396
|
return void 0;
|
|
@@ -1029,9 +1398,9 @@ function headerValue(value) {
|
|
|
1029
1398
|
function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
|
|
1030
1399
|
return resolveHttpPdlss({
|
|
1031
1400
|
method: req.method,
|
|
1032
|
-
astraPurpose:
|
|
1033
|
-
astraAction:
|
|
1034
|
-
legacyPurpose:
|
|
1401
|
+
astraPurpose: headerValue2(req.headers["x-astra-purpose"]),
|
|
1402
|
+
astraAction: headerValue2(req.headers["x-astra-action"]),
|
|
1403
|
+
legacyPurpose: headerValue2(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
|
|
1035
1404
|
queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
|
|
1036
1405
|
routePurpose: routeConfig?.purpose,
|
|
1037
1406
|
routeAction: routeConfig?.action,
|
|
@@ -1114,6 +1483,11 @@ function defaultOnDenied(result, _req, res) {
|
|
|
1114
1483
|
}
|
|
1115
1484
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
1116
1485
|
function createMiddleware(options) {
|
|
1486
|
+
if ("routes" in options) {
|
|
1487
|
+
console.warn(
|
|
1488
|
+
"[VerificationGateway] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
|
|
1489
|
+
);
|
|
1490
|
+
}
|
|
1117
1491
|
const {
|
|
1118
1492
|
extractCredentials: customExtractCredentials,
|
|
1119
1493
|
extractPurpose: customExtractPurpose,
|
|
@@ -1125,12 +1499,25 @@ function createMiddleware(options) {
|
|
|
1125
1499
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
1126
1500
|
failOnError = "open",
|
|
1127
1501
|
caseInsensitiveRouteMatch = false,
|
|
1502
|
+
anonymousBeacon = true,
|
|
1128
1503
|
...config
|
|
1129
1504
|
} = options;
|
|
1130
1505
|
if (config.apiBaseUrl) {
|
|
1131
1506
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
1132
1507
|
});
|
|
1133
1508
|
}
|
|
1509
|
+
const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "api" }) : void 0;
|
|
1510
|
+
const beaconPassThrough = emitAnonymousBeacon ? (req) => {
|
|
1511
|
+
const xff = req.headers["x-forwarded-for"];
|
|
1512
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1513
|
+
emitAnonymousBeacon({
|
|
1514
|
+
requestPath: req.path,
|
|
1515
|
+
requestMethod: req.method,
|
|
1516
|
+
counterpartyUrl: config.counterpartyUrl || `${req.protocol}://${req.get("host")}`,
|
|
1517
|
+
sourceIp: req.ip ?? (xffStr ? xffStr.split(",")[0].trim() : void 0),
|
|
1518
|
+
rawHeaders: req.headers
|
|
1519
|
+
});
|
|
1520
|
+
} : void 0;
|
|
1134
1521
|
let cachedRoutes = [];
|
|
1135
1522
|
let lastFetchAt = 0;
|
|
1136
1523
|
let refreshing = null;
|
|
@@ -1191,6 +1578,7 @@ function createMiddleware(options) {
|
|
|
1191
1578
|
cachedRoutes.length === 0 ? "no-policy" : "no-match"
|
|
1192
1579
|
);
|
|
1193
1580
|
}
|
|
1581
|
+
beaconPassThrough?.(req);
|
|
1194
1582
|
return next();
|
|
1195
1583
|
}
|
|
1196
1584
|
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
@@ -1201,6 +1589,7 @@ function createMiddleware(options) {
|
|
|
1201
1589
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
1202
1590
|
res.setHeader("X-Astra-Gateway-Reason", "route-none");
|
|
1203
1591
|
}
|
|
1592
|
+
beaconPassThrough?.(req);
|
|
1204
1593
|
return next();
|
|
1205
1594
|
}
|
|
1206
1595
|
const pdlssPair = resolveRequestPdlss(
|
|
@@ -1293,16 +1682,10 @@ function createMiddleware(options) {
|
|
|
1293
1682
|
forwardedFor: forwardedForStr,
|
|
1294
1683
|
agentCardUrl,
|
|
1295
1684
|
// Workstream D — capture EVERY inbound header (SDK-only merchants
|
|
1296
|
-
// have no edge to do it upstream)
|
|
1297
|
-
// stripped; out of the verify
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
return {
|
|
1301
|
-
headers,
|
|
1302
|
-
...apiKeyFormat && { apiKeyFormat },
|
|
1303
|
-
...platformHeaders && { platformHeaders }
|
|
1304
|
-
};
|
|
1305
|
-
})()
|
|
1685
|
+
// have no edge to do it upstream) plus the CDN-derived connection
|
|
1686
|
+
// block. Local string work, secrets stripped; out of the verify
|
|
1687
|
+
// cache key so it doesn't defeat caching.
|
|
1688
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
1306
1689
|
}
|
|
1307
1690
|
};
|
|
1308
1691
|
let result = await verify(config, verificationRequest);
|