@astrasyncai/verification-gateway 4.0.0 → 4.2.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 +100 -13
- package/dist/bin/astrasync.js +168 -27
- 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 +24 -13
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +24 -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
package/dist/adapters/express.js
CHANGED
|
@@ -92,7 +92,7 @@ function getTrustLevel(score) {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// src/version.ts
|
|
95
|
-
var SDK_VERSION = "4.
|
|
95
|
+
var SDK_VERSION = "4.1.0";
|
|
96
96
|
|
|
97
97
|
// src/well-known.ts
|
|
98
98
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
@@ -704,6 +704,21 @@ async function fetchRoutes(config, counterpartyId) {
|
|
|
704
704
|
return null;
|
|
705
705
|
}
|
|
706
706
|
}
|
|
707
|
+
async function reportUnregisteredAttempt(config, data) {
|
|
708
|
+
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
709
|
+
const { counterpartyUrl, ...rest } = data;
|
|
710
|
+
const body = {
|
|
711
|
+
...rest,
|
|
712
|
+
...counterpartyUrl && { counterpartyUrl },
|
|
713
|
+
...config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
714
|
+
};
|
|
715
|
+
await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
716
|
+
method: "POST",
|
|
717
|
+
headers: { "Content-Type": "application/json" },
|
|
718
|
+
body: JSON.stringify(body)
|
|
719
|
+
}).catch(() => {
|
|
720
|
+
});
|
|
721
|
+
}
|
|
707
722
|
async function reportCounterpartyPreCheckFailure(config, data) {
|
|
708
723
|
const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
|
|
709
724
|
await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
|
|
@@ -901,6 +916,7 @@ function performCounterpartyPreCheck(routeConfig, astraCreds, purpose) {
|
|
|
901
916
|
}
|
|
902
917
|
|
|
903
918
|
// src/metadata-capture.ts
|
|
919
|
+
var CAPTURE_SCHEMA_VERSION = 1;
|
|
904
920
|
var SECRET_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
905
921
|
"cookie",
|
|
906
922
|
"set-cookie",
|
|
@@ -1037,6 +1053,359 @@ function sanitizeHeaders(raw) {
|
|
|
1037
1053
|
if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
|
|
1038
1054
|
return result;
|
|
1039
1055
|
}
|
|
1056
|
+
var CONNECTION_HEADER_SOURCES = [
|
|
1057
|
+
["ip", ["cf-connecting-ip", "fastly-client-ip", "true-client-ip", "x-real-ip"]],
|
|
1058
|
+
[
|
|
1059
|
+
"country",
|
|
1060
|
+
["cf-ipcountry", "x-vercel-ip-country", "cloudfront-viewer-country", "fastly-geo-country"]
|
|
1061
|
+
],
|
|
1062
|
+
["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
|
|
1063
|
+
["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
|
|
1064
|
+
["asn", ["cloudfront-viewer-asn"]],
|
|
1065
|
+
["tlsVersion", ["cloudfront-viewer-tls"]],
|
|
1066
|
+
["httpVersion", ["cloudfront-viewer-http-version"]],
|
|
1067
|
+
// TLS fingerprints can only be computed where TLS terminates. When a CDN
|
|
1068
|
+
// terminates TLS in front of the merchant, its fingerprint headers ARE the
|
|
1069
|
+
// connection-layer truth — normalising them here is the permanent design.
|
|
1070
|
+
["tlsFingerprint", ["cf-ja3-hash"]],
|
|
1071
|
+
["ja4", ["cf-ja4"]]
|
|
1072
|
+
];
|
|
1073
|
+
function deriveConnectionFromHeaders(headers) {
|
|
1074
|
+
if (!headers) return void 0;
|
|
1075
|
+
const lower = {};
|
|
1076
|
+
for (const name of Object.keys(headers)) {
|
|
1077
|
+
const value = headers[name];
|
|
1078
|
+
if (typeof value === "string" && value.length > 0) lower[name.toLowerCase()] = value;
|
|
1079
|
+
}
|
|
1080
|
+
const connection = {};
|
|
1081
|
+
for (const [key, sources] of CONNECTION_HEADER_SOURCES) {
|
|
1082
|
+
for (const source of sources) {
|
|
1083
|
+
const value = lower[source]?.trim();
|
|
1084
|
+
if (value) {
|
|
1085
|
+
connection[key] = value;
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (!connection.ip) {
|
|
1091
|
+
const xff = lower["x-forwarded-for"];
|
|
1092
|
+
const client = xff?.split(",")[0]?.trim();
|
|
1093
|
+
if (client) connection.ip = client;
|
|
1094
|
+
}
|
|
1095
|
+
return Object.keys(connection).length > 0 ? connection : void 0;
|
|
1096
|
+
}
|
|
1097
|
+
function buildSdkObservedMetadata(rawHeaders) {
|
|
1098
|
+
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1099
|
+
const flat = {};
|
|
1100
|
+
if (rawHeaders) {
|
|
1101
|
+
for (const name of Object.keys(rawHeaders)) {
|
|
1102
|
+
const value = rawHeaders[name];
|
|
1103
|
+
if (value === void 0) continue;
|
|
1104
|
+
flat[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : String(value);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
const connection = deriveConnectionFromHeaders(flat);
|
|
1108
|
+
return {
|
|
1109
|
+
headers,
|
|
1110
|
+
...apiKeyFormat && { apiKeyFormat },
|
|
1111
|
+
...platformHeaders && { platformHeaders },
|
|
1112
|
+
...connection && { connection },
|
|
1113
|
+
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
1114
|
+
};
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/edge-config.ts
|
|
1118
|
+
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
1119
|
+
version: 1,
|
|
1120
|
+
mode: "observe",
|
|
1121
|
+
depth: "classify",
|
|
1122
|
+
pathRules: [],
|
|
1123
|
+
sampling: Object.freeze({ anonymousBeaconRate: 0.1 }),
|
|
1124
|
+
failurePosture: "open"
|
|
1125
|
+
});
|
|
1126
|
+
var EDGE_MODES = ["observe", "enforce"];
|
|
1127
|
+
var EDGE_DEPTHS = ["classify", "authenticate", "authorize"];
|
|
1128
|
+
function isEdgeConfig(value) {
|
|
1129
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1130
|
+
const v = value;
|
|
1131
|
+
if (v.version !== 1) return false;
|
|
1132
|
+
if (!EDGE_MODES.includes(v.mode)) return false;
|
|
1133
|
+
if (!EDGE_DEPTHS.includes(v.depth)) return false;
|
|
1134
|
+
if (!Array.isArray(v.pathRules)) return false;
|
|
1135
|
+
for (const rule of v.pathRules) {
|
|
1136
|
+
if (typeof rule !== "object" || rule === null) return false;
|
|
1137
|
+
const r = rule;
|
|
1138
|
+
if (typeof r.pattern !== "string" || r.pattern.length === 0) return false;
|
|
1139
|
+
if (r.mode !== void 0 && !EDGE_MODES.includes(r.mode)) return false;
|
|
1140
|
+
if (r.depth !== void 0 && !EDGE_DEPTHS.includes(r.depth)) return false;
|
|
1141
|
+
}
|
|
1142
|
+
const sampling = v.sampling;
|
|
1143
|
+
if (typeof sampling !== "object" || sampling === null || typeof sampling.anonymousBeaconRate !== "number" || sampling.anonymousBeaconRate < 0 || sampling.anonymousBeaconRate > 1) {
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
return v.failurePosture === "open";
|
|
1147
|
+
}
|
|
1148
|
+
async function fetchEdgeConfig(config, counterpartyId, opts) {
|
|
1149
|
+
if (!counterpartyId) return null;
|
|
1150
|
+
const headers = { Accept: "application/json" };
|
|
1151
|
+
if (config.apiKey) {
|
|
1152
|
+
headers["Authorization"] = `Bearer ${config.apiKey}`;
|
|
1153
|
+
headers["X-API-Key"] = config.apiKey;
|
|
1154
|
+
}
|
|
1155
|
+
if (opts?.etag) headers["If-None-Match"] = opts.etag;
|
|
1156
|
+
try {
|
|
1157
|
+
const response = await fetch(
|
|
1158
|
+
`${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
|
|
1159
|
+
{ method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
|
|
1160
|
+
);
|
|
1161
|
+
if (response.status === 304) return { notModified: true };
|
|
1162
|
+
if (!response.ok) return null;
|
|
1163
|
+
const body = await response.json();
|
|
1164
|
+
const edgeConfig = body.data?.edgeConfig;
|
|
1165
|
+
if (!isEdgeConfig(edgeConfig)) return null;
|
|
1166
|
+
return { edgeConfig, etag: response.headers.get("etag") ?? void 0 };
|
|
1167
|
+
} catch {
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
var DEFAULT_TTL_MS = 60 * 1e3;
|
|
1172
|
+
var DEFAULT_MAX_STALE_MS = 24 * 60 * 60 * 1e3;
|
|
1173
|
+
var cache2 = /* @__PURE__ */ new Map();
|
|
1174
|
+
var inflight2 = /* @__PURE__ */ new Map();
|
|
1175
|
+
function cacheKey(apiBaseUrl, counterpartyId) {
|
|
1176
|
+
return `${apiBaseUrl}|${counterpartyId}`;
|
|
1177
|
+
}
|
|
1178
|
+
function degradeToObserve(config) {
|
|
1179
|
+
if (config.mode !== "enforce" && !config.pathRules.some((r) => r.mode === "enforce")) {
|
|
1180
|
+
return config;
|
|
1181
|
+
}
|
|
1182
|
+
return {
|
|
1183
|
+
...config,
|
|
1184
|
+
mode: "observe",
|
|
1185
|
+
pathRules: config.pathRules.map((r) => r.mode === "enforce" ? { ...r, mode: "observe" } : r)
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
async function revalidate(config, counterpartyId) {
|
|
1189
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1190
|
+
const existing = inflight2.get(key);
|
|
1191
|
+
if (existing) return existing;
|
|
1192
|
+
const promise = fetchEdgeConfig(config, counterpartyId, { etag: cache2.get(key)?.etag }).then((result) => {
|
|
1193
|
+
if (result === null) return;
|
|
1194
|
+
const now = Date.now();
|
|
1195
|
+
if ("notModified" in result) {
|
|
1196
|
+
const entry = cache2.get(key);
|
|
1197
|
+
if (entry) entry.lastSuccessAt = now;
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
cache2.set(key, { config: result.edgeConfig, etag: result.etag, lastSuccessAt: now });
|
|
1201
|
+
}).finally(() => {
|
|
1202
|
+
inflight2.delete(key);
|
|
1203
|
+
});
|
|
1204
|
+
inflight2.set(key, promise);
|
|
1205
|
+
return promise;
|
|
1206
|
+
}
|
|
1207
|
+
async function getEdgeConfig(config, counterpartyId, opts) {
|
|
1208
|
+
const ttlMs = opts?.ttlMs ?? DEFAULT_TTL_MS;
|
|
1209
|
+
const maxStaleMs = opts?.maxStaleMs ?? DEFAULT_MAX_STALE_MS;
|
|
1210
|
+
const key = cacheKey(config.apiBaseUrl, counterpartyId);
|
|
1211
|
+
const entry = cache2.get(key);
|
|
1212
|
+
if (entry) {
|
|
1213
|
+
const age = Date.now() - entry.lastSuccessAt;
|
|
1214
|
+
if (age > ttlMs) {
|
|
1215
|
+
revalidate(config, counterpartyId).catch(() => {
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
return age > maxStaleMs ? degradeToObserve(entry.config) : entry.config;
|
|
1219
|
+
}
|
|
1220
|
+
await revalidate(config, counterpartyId).catch(() => {
|
|
1221
|
+
});
|
|
1222
|
+
return cache2.get(key)?.config ?? DEFAULT_EDGE_CONFIG;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// src/platform-signatures.ts
|
|
1226
|
+
var PLATFORM_AGENT_SIGNATURES = [
|
|
1227
|
+
{
|
|
1228
|
+
vendor: "claude",
|
|
1229
|
+
displayName: "Anthropic Claude",
|
|
1230
|
+
uaPatterns: [/claude/i, /anthropic/i],
|
|
1231
|
+
agentCardPatterns: [/anthropic\.com/i, /claude\.ai/i],
|
|
1232
|
+
apiKeyFormatPatterns: [/^sk-ant/i],
|
|
1233
|
+
headerPatterns: [/^anthropic-version/i, /^anthropic-beta/i, /^x-anthropic-/i]
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
vendor: "chatgpt",
|
|
1237
|
+
displayName: "OpenAI ChatGPT",
|
|
1238
|
+
uaPatterns: [/chatgpt/i, /openai/i, /gpt-?\d/i],
|
|
1239
|
+
agentCardPatterns: [/openai\.com/i, /chatgpt\.com/i],
|
|
1240
|
+
apiKeyFormatPatterns: [/^sk-proj/i, /^sk-svcacct/i],
|
|
1241
|
+
headerPatterns: [/^openai-organization/i, /^openai-project/i, /^openai-version/i]
|
|
1242
|
+
},
|
|
1243
|
+
{
|
|
1244
|
+
vendor: "gemini",
|
|
1245
|
+
displayName: "Google Gemini",
|
|
1246
|
+
uaPatterns: [/gemini/i, /google-gemini/i, /bard/i],
|
|
1247
|
+
agentCardPatterns: [/gemini\.google\.com/i, /ai\.google\.dev/i],
|
|
1248
|
+
apiKeyFormatPatterns: [/^aiza/i],
|
|
1249
|
+
headerPatterns: [/^x-goog-user-project/i, /^x-goog-api-client/i]
|
|
1250
|
+
},
|
|
1251
|
+
{
|
|
1252
|
+
vendor: "cursor",
|
|
1253
|
+
displayName: "Cursor",
|
|
1254
|
+
uaPatterns: [/cursor/i],
|
|
1255
|
+
agentCardPatterns: [/cursor\.sh/i, /cursor\.com/i]
|
|
1256
|
+
},
|
|
1257
|
+
{
|
|
1258
|
+
vendor: "goose",
|
|
1259
|
+
displayName: "Block Goose",
|
|
1260
|
+
uaPatterns: [/goose/i, /block-goose/i],
|
|
1261
|
+
agentCardPatterns: [/block\.xyz\/goose/i, /goose\.tools/i]
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
vendor: "perplexity",
|
|
1265
|
+
displayName: "Perplexity",
|
|
1266
|
+
uaPatterns: [/perplexity/i],
|
|
1267
|
+
agentCardPatterns: [/perplexity\.ai/i],
|
|
1268
|
+
apiKeyFormatPatterns: [/^pplx-/i]
|
|
1269
|
+
}
|
|
1270
|
+
];
|
|
1271
|
+
function matchPlatformSignature(signatures, input) {
|
|
1272
|
+
const ua = input.userAgent ?? "";
|
|
1273
|
+
const cardUrl = input.agentCardUrl ?? "";
|
|
1274
|
+
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
1275
|
+
const headerLines = [];
|
|
1276
|
+
if (input.platformHeaders) {
|
|
1277
|
+
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
1278
|
+
headerLines.push(name, `${name}: ${value}`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
for (const sig of signatures) {
|
|
1282
|
+
const uaMatch = sig.uaPatterns?.some((p) => p.test(ua));
|
|
1283
|
+
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
1284
|
+
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
1285
|
+
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
1286
|
+
if (uaMatch || cardMatch || keyMatch || headerMatch) {
|
|
1287
|
+
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
1288
|
+
const cardHost = (() => {
|
|
1289
|
+
try {
|
|
1290
|
+
return cardUrl ? new URL(cardUrl).host : "";
|
|
1291
|
+
} catch {
|
|
1292
|
+
return "";
|
|
1293
|
+
}
|
|
1294
|
+
})();
|
|
1295
|
+
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
1296
|
+
return {
|
|
1297
|
+
vendor: sig.vendor,
|
|
1298
|
+
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
1299
|
+
displayName: sig.displayName
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
return void 0;
|
|
1304
|
+
}
|
|
1305
|
+
function detectPlatformFingerprint(input) {
|
|
1306
|
+
return matchPlatformSignature(PLATFORM_AGENT_SIGNATURES, input);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// src/edge-core/classify.ts
|
|
1310
|
+
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;
|
|
1311
|
+
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;
|
|
1312
|
+
var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
1313
|
+
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
|
|
1314
|
+
|
|
1315
|
+
// src/edge-core/events.ts
|
|
1316
|
+
function sampled(rate, random = Math.random) {
|
|
1317
|
+
if (rate <= 0) return false;
|
|
1318
|
+
if (rate >= 1) return true;
|
|
1319
|
+
return random() < rate;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
// src/adapters/anonymous-beacon.ts
|
|
1323
|
+
function readHeader(raw, name) {
|
|
1324
|
+
const value = raw[name];
|
|
1325
|
+
if (typeof value === "string") return value;
|
|
1326
|
+
if (Array.isArray(value)) return value[0];
|
|
1327
|
+
return void 0;
|
|
1328
|
+
}
|
|
1329
|
+
function classifyPassThroughTraffic(path, rawHeaders) {
|
|
1330
|
+
if (_patterns.STATIC_EXTENSIONS.test(path)) return { kind: "never" };
|
|
1331
|
+
const credentials = extractCredentials(rawHeaders, {});
|
|
1332
|
+
if (credentials.astraId || credentials.apiKey || credentials.jwt) {
|
|
1333
|
+
return { kind: "never" };
|
|
1334
|
+
}
|
|
1335
|
+
const userAgent = readHeader(rawHeaders, "user-agent") ?? "";
|
|
1336
|
+
const { apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
|
|
1337
|
+
const fingerprint = detectPlatformFingerprint({ userAgent, apiKeyFormat, platformHeaders });
|
|
1338
|
+
if (fingerprint) {
|
|
1339
|
+
return {
|
|
1340
|
+
kind: "platform",
|
|
1341
|
+
protocol: "platform-ua",
|
|
1342
|
+
evidence: { vendor: fingerprint.vendor, userAgent: userAgent || "(none)" }
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
if (!userAgent || _patterns.BOT_UA_PATTERN.test(userAgent) || !_patterns.BROWSER_UA_PATTERN.test(userAgent)) {
|
|
1346
|
+
return {
|
|
1347
|
+
kind: "anonymous-bot",
|
|
1348
|
+
protocol: "ua-heuristic",
|
|
1349
|
+
evidence: { userAgent: userAgent || "(none)" }
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
if (!readHeader(rawHeaders, "sec-fetch-mode") && !readHeader(rawHeaders, "accept-language")) {
|
|
1353
|
+
return {
|
|
1354
|
+
kind: "anonymous-bot",
|
|
1355
|
+
protocol: "ua-heuristic",
|
|
1356
|
+
evidence: { userAgent, spoofedBrowser: true }
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
return { kind: "never" };
|
|
1360
|
+
}
|
|
1361
|
+
function createAnonymousBeacon(config, options) {
|
|
1362
|
+
const counterpartyId = config.counterpartyId;
|
|
1363
|
+
if (!counterpartyId) return () => {
|
|
1364
|
+
};
|
|
1365
|
+
const random = options?.random ?? Math.random;
|
|
1366
|
+
let edgeConfig;
|
|
1367
|
+
let refreshing = false;
|
|
1368
|
+
const refreshConfig = () => {
|
|
1369
|
+
if (refreshing) return;
|
|
1370
|
+
refreshing = true;
|
|
1371
|
+
getEdgeConfig(config, counterpartyId).then((cfg) => {
|
|
1372
|
+
edgeConfig = cfg;
|
|
1373
|
+
}).catch(() => {
|
|
1374
|
+
}).finally(() => {
|
|
1375
|
+
refreshing = false;
|
|
1376
|
+
});
|
|
1377
|
+
};
|
|
1378
|
+
refreshConfig();
|
|
1379
|
+
return (input) => {
|
|
1380
|
+
try {
|
|
1381
|
+
refreshConfig();
|
|
1382
|
+
const cfg = edgeConfig;
|
|
1383
|
+
if (!cfg) return;
|
|
1384
|
+
const traffic = classifyPassThroughTraffic(input.requestPath, input.rawHeaders);
|
|
1385
|
+
if (traffic.kind === "never") return;
|
|
1386
|
+
if (traffic.kind === "anonymous-bot" && !sampled(cfg.sampling.anonymousBeaconRate, random)) {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
const userAgent = readHeader(input.rawHeaders, "user-agent");
|
|
1390
|
+
const xff = input.rawHeaders["x-forwarded-for"];
|
|
1391
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1392
|
+
const sourceIp = input.sourceIp ?? xffStr?.split(",")[0]?.trim();
|
|
1393
|
+
reportUnregisteredAttempt(config, {
|
|
1394
|
+
...input.counterpartyUrl && { counterpartyUrl: input.counterpartyUrl },
|
|
1395
|
+
counterpartyType: options?.counterpartyType ?? config.counterpartyType ?? "api",
|
|
1396
|
+
...sourceIp && { sourceIp },
|
|
1397
|
+
...userAgent && { userAgent },
|
|
1398
|
+
requestPath: input.requestPath,
|
|
1399
|
+
requestMethod: input.requestMethod,
|
|
1400
|
+
...traffic.protocol && { protocol: traffic.protocol },
|
|
1401
|
+
...traffic.evidence && { evidence: traffic.evidence },
|
|
1402
|
+
observedMetadata: buildSdkObservedMetadata(input.rawHeaders)
|
|
1403
|
+
}).catch(() => {
|
|
1404
|
+
});
|
|
1405
|
+
} catch {
|
|
1406
|
+
}
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1040
1409
|
|
|
1041
1410
|
// src/adapters/express.ts
|
|
1042
1411
|
function defaultExtractCredentials(req) {
|
|
@@ -1048,7 +1417,7 @@ function defaultExtractCredentials(req) {
|
|
|
1048
1417
|
function extractAstraSyncCredentials(req) {
|
|
1049
1418
|
return extractHttpCredentials(req.headers);
|
|
1050
1419
|
}
|
|
1051
|
-
function
|
|
1420
|
+
function headerValue2(value) {
|
|
1052
1421
|
if (typeof value === "string") return value;
|
|
1053
1422
|
if (Array.isArray(value)) return value[0];
|
|
1054
1423
|
return void 0;
|
|
@@ -1056,9 +1425,9 @@ function headerValue(value) {
|
|
|
1056
1425
|
function resolveRequestPdlss(req, routeConfig, customExtractPurpose, customExtractAction) {
|
|
1057
1426
|
return resolveHttpPdlss({
|
|
1058
1427
|
method: req.method,
|
|
1059
|
-
astraPurpose:
|
|
1060
|
-
astraAction:
|
|
1061
|
-
legacyPurpose:
|
|
1428
|
+
astraPurpose: headerValue2(req.headers["x-astra-purpose"]),
|
|
1429
|
+
astraAction: headerValue2(req.headers["x-astra-action"]),
|
|
1430
|
+
legacyPurpose: headerValue2(req.headers["x-purpose"] ?? req.headers["X-Purpose"]),
|
|
1062
1431
|
queryPurpose: typeof req.query.purpose === "string" ? req.query.purpose : void 0,
|
|
1063
1432
|
routePurpose: routeConfig?.purpose,
|
|
1064
1433
|
routeAction: routeConfig?.action,
|
|
@@ -1141,6 +1510,11 @@ function defaultOnDenied(result, _req, res) {
|
|
|
1141
1510
|
}
|
|
1142
1511
|
var DEFAULT_ROUTES_REFRESH_MS = 5 * 60 * 1e3;
|
|
1143
1512
|
function createMiddleware(options) {
|
|
1513
|
+
if ("routes" in options) {
|
|
1514
|
+
console.warn(
|
|
1515
|
+
"[VerificationGateway] `routes` is dashboard-managed since v2.9.7; this option is ignored \u2014 configure route policy in your AstraSync dashboard."
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1144
1518
|
const {
|
|
1145
1519
|
extractCredentials: customExtractCredentials,
|
|
1146
1520
|
extractPurpose: customExtractPurpose,
|
|
@@ -1152,12 +1526,25 @@ function createMiddleware(options) {
|
|
|
1152
1526
|
routesRefreshMs = DEFAULT_ROUTES_REFRESH_MS,
|
|
1153
1527
|
failOnError = "open",
|
|
1154
1528
|
caseInsensitiveRouteMatch = false,
|
|
1529
|
+
anonymousBeacon = true,
|
|
1155
1530
|
...config
|
|
1156
1531
|
} = options;
|
|
1157
1532
|
if (config.apiBaseUrl) {
|
|
1158
1533
|
prefetchWellKnown(config.apiBaseUrl).catch(() => {
|
|
1159
1534
|
});
|
|
1160
1535
|
}
|
|
1536
|
+
const emitAnonymousBeacon = anonymousBeacon ? createAnonymousBeacon(config, { counterpartyType: config.counterpartyType ?? "api" }) : void 0;
|
|
1537
|
+
const beaconPassThrough = emitAnonymousBeacon ? (req) => {
|
|
1538
|
+
const xff = req.headers["x-forwarded-for"];
|
|
1539
|
+
const xffStr = Array.isArray(xff) ? xff.join(", ") : xff;
|
|
1540
|
+
emitAnonymousBeacon({
|
|
1541
|
+
requestPath: req.path,
|
|
1542
|
+
requestMethod: req.method,
|
|
1543
|
+
counterpartyUrl: config.counterpartyUrl || `${req.protocol}://${req.get("host")}`,
|
|
1544
|
+
sourceIp: req.ip ?? (xffStr ? xffStr.split(",")[0].trim() : void 0),
|
|
1545
|
+
rawHeaders: req.headers
|
|
1546
|
+
});
|
|
1547
|
+
} : void 0;
|
|
1161
1548
|
let cachedRoutes = [];
|
|
1162
1549
|
let lastFetchAt = 0;
|
|
1163
1550
|
let refreshing = null;
|
|
@@ -1218,6 +1605,7 @@ function createMiddleware(options) {
|
|
|
1218
1605
|
cachedRoutes.length === 0 ? "no-policy" : "no-match"
|
|
1219
1606
|
);
|
|
1220
1607
|
}
|
|
1608
|
+
beaconPassThrough?.(req);
|
|
1221
1609
|
return next();
|
|
1222
1610
|
}
|
|
1223
1611
|
const wellKnownUrls = config.apiBaseUrl ? await getWellKnownUrls(config.apiBaseUrl).catch(() => void 0) : void 0;
|
|
@@ -1228,6 +1616,7 @@ function createMiddleware(options) {
|
|
|
1228
1616
|
res.setHeader("X-Astra-Gateway-Mode", "unenforced");
|
|
1229
1617
|
res.setHeader("X-Astra-Gateway-Reason", "route-none");
|
|
1230
1618
|
}
|
|
1619
|
+
beaconPassThrough?.(req);
|
|
1231
1620
|
return next();
|
|
1232
1621
|
}
|
|
1233
1622
|
const pdlssPair = resolveRequestPdlss(
|
|
@@ -1320,16 +1709,10 @@ function createMiddleware(options) {
|
|
|
1320
1709
|
forwardedFor: forwardedForStr,
|
|
1321
1710
|
agentCardUrl,
|
|
1322
1711
|
// Workstream D — capture EVERY inbound header (SDK-only merchants
|
|
1323
|
-
// have no edge to do it upstream)
|
|
1324
|
-
// stripped; out of the verify
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
return {
|
|
1328
|
-
headers,
|
|
1329
|
-
...apiKeyFormat && { apiKeyFormat },
|
|
1330
|
-
...platformHeaders && { platformHeaders }
|
|
1331
|
-
};
|
|
1332
|
-
})()
|
|
1712
|
+
// have no edge to do it upstream) plus the CDN-derived connection
|
|
1713
|
+
// block. Local string work, secrets stripped; out of the verify
|
|
1714
|
+
// cache key so it doesn't defeat caching.
|
|
1715
|
+
observedMetadata: buildSdkObservedMetadata(req.headers)
|
|
1333
1716
|
}
|
|
1334
1717
|
};
|
|
1335
1718
|
let result = await verify(config, verificationRequest);
|