@astrasyncai/verification-gateway 4.3.0 → 4.5.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/dist/adapters/express.js +104 -28
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +104 -28
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.js +63 -21
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +63 -21
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.js +103 -27
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +103 -27
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.js +33 -12
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +33 -12
- package/dist/adapters/sdk.mjs.map +1 -1
- 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 +31 -10
- package/dist/bin/astrasync-codex-hook.js +31 -10
- package/dist/bin/astrasync-guard.js +35 -14
- package/dist/bin/astrasync.js +75 -29
- package/dist/browser/background.js +31 -10
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +31 -10
- package/dist/browser/background.mjs.map +1 -1
- 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.js +31 -10
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +31 -10
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/extension.js +31 -10
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +31 -10
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/edge-config.d.mts +11 -4
- package/dist/edge-config.d.ts +11 -4
- 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 +21 -7
- package/dist/edge-core/index.d.ts +21 -7
- package/dist/edge-core/index.js +170 -25
- package/dist/edge-core/index.js.map +1 -1
- package/dist/edge-core/index.mjs +170 -25
- package/dist/edge-core/index.mjs.map +1 -1
- package/dist/gateway/gateway.js +31 -10
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +31 -10
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/index.d.mts +51 -2
- package/dist/index.d.ts +51 -2
- package/dist/index.js +169 -40
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +165 -40
- package/dist/index.mjs.map +1 -1
- package/dist/metadata-capture.d.mts +17 -0
- package/dist/metadata-capture.d.ts +17 -0
- package/dist/metadata-capture.js +25 -4
- package/dist/metadata-capture.js.map +1 -1
- package/dist/metadata-capture.mjs +25 -4
- package/dist/metadata-capture.mjs.map +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/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/verify.js +38 -17
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +38 -17
- package/dist/verify.mjs.map +1 -1
- package/package.json +2 -2
package/dist/edge-core/index.js
CHANGED
|
@@ -42,6 +42,27 @@ __export(edge_core_exports, {
|
|
|
42
42
|
});
|
|
43
43
|
module.exports = __toCommonJS(edge_core_exports);
|
|
44
44
|
|
|
45
|
+
// src/version.ts
|
|
46
|
+
var SDK_VERSION = "4.5.0";
|
|
47
|
+
|
|
48
|
+
// src/http.ts
|
|
49
|
+
var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
|
|
50
|
+
function withUserAgent(initHeaders) {
|
|
51
|
+
if (!initHeaders) return { "user-agent": SDK_USER_AGENT };
|
|
52
|
+
if (initHeaders instanceof Headers) {
|
|
53
|
+
const copy = new Headers(initHeaders);
|
|
54
|
+
if (!copy.has("user-agent")) copy.set("user-agent", SDK_USER_AGENT);
|
|
55
|
+
return copy;
|
|
56
|
+
}
|
|
57
|
+
if (Array.isArray(initHeaders)) {
|
|
58
|
+
const has2 = initHeaders.some(([name]) => name.toLowerCase() === "user-agent");
|
|
59
|
+
return has2 ? initHeaders : [...initHeaders, ["user-agent", SDK_USER_AGENT]];
|
|
60
|
+
}
|
|
61
|
+
const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === "user-agent");
|
|
62
|
+
return has ? initHeaders : { "user-agent": SDK_USER_AGENT, ...initHeaders };
|
|
63
|
+
}
|
|
64
|
+
var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
|
|
65
|
+
|
|
45
66
|
// src/edge-config.ts
|
|
46
67
|
var DEFAULT_EDGE_CONFIG = Object.freeze({
|
|
47
68
|
version: 1,
|
|
@@ -101,7 +122,13 @@ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
|
101
122
|
"x-agent-card-url",
|
|
102
123
|
"x-agent-provider"
|
|
103
124
|
]);
|
|
104
|
-
var PLATFORM_HEADER_PREFIXES = [
|
|
125
|
+
var PLATFORM_HEADER_PREFIXES = [
|
|
126
|
+
"x-goog-",
|
|
127
|
+
"x-stainless-",
|
|
128
|
+
"x-anthropic-",
|
|
129
|
+
"x-openai-",
|
|
130
|
+
"sec-ch-"
|
|
131
|
+
];
|
|
105
132
|
var KEY_FORMAT_PREFIXES = [
|
|
106
133
|
"sk-ant-api03",
|
|
107
134
|
"sk-ant-api",
|
|
@@ -173,6 +200,7 @@ function sanitizeHeaders(raw) {
|
|
|
173
200
|
const headers = {};
|
|
174
201
|
const platformHeaders = {};
|
|
175
202
|
let apiKeyFormat;
|
|
203
|
+
let cookieCount;
|
|
176
204
|
let totalBytes = 0;
|
|
177
205
|
let count = 0;
|
|
178
206
|
if (!raw) return { headers };
|
|
@@ -187,6 +215,9 @@ function sanitizeHeaders(raw) {
|
|
|
187
215
|
if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
|
|
188
216
|
continue;
|
|
189
217
|
}
|
|
218
|
+
if (name === "cookie") {
|
|
219
|
+
cookieCount = joined.split(";").filter((s) => s.trim().length > 0).length;
|
|
220
|
+
}
|
|
190
221
|
if (isSecretHeader(name)) continue;
|
|
191
222
|
let value = joined;
|
|
192
223
|
if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
|
|
@@ -202,6 +233,7 @@ function sanitizeHeaders(raw) {
|
|
|
202
233
|
const result = { headers };
|
|
203
234
|
if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
|
|
204
235
|
if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
|
|
236
|
+
if (cookieCount !== void 0) result.cookieCount = cookieCount;
|
|
205
237
|
return result;
|
|
206
238
|
}
|
|
207
239
|
|
|
@@ -249,12 +281,43 @@ var PLATFORM_AGENT_SIGNATURES = [
|
|
|
249
281
|
uaPatterns: [/perplexity/i],
|
|
250
282
|
agentCardPatterns: [/perplexity\.ai/i],
|
|
251
283
|
apiKeyFormatPatterns: [/^pplx-/i]
|
|
284
|
+
},
|
|
285
|
+
// Agentic browsers — versioned-token patterns ONLY (a stock Chrome UA must
|
|
286
|
+
// never match). Distinct vendor keys (not 'chatgpt'/'perplexity'): the
|
|
287
|
+
// browser product is a different surface than the assistant service, and
|
|
288
|
+
// the DB registry mirrors one row per vendor. These sit after the primary
|
|
289
|
+
// vendor entries so a UA carrying an explicit vendor token keeps its
|
|
290
|
+
// primary attribution; a browser-shaped UA with the agentic token is
|
|
291
|
+
// caught here BEFORE classify's human tier, which is what makes these
|
|
292
|
+
// visits observable at all. Token drift is real (some builds mimic Chrome
|
|
293
|
+
// outright) — re-verify against captured events when a new browser ships,
|
|
294
|
+
// and check `sec-ch-ua` platformHeaders for the real brand.
|
|
295
|
+
{
|
|
296
|
+
vendor: "chatgpt-atlas",
|
|
297
|
+
displayName: "ChatGPT Atlas browser",
|
|
298
|
+
uaPatterns: [/\batlas\/\d/i],
|
|
299
|
+
headerPatterns: [/^sec-ch-ua: .*atlas/i]
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
vendor: "perplexity-comet",
|
|
303
|
+
displayName: "Perplexity Comet browser",
|
|
304
|
+
uaPatterns: [/\bcomet\/\d/i],
|
|
305
|
+
headerPatterns: [/^sec-ch-ua: .*comet/i]
|
|
306
|
+
},
|
|
307
|
+
// Our own SDK self-identifies (sdkFetch stamps this UA) — first-party
|
|
308
|
+
// traffic must never pollute the unknown/anonymous pool it exists to audit.
|
|
309
|
+
{
|
|
310
|
+
vendor: "astrasync-sdk",
|
|
311
|
+
displayName: "AstraSync SDK",
|
|
312
|
+
uaPatterns: [/^astrasync-sdk\//i]
|
|
252
313
|
}
|
|
253
314
|
];
|
|
254
315
|
function matchPlatformSignature(signatures, input) {
|
|
255
316
|
const ua = input.userAgent ?? "";
|
|
256
317
|
const cardUrl = input.agentCardUrl ?? "";
|
|
257
318
|
const apiKeyFormat = input.apiKeyFormat ?? "";
|
|
319
|
+
const ja4 = input.ja4 ?? "";
|
|
320
|
+
const asn = input.asn ?? "";
|
|
258
321
|
const headerLines = [];
|
|
259
322
|
if (input.platformHeaders) {
|
|
260
323
|
for (const [name, value] of Object.entries(input.platformHeaders)) {
|
|
@@ -266,7 +329,9 @@ function matchPlatformSignature(signatures, input) {
|
|
|
266
329
|
const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
|
|
267
330
|
const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
|
|
268
331
|
const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
|
|
269
|
-
|
|
332
|
+
const ja4Match = ja4.length > 0 && sig.ja4Patterns?.some((p) => p.test(ja4));
|
|
333
|
+
const asnMatch = asn.length > 0 && sig.asnList?.includes(asn);
|
|
334
|
+
if (uaMatch || cardMatch || keyMatch || headerMatch || ja4Match || asnMatch) {
|
|
270
335
|
const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
|
|
271
336
|
const cardHost = (() => {
|
|
272
337
|
try {
|
|
@@ -275,7 +340,7 @@ function matchPlatformSignature(signatures, input) {
|
|
|
275
340
|
return "";
|
|
276
341
|
}
|
|
277
342
|
})();
|
|
278
|
-
const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
|
|
343
|
+
const keyBasis = uaBase || cardHost || apiKeyFormat || ja4 || asn && `asn${asn}` || "unknown";
|
|
279
344
|
return {
|
|
280
345
|
vendor: sig.vendor,
|
|
281
346
|
fingerprintKey: `${sig.vendor}:${keyBasis}`,
|
|
@@ -297,9 +362,6 @@ function getTrustLevel(score) {
|
|
|
297
362
|
return "BRONZE";
|
|
298
363
|
}
|
|
299
364
|
|
|
300
|
-
// src/version.ts
|
|
301
|
-
var SDK_VERSION = "4.1.0";
|
|
302
|
-
|
|
303
365
|
// src/well-known.ts
|
|
304
366
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
305
367
|
var cache = /* @__PURE__ */ new Map();
|
|
@@ -328,7 +390,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
328
390
|
initCheckPerformed = true;
|
|
329
391
|
try {
|
|
330
392
|
const probeUrl = `${apiBaseUrl}/agents/verify-access`;
|
|
331
|
-
const response = await
|
|
393
|
+
const response = await sdkFetch(probeUrl, { method: "HEAD" });
|
|
332
394
|
const contentType = response.headers.get("content-type") ?? "";
|
|
333
395
|
if (contentType.startsWith("text/html")) {
|
|
334
396
|
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).`;
|
|
@@ -342,7 +404,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
|
|
|
342
404
|
);
|
|
343
405
|
}
|
|
344
406
|
if (apiKey) {
|
|
345
|
-
const selfCheck = await
|
|
407
|
+
const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
|
|
346
408
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
347
409
|
});
|
|
348
410
|
if (selfCheck.status === 401) {
|
|
@@ -589,7 +651,7 @@ async function callVerifyAccessAPI(config, request) {
|
|
|
589
651
|
headers["X-API-Key"] = config.apiKey;
|
|
590
652
|
}
|
|
591
653
|
try {
|
|
592
|
-
const response = await
|
|
654
|
+
const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {
|
|
593
655
|
method: "POST",
|
|
594
656
|
headers,
|
|
595
657
|
body: JSON.stringify(body),
|
|
@@ -771,12 +833,15 @@ async function verify(config, request, options) {
|
|
|
771
833
|
const verificationContext = apiResponse.verificationContext;
|
|
772
834
|
const accessLevel = apiResponse.access?.accessLevel ?? "standard";
|
|
773
835
|
const result = {
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
//
|
|
777
|
-
//
|
|
778
|
-
//
|
|
779
|
-
|
|
836
|
+
// 4.5.0 (probe F1): read idVerified from verificationContext with the
|
|
837
|
+
// SAME absent⇒false default as the deny branch. The previous `!== false`
|
|
838
|
+
// default manufactured `identityVerified: true` for anonymous
|
|
839
|
+
// allow_full/allow_partial/audit responses (which allow access but omit
|
|
840
|
+
// verificationContext entirely) — the "anonymous caller labelled
|
|
841
|
+
// verified" defect. Every authenticated success response has carried
|
|
842
|
+
// `verificationContext.idVerified: true` since v2.2.4, so a truth claim
|
|
843
|
+
// this load-bearing must never be defaulted on.
|
|
844
|
+
identityVerified: apiResponse.verificationContext?.idVerified === true,
|
|
780
845
|
policyAllowed: true,
|
|
781
846
|
accessLevel,
|
|
782
847
|
agent,
|
|
@@ -834,7 +899,7 @@ async function reportUnregisteredAttempt(config, data) {
|
|
|
834
899
|
...counterpartyUrl && { counterpartyUrl },
|
|
835
900
|
...config.counterpartyId && { counterpartyId: config.counterpartyId }
|
|
836
901
|
};
|
|
837
|
-
await
|
|
902
|
+
await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
|
|
838
903
|
method: "POST",
|
|
839
904
|
headers: { "Content-Type": "application/json" },
|
|
840
905
|
body: JSON.stringify(body)
|
|
@@ -851,16 +916,39 @@ function requestUrl(req, host) {
|
|
|
851
916
|
return `https://${host}${req.uri}${qs}`;
|
|
852
917
|
}
|
|
853
918
|
function buildObservedMetadata(req) {
|
|
854
|
-
const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(req.headers);
|
|
919
|
+
const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(req.headers);
|
|
855
920
|
const connection = req.connection ?? {};
|
|
921
|
+
const queryKeys = extractQueryKeys(req.querystring);
|
|
856
922
|
return {
|
|
857
923
|
headers,
|
|
858
924
|
...apiKeyFormat && { apiKeyFormat },
|
|
859
925
|
...platformHeaders && { platformHeaders },
|
|
860
926
|
...Object.keys(connection).length > 0 && { connection },
|
|
927
|
+
...cookieCount !== void 0 && { cookieCount },
|
|
928
|
+
...queryKeys && { queryKeys },
|
|
861
929
|
schemaVersion: CAPTURE_SCHEMA_VERSION
|
|
862
930
|
};
|
|
863
931
|
}
|
|
932
|
+
var MAX_QUERY_KEYS = 32;
|
|
933
|
+
var MAX_QUERY_KEY_CHARS = 64;
|
|
934
|
+
function extractQueryKeys(querystring) {
|
|
935
|
+
if (!querystring) return void 0;
|
|
936
|
+
let params;
|
|
937
|
+
try {
|
|
938
|
+
params = new URLSearchParams(querystring);
|
|
939
|
+
} catch {
|
|
940
|
+
return void 0;
|
|
941
|
+
}
|
|
942
|
+
const keys = [];
|
|
943
|
+
for (const key of params.keys()) {
|
|
944
|
+
const normalized = key.toLowerCase().slice(0, MAX_QUERY_KEY_CHARS);
|
|
945
|
+
if (normalized && !keys.includes(normalized)) {
|
|
946
|
+
keys.push(normalized);
|
|
947
|
+
if (keys.length >= MAX_QUERY_KEYS) break;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return keys.length > 0 ? keys : void 0;
|
|
951
|
+
}
|
|
864
952
|
|
|
865
953
|
// src/edge-core/detect.ts
|
|
866
954
|
var SD_JWT_PATTERN = /^eyJ[\w-]+\.[\w-]*\.[\w-]+~/;
|
|
@@ -1004,6 +1092,7 @@ function detectCommerce(req, host) {
|
|
|
1004
1092
|
|
|
1005
1093
|
// src/edge-core/classify.ts
|
|
1006
1094
|
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;
|
|
1095
|
+
var SELF_UA_PATTERN = /^astrasync-sdk\//i;
|
|
1007
1096
|
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;
|
|
1008
1097
|
var BROWSER_UA_PATTERN = /mozilla\/\d/i;
|
|
1009
1098
|
function resolvePolicy(config, path) {
|
|
@@ -1020,6 +1109,9 @@ function resolvePolicy(config, path) {
|
|
|
1020
1109
|
}
|
|
1021
1110
|
function classify(req, host, depth) {
|
|
1022
1111
|
const userAgent = headerValue(req, "user-agent") ?? "";
|
|
1112
|
+
if (SELF_UA_PATTERN.test(userAgent)) {
|
|
1113
|
+
return { tier: "self" };
|
|
1114
|
+
}
|
|
1023
1115
|
const credentials = extractCredentials(req.headers, {});
|
|
1024
1116
|
if (credentials.astraId || credentials.apiKey || credentials.jwt) {
|
|
1025
1117
|
return { tier: "verified", protocol: "x-astra" };
|
|
@@ -1050,7 +1142,7 @@ function classify(req, host, depth) {
|
|
|
1050
1142
|
}
|
|
1051
1143
|
return { tier: "human" };
|
|
1052
1144
|
}
|
|
1053
|
-
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
|
|
1145
|
+
var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
|
|
1054
1146
|
|
|
1055
1147
|
// src/edge-core/messaging.ts
|
|
1056
1148
|
var PROTOCOL_MESSAGES = {
|
|
@@ -1090,6 +1182,20 @@ function buildDenyBody(outcome, apiBaseUrl) {
|
|
|
1090
1182
|
}
|
|
1091
1183
|
};
|
|
1092
1184
|
}
|
|
1185
|
+
function buildUnavailableBody(outcome) {
|
|
1186
|
+
return {
|
|
1187
|
+
success: false,
|
|
1188
|
+
error: {
|
|
1189
|
+
code: "VERIFICATION_UNAVAILABLE",
|
|
1190
|
+
message: "Agent verification is temporarily unavailable and this path enforces verification. Your identity was not evaluated and no denial was recorded \u2014 retry with exponential backoff.",
|
|
1191
|
+
failures: outcome.reasons,
|
|
1192
|
+
...outcome.correlationId && { correlationId: outcome.correlationId },
|
|
1193
|
+
guidance: {
|
|
1194
|
+
message: "Retry the request with exponential backoff; the condition is transient."
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
}
|
|
1093
1199
|
function buildManualReviewBody(outcome) {
|
|
1094
1200
|
return {
|
|
1095
1201
|
success: false,
|
|
@@ -1132,6 +1238,14 @@ function applyOutcome(options, outcome, policy) {
|
|
|
1132
1238
|
}
|
|
1133
1239
|
switch (outcome.decision) {
|
|
1134
1240
|
case "deny":
|
|
1241
|
+
if (outcome.unavailable) {
|
|
1242
|
+
return {
|
|
1243
|
+
action: "respond",
|
|
1244
|
+
status: 503,
|
|
1245
|
+
headers: { "Retry-After": "30" },
|
|
1246
|
+
bodyJson: buildUnavailableBody(outcome)
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1135
1249
|
return {
|
|
1136
1250
|
action: "respond",
|
|
1137
1251
|
status: 403,
|
|
@@ -1370,6 +1484,17 @@ function outcomeFromVerifyResult(classification, result) {
|
|
|
1370
1484
|
};
|
|
1371
1485
|
return outcome;
|
|
1372
1486
|
}
|
|
1487
|
+
function unavailableOutcome(classification, policy, reason, correlationId) {
|
|
1488
|
+
const outcome = baseOutcome(classification);
|
|
1489
|
+
outcome.unavailable = true;
|
|
1490
|
+
outcome.reasons = [reason];
|
|
1491
|
+
if (correlationId) outcome.correlationId = correlationId;
|
|
1492
|
+
if (policy.mode === "enforce") outcome.decision = "deny";
|
|
1493
|
+
return outcome;
|
|
1494
|
+
}
|
|
1495
|
+
function isApiErrorResult(result) {
|
|
1496
|
+
return (result.failures ?? []).some((f) => f.dimension === "verify_access.api_error") === true && !result.misconfigured;
|
|
1497
|
+
}
|
|
1373
1498
|
async function runTier(options, req, classification, policy, apiKey, sampling) {
|
|
1374
1499
|
const budgets = options.budgets ?? {};
|
|
1375
1500
|
const beaconMs = budgets.beaconMs ?? 300;
|
|
@@ -1396,9 +1521,19 @@ async function runTier(options, req, classification, policy, apiKey, sampling) {
|
|
|
1396
1521
|
verifyMs
|
|
1397
1522
|
);
|
|
1398
1523
|
if (!result) {
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1524
|
+
return unavailableOutcome(
|
|
1525
|
+
classification,
|
|
1526
|
+
policy,
|
|
1527
|
+
"verify-access unreachable within budget"
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1530
|
+
if (isApiErrorResult(result)) {
|
|
1531
|
+
return unavailableOutcome(
|
|
1532
|
+
classification,
|
|
1533
|
+
policy,
|
|
1534
|
+
result.denialReasons?.[0] ?? "verify-access api error",
|
|
1535
|
+
result.correlationId
|
|
1536
|
+
);
|
|
1402
1537
|
}
|
|
1403
1538
|
return outcomeFromVerifyResult(classification, result);
|
|
1404
1539
|
}
|
|
@@ -1409,9 +1544,19 @@ async function runTier(options, req, classification, policy, apiKey, sampling) {
|
|
|
1409
1544
|
verifyMs
|
|
1410
1545
|
);
|
|
1411
1546
|
if (!result) {
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1547
|
+
return unavailableOutcome(
|
|
1548
|
+
classification,
|
|
1549
|
+
policy,
|
|
1550
|
+
"verify-access unreachable within budget"
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
if (isApiErrorResult(result)) {
|
|
1554
|
+
return unavailableOutcome(
|
|
1555
|
+
classification,
|
|
1556
|
+
policy,
|
|
1557
|
+
result.denialReasons?.[0] ?? "verify-access api error",
|
|
1558
|
+
result.correlationId
|
|
1559
|
+
);
|
|
1415
1560
|
}
|
|
1416
1561
|
return outcomeFromVerifyResult(classification, result);
|
|
1417
1562
|
}
|
|
@@ -1453,7 +1598,7 @@ async function evaluateEdgeRequest(options, req, config, apiKey) {
|
|
|
1453
1598
|
if (policy.skip) return { policy, application: passUntouched() };
|
|
1454
1599
|
const host = headerValue(req, "x-forwarded-host") ?? headerValue(req, "host") ?? new URL(options.counterpartyUrl).host;
|
|
1455
1600
|
const classification = classify(req, host, policy.depth);
|
|
1456
|
-
if (classification.tier === "human") {
|
|
1601
|
+
if (classification.tier === "human" || classification.tier === "self") {
|
|
1457
1602
|
return { policy, classification, application: passUntouched() };
|
|
1458
1603
|
}
|
|
1459
1604
|
const outcome = await runTier(options, req, classification, policy, apiKey, config.sampling);
|