@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.
Files changed (87) hide show
  1. package/dist/adapters/express.js +104 -28
  2. package/dist/adapters/express.js.map +1 -1
  3. package/dist/adapters/express.mjs +104 -28
  4. package/dist/adapters/express.mjs.map +1 -1
  5. package/dist/adapters/mcp.js +63 -21
  6. package/dist/adapters/mcp.js.map +1 -1
  7. package/dist/adapters/mcp.mjs +63 -21
  8. package/dist/adapters/mcp.mjs.map +1 -1
  9. package/dist/adapters/nextjs.js +103 -27
  10. package/dist/adapters/nextjs.js.map +1 -1
  11. package/dist/adapters/nextjs.mjs +103 -27
  12. package/dist/adapters/nextjs.mjs.map +1 -1
  13. package/dist/adapters/sdk.js +33 -12
  14. package/dist/adapters/sdk.js.map +1 -1
  15. package/dist/adapters/sdk.mjs +33 -12
  16. package/dist/adapters/sdk.mjs.map +1 -1
  17. package/dist/agent/index.js +24 -3
  18. package/dist/agent/index.js.map +1 -1
  19. package/dist/agent/index.mjs +24 -3
  20. package/dist/agent/index.mjs.map +1 -1
  21. package/dist/bin/astrasync-claude-hook.js +31 -10
  22. package/dist/bin/astrasync-codex-hook.js +31 -10
  23. package/dist/bin/astrasync-guard.js +35 -14
  24. package/dist/bin/astrasync.js +75 -29
  25. package/dist/browser/background.js +31 -10
  26. package/dist/browser/background.js.map +1 -1
  27. package/dist/browser/background.mjs +31 -10
  28. package/dist/browser/background.mjs.map +1 -1
  29. package/dist/cli/index.js +23 -2
  30. package/dist/cli/index.js.map +1 -1
  31. package/dist/cli/index.mjs +23 -2
  32. package/dist/cli/index.mjs.map +1 -1
  33. package/dist/codex/index.js +31 -10
  34. package/dist/codex/index.js.map +1 -1
  35. package/dist/codex/index.mjs +31 -10
  36. package/dist/codex/index.mjs.map +1 -1
  37. package/dist/cursor/extension.js +31 -10
  38. package/dist/cursor/extension.js.map +1 -1
  39. package/dist/cursor/extension.mjs +31 -10
  40. package/dist/cursor/extension.mjs.map +1 -1
  41. package/dist/edge-config.d.mts +11 -4
  42. package/dist/edge-config.d.ts +11 -4
  43. package/dist/edge-config.js +24 -1
  44. package/dist/edge-config.js.map +1 -1
  45. package/dist/edge-config.mjs +22 -1
  46. package/dist/edge-config.mjs.map +1 -1
  47. package/dist/edge-core/index.d.mts +21 -7
  48. package/dist/edge-core/index.d.ts +21 -7
  49. package/dist/edge-core/index.js +170 -25
  50. package/dist/edge-core/index.js.map +1 -1
  51. package/dist/edge-core/index.mjs +170 -25
  52. package/dist/edge-core/index.mjs.map +1 -1
  53. package/dist/gateway/gateway.js +31 -10
  54. package/dist/gateway/gateway.js.map +1 -1
  55. package/dist/gateway/gateway.mjs +31 -10
  56. package/dist/gateway/gateway.mjs.map +1 -1
  57. package/dist/index.d.mts +51 -2
  58. package/dist/index.d.ts +51 -2
  59. package/dist/index.js +169 -40
  60. package/dist/index.js.map +1 -1
  61. package/dist/index.mjs +165 -40
  62. package/dist/index.mjs.map +1 -1
  63. package/dist/metadata-capture.d.mts +17 -0
  64. package/dist/metadata-capture.d.ts +17 -0
  65. package/dist/metadata-capture.js +25 -4
  66. package/dist/metadata-capture.js.map +1 -1
  67. package/dist/metadata-capture.mjs +25 -4
  68. package/dist/metadata-capture.mjs.map +1 -1
  69. package/dist/platform-signatures.d.mts +18 -1
  70. package/dist/platform-signatures.d.ts +18 -1
  71. package/dist/platform-signatures.js +35 -2
  72. package/dist/platform-signatures.js.map +1 -1
  73. package/dist/platform-signatures.mjs +35 -2
  74. package/dist/platform-signatures.mjs.map +1 -1
  75. package/dist/registration/index.js +25 -4
  76. package/dist/registration/index.js.map +1 -1
  77. package/dist/registration/index.mjs +25 -4
  78. package/dist/registration/index.mjs.map +1 -1
  79. package/dist/transport/index.js +22 -1
  80. package/dist/transport/index.js.map +1 -1
  81. package/dist/transport/index.mjs +22 -1
  82. package/dist/transport/index.mjs.map +1 -1
  83. package/dist/verify.js +38 -17
  84. package/dist/verify.js.map +1 -1
  85. package/dist/verify.mjs +38 -17
  86. package/dist/verify.mjs.map +1 -1
  87. package/package.json +2 -2
@@ -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 = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
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
- if (uaMatch || cardMatch || keyMatch || headerMatch) {
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 fetch(probeUrl, { method: "HEAD" });
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 fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
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 fetch(`${config.apiBaseUrl}/agents/verify-access`, {
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
- // Round-18 G4: backend allowed access. Identity is verified (we resolved
775
- // the caller to an agent) and policy passed all gates. Read idVerified
776
- // from verificationContext for symmetry with the deny branch; default true
777
- // on success path since `access.allowed === true` implies identity was
778
- // resolvable (anonymous-allow paths flow through createGuidanceResponse).
779
- identityVerified: apiResponse.verificationContext?.idVerified !== false,
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 fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
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
- const outcome = baseOutcome(classification);
1400
- outcome.reasons = ["verify-access unreachable within budget \u2014 fail-open"];
1401
- return outcome;
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
- const outcome2 = baseOutcome(classification);
1413
- outcome2.reasons = ["verify-access unreachable within budget \u2014 fail-open"];
1414
- return outcome2;
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);