@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
@@ -35,6 +35,27 @@ __export(nextjs_exports, {
35
35
  });
36
36
  module.exports = __toCommonJS(nextjs_exports);
37
37
 
38
+ // src/version.ts
39
+ var SDK_VERSION = "4.5.0";
40
+
41
+ // src/http.ts
42
+ var SDK_USER_AGENT = `astrasync-sdk/${SDK_VERSION}`;
43
+ function withUserAgent(initHeaders) {
44
+ if (!initHeaders) return { "user-agent": SDK_USER_AGENT };
45
+ if (initHeaders instanceof Headers) {
46
+ const copy = new Headers(initHeaders);
47
+ if (!copy.has("user-agent")) copy.set("user-agent", SDK_USER_AGENT);
48
+ return copy;
49
+ }
50
+ if (Array.isArray(initHeaders)) {
51
+ const has2 = initHeaders.some(([name]) => name.toLowerCase() === "user-agent");
52
+ return has2 ? initHeaders : [...initHeaders, ["user-agent", SDK_USER_AGENT]];
53
+ }
54
+ const has = Object.keys(initHeaders).some((name) => name.toLowerCase() === "user-agent");
55
+ return has ? initHeaders : { "user-agent": SDK_USER_AGENT, ...initHeaders };
56
+ }
57
+ var sdkFetch = (input, init) => fetch(input, { ...init, headers: withUserAgent(init?.headers) });
58
+
38
59
  // src/adapters/stepup-poll.ts
39
60
  var DEFAULT_POLL_INTERVAL_MS = 3e3;
40
61
  var MIN_POLL_INTERVAL_MS = 250;
@@ -49,7 +70,7 @@ function resolvePollUrl(config, pollUrl) {
49
70
  async function pollStepUpStatus(config, approval) {
50
71
  if (!approval.pollUrl) return null;
51
72
  try {
52
- const res = await fetch(resolvePollUrl(config, approval.pollUrl), {
73
+ const res = await sdkFetch(resolvePollUrl(config, approval.pollUrl), {
53
74
  method: "GET",
54
75
  signal: AbortSignal.timeout(POLL_REQUEST_TIMEOUT_MS)
55
76
  });
@@ -101,9 +122,6 @@ function getTrustLevel(score) {
101
122
  return "BRONZE";
102
123
  }
103
124
 
104
- // src/version.ts
105
- var SDK_VERSION = "4.1.0";
106
-
107
125
  // src/well-known.ts
108
126
  var CACHE_TTL_MS = 60 * 60 * 1e3;
109
127
  var cache = /* @__PURE__ */ new Map();
@@ -132,7 +150,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
132
150
  initCheckPerformed = true;
133
151
  try {
134
152
  const probeUrl = `${apiBaseUrl}/agents/verify-access`;
135
- const response = await fetch(probeUrl, { method: "HEAD" });
153
+ const response = await sdkFetch(probeUrl, { method: "HEAD" });
136
154
  const contentType = response.headers.get("content-type") ?? "";
137
155
  if (contentType.startsWith("text/html")) {
138
156
  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).`;
@@ -146,7 +164,7 @@ async function performInitCheck(apiBaseUrl, debug, strictInit, apiKey) {
146
164
  );
147
165
  }
148
166
  if (apiKey) {
149
- const selfCheck = await fetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
167
+ const selfCheck = await sdkFetch(`${apiBaseUrl}/agents/verify-access/self-check`, {
150
168
  headers: { Authorization: `Bearer ${apiKey}` }
151
169
  });
152
170
  if (selfCheck.status === 401) {
@@ -393,7 +411,7 @@ async function callVerifyAccessAPI(config, request) {
393
411
  headers["X-API-Key"] = config.apiKey;
394
412
  }
395
413
  try {
396
- const response = await fetch(`${config.apiBaseUrl}/agents/verify-access`, {
414
+ const response = await sdkFetch(`${config.apiBaseUrl}/agents/verify-access`, {
397
415
  method: "POST",
398
416
  headers,
399
417
  body: JSON.stringify(body),
@@ -575,12 +593,15 @@ async function verify(config, request, options) {
575
593
  const verificationContext = apiResponse.verificationContext;
576
594
  const accessLevel = apiResponse.access?.accessLevel ?? "standard";
577
595
  const result = {
578
- // Round-18 G4: backend allowed access. Identity is verified (we resolved
579
- // the caller to an agent) and policy passed all gates. Read idVerified
580
- // from verificationContext for symmetry with the deny branch; default true
581
- // on success path since `access.allowed === true` implies identity was
582
- // resolvable (anonymous-allow paths flow through createGuidanceResponse).
583
- identityVerified: apiResponse.verificationContext?.idVerified !== false,
596
+ // 4.5.0 (probe F1): read idVerified from verificationContext with the
597
+ // SAME absent⇒false default as the deny branch. The previous `!== false`
598
+ // default manufactured `identityVerified: true` for anonymous
599
+ // allow_full/allow_partial/audit responses (which allow access but omit
600
+ // verificationContext entirely) — the "anonymous caller labelled
601
+ // verified" defect. Every authenticated success response has carried
602
+ // `verificationContext.idVerified: true` since v2.2.4, so a truth claim
603
+ // this load-bearing must never be defaulted on.
604
+ identityVerified: apiResponse.verificationContext?.idVerified === true,
584
605
  policyAllowed: true,
585
606
  accessLevel,
586
607
  agent,
@@ -638,7 +659,7 @@ async function fetchRoutes(config, counterpartyId) {
638
659
  headers["X-API-Key"] = config.apiKey;
639
660
  }
640
661
  try {
641
- const response = await fetch(
662
+ const response = await sdkFetch(
642
663
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/routes`,
643
664
  { method: "GET", headers }
644
665
  );
@@ -657,7 +678,7 @@ async function reportUnregisteredAttempt(config, data) {
657
678
  ...counterpartyUrl && { counterpartyUrl },
658
679
  ...config.counterpartyId && { counterpartyId: config.counterpartyId }
659
680
  };
660
- await fetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
681
+ await sdkFetch(`${apiBaseUrl}/verification-activity/unregistered-attempt`, {
661
682
  method: "POST",
662
683
  headers: { "Content-Type": "application/json" },
663
684
  body: JSON.stringify(body)
@@ -666,7 +687,7 @@ async function reportUnregisteredAttempt(config, data) {
666
687
  }
667
688
  async function reportCounterpartyPreCheckFailure(config, data) {
668
689
  const apiBaseUrl = config.apiBaseUrl || DEFAULT_CONFIG.apiBaseUrl;
669
- await fetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
690
+ await sdkFetch(`${apiBaseUrl}/verification-activity/counterparty-pre-check-failure`, {
670
691
  method: "POST",
671
692
  headers: { "Content-Type": "application/json" },
672
693
  body: JSON.stringify(data)
@@ -805,7 +826,13 @@ var PLATFORM_HEADER_NAMES = /* @__PURE__ */ new Set([
805
826
  "x-agent-card-url",
806
827
  "x-agent-provider"
807
828
  ]);
808
- var PLATFORM_HEADER_PREFIXES = ["x-goog-", "x-stainless-", "x-anthropic-", "x-openai-"];
829
+ var PLATFORM_HEADER_PREFIXES = [
830
+ "x-goog-",
831
+ "x-stainless-",
832
+ "x-anthropic-",
833
+ "x-openai-",
834
+ "sec-ch-"
835
+ ];
809
836
  var KEY_FORMAT_PREFIXES = [
810
837
  "sk-ant-api03",
811
838
  "sk-ant-api",
@@ -877,6 +904,7 @@ function sanitizeHeaders(raw) {
877
904
  const headers = {};
878
905
  const platformHeaders = {};
879
906
  let apiKeyFormat;
907
+ let cookieCount;
880
908
  let totalBytes = 0;
881
909
  let count = 0;
882
910
  if (!raw) return { headers };
@@ -891,6 +919,9 @@ function sanitizeHeaders(raw) {
891
919
  if (fmt && !apiKeyFormat) apiKeyFormat = fmt;
892
920
  continue;
893
921
  }
922
+ if (name === "cookie") {
923
+ cookieCount = joined.split(";").filter((s) => s.trim().length > 0).length;
924
+ }
894
925
  if (isSecretHeader(name)) continue;
895
926
  let value = joined;
896
927
  if (byteLength(value) > MAX_HEADER_VALUE_BYTES) {
@@ -906,6 +937,7 @@ function sanitizeHeaders(raw) {
906
937
  const result = { headers };
907
938
  if (apiKeyFormat) result.apiKeyFormat = apiKeyFormat;
908
939
  if (Object.keys(platformHeaders).length > 0) result.platformHeaders = platformHeaders;
940
+ if (cookieCount !== void 0) result.cookieCount = cookieCount;
909
941
  return result;
910
942
  }
911
943
  var CONNECTION_HEADER_SOURCES = [
@@ -917,6 +949,9 @@ var CONNECTION_HEADER_SOURCES = [
917
949
  ["region", ["x-vercel-ip-country-region", "cloudfront-viewer-country-region"]],
918
950
  ["countryRegionName", ["cloudfront-viewer-country-region-name"]],
919
951
  ["city", ["x-vercel-ip-city", "cloudfront-viewer-city"]],
952
+ ["latitude", ["x-vercel-ip-latitude", "cloudfront-viewer-latitude"]],
953
+ ["longitude", ["x-vercel-ip-longitude", "cloudfront-viewer-longitude"]],
954
+ ["postalCode", ["x-vercel-ip-postal-code", "cloudfront-viewer-postal-code"]],
920
955
  ["asn", ["cloudfront-viewer-asn"]],
921
956
  ["tlsVersion", ["cloudfront-viewer-tls"]],
922
957
  ["httpVersion", ["cloudfront-viewer-http-version"]],
@@ -951,19 +986,25 @@ function deriveConnectionFromHeaders(headers) {
951
986
  }
952
987
  }
953
988
  }
954
- if (!connection.ip) {
989
+ {
955
990
  const addr = lower["cloudfront-viewer-address"]?.trim();
956
- if (addr) connection.ip = addr.replace(/:\d+$/, "");
991
+ const portMatch = addr?.match(/:(\d+)$/);
992
+ if (portMatch) connection.sourcePort = portMatch[1];
993
+ if (!connection.ip && addr) connection.ip = addr.replace(/:\d+$/, "");
957
994
  }
958
995
  if (!connection.ip) {
959
996
  const xff = lower["x-forwarded-for"];
960
997
  const client = xff?.split(",")[0]?.trim();
961
998
  if (client) connection.ip = client;
962
999
  }
1000
+ if (!connection.tlsCipher) {
1001
+ const cipher = connection.tlsVersion?.split(":")[1];
1002
+ if (cipher) connection.tlsCipher = cipher;
1003
+ }
963
1004
  return Object.keys(connection).length > 0 ? connection : void 0;
964
1005
  }
965
1006
  function buildSdkObservedMetadata(rawHeaders) {
966
- const { headers, apiKeyFormat, platformHeaders } = sanitizeHeaders(rawHeaders);
1007
+ const { headers, apiKeyFormat, platformHeaders, cookieCount } = sanitizeHeaders(rawHeaders);
967
1008
  const flat = {};
968
1009
  if (rawHeaders) {
969
1010
  for (const name of Object.keys(rawHeaders)) {
@@ -978,6 +1019,7 @@ function buildSdkObservedMetadata(rawHeaders) {
978
1019
  ...apiKeyFormat && { apiKeyFormat },
979
1020
  ...platformHeaders && { platformHeaders },
980
1021
  ...connection && { connection },
1022
+ ...cookieCount !== void 0 && { cookieCount },
981
1023
  schemaVersion: CAPTURE_SCHEMA_VERSION
982
1024
  };
983
1025
  }
@@ -1022,7 +1064,7 @@ async function fetchEdgeConfig(config, counterpartyId, opts) {
1022
1064
  }
1023
1065
  if (opts?.etag) headers["If-None-Match"] = opts.etag;
1024
1066
  try {
1025
- const response = await fetch(
1067
+ const response = await sdkFetch(
1026
1068
  `${config.apiBaseUrl}/endpoints/${encodeURIComponent(counterpartyId)}/edge-config`,
1027
1069
  { method: "GET", headers, signal: AbortSignal.timeout(opts?.timeoutMs ?? 5e3) }
1028
1070
  );
@@ -1134,12 +1176,43 @@ var PLATFORM_AGENT_SIGNATURES = [
1134
1176
  uaPatterns: [/perplexity/i],
1135
1177
  agentCardPatterns: [/perplexity\.ai/i],
1136
1178
  apiKeyFormatPatterns: [/^pplx-/i]
1179
+ },
1180
+ // Agentic browsers — versioned-token patterns ONLY (a stock Chrome UA must
1181
+ // never match). Distinct vendor keys (not 'chatgpt'/'perplexity'): the
1182
+ // browser product is a different surface than the assistant service, and
1183
+ // the DB registry mirrors one row per vendor. These sit after the primary
1184
+ // vendor entries so a UA carrying an explicit vendor token keeps its
1185
+ // primary attribution; a browser-shaped UA with the agentic token is
1186
+ // caught here BEFORE classify's human tier, which is what makes these
1187
+ // visits observable at all. Token drift is real (some builds mimic Chrome
1188
+ // outright) — re-verify against captured events when a new browser ships,
1189
+ // and check `sec-ch-ua` platformHeaders for the real brand.
1190
+ {
1191
+ vendor: "chatgpt-atlas",
1192
+ displayName: "ChatGPT Atlas browser",
1193
+ uaPatterns: [/\batlas\/\d/i],
1194
+ headerPatterns: [/^sec-ch-ua: .*atlas/i]
1195
+ },
1196
+ {
1197
+ vendor: "perplexity-comet",
1198
+ displayName: "Perplexity Comet browser",
1199
+ uaPatterns: [/\bcomet\/\d/i],
1200
+ headerPatterns: [/^sec-ch-ua: .*comet/i]
1201
+ },
1202
+ // Our own SDK self-identifies (sdkFetch stamps this UA) — first-party
1203
+ // traffic must never pollute the unknown/anonymous pool it exists to audit.
1204
+ {
1205
+ vendor: "astrasync-sdk",
1206
+ displayName: "AstraSync SDK",
1207
+ uaPatterns: [/^astrasync-sdk\//i]
1137
1208
  }
1138
1209
  ];
1139
1210
  function matchPlatformSignature(signatures, input) {
1140
1211
  const ua = input.userAgent ?? "";
1141
1212
  const cardUrl = input.agentCardUrl ?? "";
1142
1213
  const apiKeyFormat = input.apiKeyFormat ?? "";
1214
+ const ja4 = input.ja4 ?? "";
1215
+ const asn = input.asn ?? "";
1143
1216
  const headerLines = [];
1144
1217
  if (input.platformHeaders) {
1145
1218
  for (const [name, value] of Object.entries(input.platformHeaders)) {
@@ -1151,7 +1224,9 @@ function matchPlatformSignature(signatures, input) {
1151
1224
  const cardMatch = sig.agentCardPatterns?.some((p) => p.test(cardUrl));
1152
1225
  const keyMatch = apiKeyFormat.length > 0 && sig.apiKeyFormatPatterns?.some((p) => p.test(apiKeyFormat));
1153
1226
  const headerMatch = headerLines.length > 0 && sig.headerPatterns?.some((p) => headerLines.some((line) => p.test(line)));
1154
- if (uaMatch || cardMatch || keyMatch || headerMatch) {
1227
+ const ja4Match = ja4.length > 0 && sig.ja4Patterns?.some((p) => p.test(ja4));
1228
+ const asnMatch = asn.length > 0 && sig.asnList?.includes(asn);
1229
+ if (uaMatch || cardMatch || keyMatch || headerMatch || ja4Match || asnMatch) {
1155
1230
  const uaBase = ua.split(/[\s/]/)[0]?.toLowerCase() ?? "";
1156
1231
  const cardHost = (() => {
1157
1232
  try {
@@ -1160,7 +1235,7 @@ function matchPlatformSignature(signatures, input) {
1160
1235
  return "";
1161
1236
  }
1162
1237
  })();
1163
- const keyBasis = uaBase || cardHost || apiKeyFormat || "unknown";
1238
+ const keyBasis = uaBase || cardHost || apiKeyFormat || ja4 || asn && `asn${asn}` || "unknown";
1164
1239
  return {
1165
1240
  vendor: sig.vendor,
1166
1241
  fingerprintKey: `${sig.vendor}:${keyBasis}`,
@@ -1176,9 +1251,10 @@ function detectPlatformFingerprint(input) {
1176
1251
 
1177
1252
  // src/edge-core/classify.ts
1178
1253
  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;
1254
+ var SELF_UA_PATTERN = /^astrasync-sdk\//i;
1179
1255
  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;
1180
1256
  var BROWSER_UA_PATTERN = /mozilla\/\d/i;
1181
- var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN };
1257
+ var _patterns = { STATIC_EXTENSIONS, BOT_UA_PATTERN, BROWSER_UA_PATTERN, SELF_UA_PATTERN };
1182
1258
 
1183
1259
  // src/edge-core/events.ts
1184
1260
  function sampled(rate, random = Math.random) {
@@ -1786,7 +1862,7 @@ function createMiddleware(options) {
1786
1862
  };
1787
1863
  let result = await verify(config, verificationRequest);
1788
1864
  let stepUpHoldOutcome;
1789
- if (config.stepUpMaxWaitMs !== void 0 && result.identityVerified && requiresHumanApproval(result) && result.stepUpApproval?.pollUrl) {
1865
+ if (config.stepUpMaxWaitMs !== void 0 && result.policyAllowed && requiresHumanApproval(result) && result.stepUpApproval?.pollUrl) {
1790
1866
  stepUpHoldOutcome = await awaitStepUpApproval(config, result.stepUpApproval);
1791
1867
  if (stepUpHoldOutcome === "approved") {
1792
1868
  result = await verify(config, verificationRequest, { bypassCache: true });
@@ -1794,9 +1870,9 @@ function createMiddleware(options) {
1794
1870
  result.stepUpOutcome = stepUpHoldOutcome;
1795
1871
  }
1796
1872
  }
1797
- const approvalRequired = result.identityVerified && result.policyAllowed && requiresHumanApproval(result);
1873
+ const approvalRequired = result.policyAllowed && requiresHumanApproval(result);
1798
1874
  if (approvalRequired) annotateApprovalRequired(result);
1799
- if (!result.identityVerified || !result.policyAllowed || approvalRequired) {
1875
+ if (!result.policyAllowed || approvalRequired) {
1800
1876
  if (result.misconfigured && pathname.startsWith("/api/")) {
1801
1877
  return NextResponse.json(
1802
1878
  {