@tonyclaw/agent-inspector 3.0.26 → 3.0.27

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 (72) hide show
  1. package/.output/cli.js +80 -11
  2. package/.output/nitro.json +1 -1
  3. package/.output/public/assets/{CompareDrawer-SZRUDQc9.js → CompareDrawer-BVCAAbbu.js} +1 -1
  4. package/.output/public/assets/{ProxyViewerContainer-BJvwOrSi.js → ProxyViewerContainer-CYOXB81n.js} +200 -200
  5. package/.output/public/assets/{ReplayDialog-DgfmqiM3.js → ReplayDialog-DMX23O5G.js} +1 -1
  6. package/.output/public/assets/{RequestAnatomy-CS6c7Qfn.js → RequestAnatomy-S17pYDRc.js} +1 -1
  7. package/.output/public/assets/{ResponseView-C0Ri_5gm.js → ResponseView-Cf1XrawD.js} +2 -2
  8. package/.output/public/assets/{StreamingChunkSequence-7dYcHWyZ.js → StreamingChunkSequence-BdOrVlCi.js} +1 -1
  9. package/.output/public/assets/{_sessionId-D-V84cgw.js → _sessionId-BnKGmJrR.js} +1 -1
  10. package/.output/public/assets/{index-DgFWCxOs.js → index-9fMLCU4d.js} +1 -1
  11. package/.output/public/assets/{index-CSIL7-MK.js → index-B1atk19i.js} +2 -2
  12. package/.output/public/assets/{index-DrVJsks2.js → index-gY3I-7-D.js} +1 -1
  13. package/.output/public/assets/{json-viewer-C9Ji3gYN.js → json-viewer-CpyYpPjx.js} +1 -1
  14. package/.output/server/_libs/lucide-react.mjs +6 -6
  15. package/.output/server/{_sessionId-BQ6YQOO1.mjs → _sessionId-eA_h2_s6.mjs} +4 -2
  16. package/.output/server/_ssr/{CompareDrawer-CqNzIeMR.mjs → CompareDrawer-K2k9G7r1.mjs} +5 -3
  17. package/.output/server/_ssr/{ProxyViewerContainer-CMC7Vsbw.mjs → ProxyViewerContainer-CbyBpCAj.mjs} +27 -60
  18. package/.output/server/_ssr/{ReplayDialog-D9p_LaTr.mjs → ReplayDialog-DacI2Iv0.mjs} +5 -3
  19. package/.output/server/_ssr/{RequestAnatomy-B4ge3qNZ.mjs → RequestAnatomy-gh-pAdkO.mjs} +4 -2
  20. package/.output/server/_ssr/{ResponseView-BCqMKApI.mjs → ResponseView-BRJKN50-.mjs} +5 -3
  21. package/.output/server/_ssr/{StreamingChunkSequence-BA4zTLE4.mjs → StreamingChunkSequence-BQLVEuWY.mjs} +4 -2
  22. package/.output/server/_ssr/{index-CzxhDjlq.mjs → index-DuSWrjbH.mjs} +4 -2
  23. package/.output/server/_ssr/index.mjs +2 -2
  24. package/.output/server/_ssr/{json-viewer-B3qV6iEJ.mjs → json-viewer-DNs1ZjKI.mjs} +4 -2
  25. package/.output/server/_ssr/{router-CZe_R31M.mjs → router-Dgx6-ot3.mjs} +712 -217
  26. package/.output/server/{_tanstack-start-manifest_v-B50VTLrH.mjs → _tanstack-start-manifest_v-CoGjnkQD.mjs} +1 -1
  27. package/.output/server/index.mjs +64 -64
  28. package/README.md +8 -0
  29. package/package.json +4 -5
  30. package/src/cli.ts +43 -9
  31. package/src/components/providers/ProviderCard.tsx +3 -36
  32. package/src/components/providers/ProviderForm.tsx +13 -12
  33. package/src/components/providers/ProvidersPanel.tsx +2 -2
  34. package/src/components/providers/SettingsDialog.tsx +3 -3
  35. package/src/components/proxy-viewer/ProviderLogoStack.tsx +0 -3
  36. package/src/contracts/anthropic.ts +0 -5
  37. package/src/contracts/index.ts +0 -4
  38. package/src/contracts/log.ts +0 -1
  39. package/src/contracts/openai.ts +0 -3
  40. package/src/knowledge/openclawClient.ts +22 -9
  41. package/src/knowledge/openclawGatewayClient.ts +20 -13
  42. package/src/lib/alertContract.ts +0 -3
  43. package/src/lib/ecosystemContract.ts +0 -3
  44. package/src/lib/export-logs.ts +1 -4
  45. package/src/lib/safeFetch.ts +100 -0
  46. package/src/lib/ssrfGuard.ts +133 -0
  47. package/src/lib/stopReason.ts +1 -4
  48. package/src/lib/unknown.ts +27 -0
  49. package/src/lib/useProviders.ts +1 -1
  50. package/src/mcp/mode.ts +3 -9
  51. package/src/mcp/server.ts +5 -14
  52. package/src/mcp/toolHandlers.ts +45 -54
  53. package/src/proxy/constants.ts +17 -0
  54. package/src/proxy/formats/anthropic/handler.ts +5 -1
  55. package/src/proxy/formats/anthropic/schemas.ts +1 -7
  56. package/src/proxy/formats/index.ts +1 -1
  57. package/src/proxy/formats/openai/schemas.ts +0 -3
  58. package/src/proxy/formats/registry.ts +0 -3
  59. package/src/proxy/handler.ts +91 -12
  60. package/src/proxy/identityProxy.ts +74 -1
  61. package/src/proxy/logImporter.ts +82 -12
  62. package/src/proxy/logIndex.ts +1 -4
  63. package/src/proxy/providerSecretStore.ts +181 -0
  64. package/src/proxy/providers.ts +84 -11
  65. package/src/proxy/schemas.ts +1 -8
  66. package/src/proxy/sqliteLogIndex.ts +0 -16
  67. package/src/proxy/store.ts +1 -5
  68. package/src/proxy/toolSchemaWarnings.ts +1 -9
  69. package/src/routes/api/logs.$id.replay.ts +17 -5
  70. package/src/routes/api/providers.$providerId.model-metadata.ts +11 -1
  71. package/src/routes/api/providers.$providerId.ts +18 -5
  72. package/src/routes/api/providers.ts +18 -7
@@ -1,17 +1,19 @@
1
1
  import { c as createRouter, a as createRootRoute, b as createFileRoute, l as lazyRouteComponent, O as Outlet, H as HeadContent, S as Scripts } from "../_libs/tanstack__react-router.mjs";
2
2
  import { r as reactExports, j as jsxRuntimeExports } from "../_libs/react.mjs";
3
3
  import { S as SWRConfig } from "../_libs/swr.mjs";
4
- import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, createReadStream, readdirSync, statSync, rmSync, cpSync, createWriteStream } from "node:fs";
4
+ import { existsSync, readFileSync, mkdirSync, writeFileSync, renameSync, copyFileSync, unlinkSync, createReadStream, readdirSync, statSync, rmSync, cpSync, chmodSync, createWriteStream } from "node:fs";
5
5
  import fs, { mkdir, appendFile, writeFile, readdir, open, readFile, stat, unlink, rm, rename } from "node:fs/promises";
6
6
  import { createInterface } from "node:readline";
7
7
  import { Buffer } from "node:buffer";
8
8
  import path, { basename, join, dirname, isAbsolute, resolve, relative } from "node:path";
9
- import { randomUUID, createHash } from "node:crypto";
9
+ import { randomUUID, timingSafeEqual, createHash, createDecipheriv, randomBytes, createCipheriv } from "node:crypto";
10
10
  import { spawn, execFile } from "node:child_process";
11
11
  import { fileURLToPath } from "node:url";
12
12
  import { C as Conf } from "../_libs/conf.mjs";
13
13
  import { randomUUID as randomUUID$1 } from "crypto";
14
14
  import { promisify } from "node:util";
15
+ import { lookup } from "node:dns/promises";
16
+ import { isIP } from "node:net";
15
17
  import { Worker } from "node:worker_threads";
16
18
  import { M as McpServer, W as WebStandardStreamableHTTPServerTransport, R as ResourceTemplate } from "../_libs/modelcontextprotocol__server.mjs";
17
19
  import { J as JSZip } from "../_libs/jszip.mjs";
@@ -370,7 +372,7 @@ function RootDocument({ children }) {
370
372
  ] })
371
373
  ] });
372
374
  }
373
- const $$splitComponentImporter$1 = () => import("./index-CzxhDjlq.mjs");
375
+ const $$splitComponentImporter$1 = () => import("./index-DuSWrjbH.mjs");
374
376
  const Route$L = createFileRoute("/")({
375
377
  ssr: false,
376
378
  component: lazyRouteComponent($$splitComponentImporter$1, "component")
@@ -414,7 +416,7 @@ function decodeSessionIdFromPath(encoded) {
414
416
  function getSessionPath(sessionId) {
415
417
  return `/session/${encodeSessionIdForPath(sessionId)}`;
416
418
  }
417
- const $$splitComponentImporter = () => import("../_sessionId-BQ6YQOO1.mjs");
419
+ const $$splitComponentImporter = () => import("../_sessionId-eA_h2_s6.mjs");
418
420
  const Route$K = createFileRoute("/session/$sessionId")({
419
421
  ssr: false,
420
422
  component: lazyRouteComponent($$splitComponentImporter, "component"),
@@ -1192,6 +1194,18 @@ function readChunks(path2) {
1192
1194
  return null;
1193
1195
  }
1194
1196
  }
1197
+ function isRecord(value) {
1198
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1199
+ }
1200
+ function readProperty$5(value, name2) {
1201
+ if (!isRecord(value)) return void 0;
1202
+ const descriptor = Object.getOwnPropertyDescriptor(value, name2);
1203
+ return descriptor === void 0 ? void 0 : descriptor.value;
1204
+ }
1205
+ function textByteLength(value) {
1206
+ if (value === null) return null;
1207
+ return new TextEncoder().encode(value).byteLength;
1208
+ }
1195
1209
  const SQLITE_INDEX_FILE = "inspector.sqlite";
1196
1210
  const SQLITE_PACKAGE_NAME$1 = "better-sqlite3";
1197
1211
  const BUN_SQLITE_MODULE_NAME$1 = "bun:sqlite";
@@ -1328,7 +1342,7 @@ function objectFromUnknown$1(value) {
1328
1342
  if (typeof value === "object" || typeof value === "function") return value;
1329
1343
  return null;
1330
1344
  }
1331
- function readProperty$5(value, name2) {
1345
+ function readProperty$4(value, name2) {
1332
1346
  let current = objectFromUnknown$1(value);
1333
1347
  while (current !== null) {
1334
1348
  const desc = Object.getOwnPropertyDescriptor(current, name2);
@@ -1342,7 +1356,7 @@ function readProperty$5(value, name2) {
1342
1356
  return void 0;
1343
1357
  }
1344
1358
  function readFunction$1(value, name2) {
1345
- const property = readProperty$5(value, name2);
1359
+ const property = readProperty$4(value, name2);
1346
1360
  return typeof property === "function" ? property : null;
1347
1361
  }
1348
1362
  function callMethod$1(target, methodName, args) {
@@ -1384,7 +1398,7 @@ function execSql$1(db, sql) {
1384
1398
  return result !== void 0;
1385
1399
  }
1386
1400
  function sqliteConstructorFromModule$1(moduleValue, exportName) {
1387
- const exportedValue = readProperty$5(moduleValue, exportName);
1401
+ const exportedValue = readProperty$4(moduleValue, exportName);
1388
1402
  if (typeof exportedValue === "function") {
1389
1403
  return (path2) => Reflect.construct(exportedValue, [path2]);
1390
1404
  }
@@ -1527,11 +1541,11 @@ function entryParams(entry) {
1527
1541
  ];
1528
1542
  }
1529
1543
  function readNumber$1(row, name2) {
1530
- const value = readProperty$5(row, name2);
1544
+ const value = readProperty$4(row, name2);
1531
1545
  return typeof value === "number" && Number.isFinite(value) ? value : null;
1532
1546
  }
1533
1547
  function readString$2(row, name2) {
1534
- const value = readProperty$5(row, name2);
1548
+ const value = readProperty$4(row, name2);
1535
1549
  return typeof value === "string" ? value : null;
1536
1550
  }
1537
1551
  function readBoolean(row, name2) {
@@ -1716,9 +1730,6 @@ function getIndexPath() {
1716
1730
  }
1717
1731
  let cachedIndex = null;
1718
1732
  let sqliteBackfillScheduledMaxId = 0;
1719
- function textByteLength$2(value) {
1720
- return value === null ? null : Buffer.byteLength(value, "utf8");
1721
- }
1722
1733
  function createLogIndexEntryMetadata(log) {
1723
1734
  return {
1724
1735
  sessionId: log.sessionId,
@@ -1751,8 +1762,8 @@ function createLogIndexEntryMetadata(log) {
1751
1762
  clientCwd: log.clientCwd ?? null,
1752
1763
  clientProjectFolder: log.clientProjectFolder ?? null,
1753
1764
  streamingChunksPath: log.streamingChunksPath ?? null,
1754
- rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength$2(log.rawRequestBody),
1755
- responseTextBytes: log.responseTextBytes ?? textByteLength$2(log.responseText),
1765
+ rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
1766
+ responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
1756
1767
  warnings: log.warnings,
1757
1768
  error: log.error ?? null
1758
1769
  }
@@ -1932,7 +1943,7 @@ function readStringArrayField(entry, field) {
1932
1943
  function readTextByteLength(entry, bytesField, textField) {
1933
1944
  const explicitBytes = readNumberOrNullField(entry, bytesField);
1934
1945
  if (explicitBytes !== null && explicitBytes >= 0) return explicitBytes;
1935
- return textByteLength$2(readStringOrNullField(entry, textField));
1946
+ return textByteLength(readStringOrNullField(entry, textField));
1936
1947
  }
1937
1948
  function readLogIndexSummary(entry, id) {
1938
1949
  return {
@@ -2284,9 +2295,18 @@ const HEADER_CONTENT_ENCODING = "content-encoding";
2284
2295
  const HEADER_CONTENT_LENGTH = "content-length";
2285
2296
  const HEADER_HOST = "host";
2286
2297
  const AUTH_HEADER_X_API_KEY = "x-api-key";
2298
+ const SENSITIVE_HEADERS = /* @__PURE__ */ new Set([
2299
+ HEADER_AUTHORIZATION,
2300
+ HEADER_X_API_KEY,
2301
+ "cookie",
2302
+ "set-cookie",
2303
+ "proxy-authorization"
2304
+ ]);
2287
2305
  const CONTENT_TYPE_EVENT_STREAM = "text/event-stream";
2288
2306
  const STATUS_FORBIDDEN = 403;
2289
2307
  const STATUS_BAD_GATEWAY = 502;
2308
+ const STATUS_PAYLOAD_TOO_LARGE = 413;
2309
+ const MAX_REQUEST_BODY_BYTES = 50 * 1024 * 1024;
2290
2310
  const PRESERVE_HEADERS = /* @__PURE__ */ new Set([HEADER_CONTENT_TYPE]);
2291
2311
  class FormatRegistryImpl {
2292
2312
  handlers = /* @__PURE__ */ new Map();
@@ -2383,7 +2403,7 @@ function objectFromUnknown(value) {
2383
2403
  if (typeof value === "object" || typeof value === "function") return value;
2384
2404
  return null;
2385
2405
  }
2386
- function readProperty$4(value, name2) {
2406
+ function readProperty$3(value, name2) {
2387
2407
  let current = objectFromUnknown(value);
2388
2408
  while (current !== null) {
2389
2409
  const desc = Object.getOwnPropertyDescriptor(current, name2);
@@ -2397,7 +2417,7 @@ function readProperty$4(value, name2) {
2397
2417
  return void 0;
2398
2418
  }
2399
2419
  function readFunction(value, name2) {
2400
- const property = readProperty$4(value, name2);
2420
+ const property = readProperty$3(value, name2);
2401
2421
  return typeof property === "function" ? property : null;
2402
2422
  }
2403
2423
  function callMethod(target, methodName, args) {
@@ -2435,7 +2455,7 @@ function closeDatabase(db) {
2435
2455
  callMethod(db, "close", []);
2436
2456
  }
2437
2457
  function sqliteConstructorFromModule(moduleValue, exportName) {
2438
- const exportedValue = readProperty$4(moduleValue, exportName);
2458
+ const exportedValue = readProperty$3(moduleValue, exportName);
2439
2459
  if (typeof exportedValue === "function") {
2440
2460
  return (path2) => Reflect.construct(exportedValue, [path2]);
2441
2461
  }
@@ -2526,11 +2546,11 @@ async function archiveSessionLog(log) {
2526
2546
  }
2527
2547
  }
2528
2548
  function readString$1(row, name2) {
2529
- const value = readProperty$4(row, name2);
2549
+ const value = readProperty$3(row, name2);
2530
2550
  return typeof value === "string" ? value : null;
2531
2551
  }
2532
2552
  function readNumber(row, name2) {
2533
- const value = readProperty$4(row, name2);
2553
+ const value = readProperty$3(row, name2);
2534
2554
  return typeof value === "number" && Number.isFinite(value) ? value : null;
2535
2555
  }
2536
2556
  function logFromJson(value) {
@@ -3110,10 +3130,6 @@ function removeFromCache(id) {
3110
3130
  memoryCache.delete(id);
3111
3131
  removeLogsFromCursorPageIndexes(/* @__PURE__ */ new Set([id]));
3112
3132
  }
3113
- function textByteLength$1(value) {
3114
- if (value === null) return null;
3115
- return Buffer.byteLength(value, "utf8");
3116
- }
3117
3133
  function compactLogForList(log) {
3118
3134
  return {
3119
3135
  ...log,
@@ -3122,8 +3138,8 @@ function compactLogForList(log) {
3122
3138
  rawHeaders: void 0,
3123
3139
  headers: void 0,
3124
3140
  streamingChunks: void 0,
3125
- rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength$1(log.rawRequestBody),
3126
- responseTextBytes: log.responseTextBytes ?? textByteLength$1(log.responseText),
3141
+ rawRequestBodyBytes: log.rawRequestBodyBytes ?? textByteLength(log.rawRequestBody),
3142
+ responseTextBytes: log.responseTextBytes ?? textByteLength(log.responseText),
3127
3143
  bodyContentMode: "compact"
3128
3144
  };
3129
3145
  }
@@ -4275,7 +4291,7 @@ const AnthropicFormatHandler = {
4275
4291
  if (result.success) {
4276
4292
  return {
4277
4293
  model: result.data.model,
4278
- sessionId: result.data.metadata?.user_id ?? headers?.get("x-session-affinity") ?? null
4294
+ sessionId: result.data.metadata?.user_id ?? headers?.get("x-agent-inspector-session-id") ?? headers?.get("x-session-affinity") ?? null
4279
4295
  };
4280
4296
  }
4281
4297
  return null;
@@ -4390,24 +4406,24 @@ const anthropicProvider = {
4390
4406
  };
4391
4407
  registry.register(anthropicProvider);
4392
4408
  const MAX_CHUNKS = 1e3;
4393
- function readProperty$3(value, key) {
4409
+ function readProperty$2(value, key) {
4394
4410
  if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
4395
4411
  return Object.getOwnPropertyDescriptor(value, key)?.value;
4396
4412
  }
4397
4413
  function readStringProperty$2(value, key) {
4398
- const property = readProperty$3(value, key);
4414
+ const property = readProperty$2(value, key);
4399
4415
  return typeof property === "string" ? property : null;
4400
4416
  }
4401
4417
  function readNumberProperty(value, key) {
4402
- const property = readProperty$3(value, key);
4418
+ const property = readProperty$2(value, key);
4403
4419
  return typeof property === "number" && Number.isFinite(property) ? property : null;
4404
4420
  }
4405
4421
  function readObjectProperty(value, key) {
4406
- const property = readProperty$3(value, key);
4422
+ const property = readProperty$2(value, key);
4407
4423
  return property !== null && typeof property === "object" && !Array.isArray(property) ? property : null;
4408
4424
  }
4409
4425
  function readArrayProperty$1(value, key) {
4410
- const property = readProperty$3(value, key);
4426
+ const property = readProperty$2(value, key);
4411
4427
  return Array.isArray(property) ? property : [];
4412
4428
  }
4413
4429
  function parseSseEvents(raw) {
@@ -4599,7 +4615,7 @@ function extractChatCompletionsStream(events, log, fallbackModel, collectChunks)
4599
4615
  }
4600
4616
  function parseResponsesResponse(value) {
4601
4617
  const fromResponseProperty = OpenAIResponsesResponseSchema.safeParse(
4602
- readProperty$3(value, "response")
4618
+ readProperty$2(value, "response")
4603
4619
  );
4604
4620
  if (fromResponseProperty.success) return fromResponseProperty.data;
4605
4621
  const direct = OpenAIResponsesResponseSchema.safeParse(value);
@@ -5416,6 +5432,146 @@ function withBuiltinProviderModelMetadata(provider, updatedAt) {
5416
5432
  modelMetadata: result.modelMetadata
5417
5433
  };
5418
5434
  }
5435
+ const SECRET_REFERENCE_PREFIX = "secret:provider:";
5436
+ const SECRET_FILE_VERSION = 1;
5437
+ const KEY_BYTES = 32;
5438
+ const IV_BYTES = 12;
5439
+ const AUTH_TAG_BYTES = 16;
5440
+ const SecretFileSchema = object({
5441
+ version: literal(SECRET_FILE_VERSION),
5442
+ secrets: record(string(), string())
5443
+ });
5444
+ function emptySecretFile() {
5445
+ return { version: SECRET_FILE_VERSION, secrets: {} };
5446
+ }
5447
+ function readSecretFile(path2) {
5448
+ if (!existsSync(path2)) return emptySecretFile();
5449
+ try {
5450
+ const parsed = JSON.parse(readFileSync(path2, "utf8"));
5451
+ const result = SecretFileSchema.safeParse(parsed);
5452
+ if (result.success) return result.data;
5453
+ logger.warn(`[provider-secrets] Ignoring invalid secret file ${path2}`);
5454
+ } catch (error) {
5455
+ logger.warn(`[provider-secrets] Could not read ${path2}: ${String(error)}`);
5456
+ }
5457
+ return emptySecretFile();
5458
+ }
5459
+ function writePrivateFile(path2, content) {
5460
+ const temporaryPath = `${path2}.${String(process.pid)}.tmp`;
5461
+ try {
5462
+ writeFileSync(temporaryPath, content, { encoding: "utf8", mode: 384 });
5463
+ chmodSync(temporaryPath, 384);
5464
+ renameSync(temporaryPath, path2);
5465
+ chmodSync(path2, 384);
5466
+ return true;
5467
+ } catch (error) {
5468
+ rmSync(temporaryPath, { force: true });
5469
+ logger.error(`[provider-secrets] Could not write ${path2}: ${String(error)}`);
5470
+ return false;
5471
+ }
5472
+ }
5473
+ function readKey(path2) {
5474
+ try {
5475
+ if (!existsSync(path2)) return null;
5476
+ const key = Buffer.from(readFileSync(path2, "utf8").trim(), "base64");
5477
+ if (key.byteLength === KEY_BYTES) return key;
5478
+ logger.error(`[provider-secrets] Invalid encryption key at ${path2}`);
5479
+ return null;
5480
+ } catch (error) {
5481
+ logger.error(`[provider-secrets] Could not read encryption key: ${String(error)}`);
5482
+ return null;
5483
+ }
5484
+ }
5485
+ function readOrCreateKey(keyPath, secretsPath) {
5486
+ const existingKey = readKey(keyPath);
5487
+ if (existingKey !== null) return existingKey;
5488
+ if (existsSync(keyPath)) return null;
5489
+ if (existsSync(secretsPath)) {
5490
+ logger.error(
5491
+ `[provider-secrets] Refusing to replace missing key while encrypted secrets exist at ${secretsPath}`
5492
+ );
5493
+ return null;
5494
+ }
5495
+ const key = randomBytes(KEY_BYTES);
5496
+ if (!writePrivateFile(keyPath, key.toString("base64"))) return null;
5497
+ return key;
5498
+ }
5499
+ function encrypt(secret, key) {
5500
+ try {
5501
+ const iv = randomBytes(IV_BYTES);
5502
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
5503
+ const ciphertext = Buffer.concat([cipher.update(secret, "utf8"), cipher.final()]);
5504
+ const tag = cipher.getAuthTag();
5505
+ return Buffer.concat([iv, tag, ciphertext]).toString("base64");
5506
+ } catch (error) {
5507
+ logger.error(`[provider-secrets] Encryption failed: ${String(error)}`);
5508
+ return null;
5509
+ }
5510
+ }
5511
+ function decrypt(payload, key) {
5512
+ try {
5513
+ const packed = Buffer.from(payload, "base64");
5514
+ if (packed.byteLength <= IV_BYTES + AUTH_TAG_BYTES) return null;
5515
+ const iv = packed.subarray(0, IV_BYTES);
5516
+ const tag = packed.subarray(IV_BYTES, IV_BYTES + AUTH_TAG_BYTES);
5517
+ const ciphertext = packed.subarray(IV_BYTES + AUTH_TAG_BYTES);
5518
+ const decipher = createDecipheriv("aes-256-gcm", key, iv);
5519
+ decipher.setAuthTag(tag);
5520
+ return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8");
5521
+ } catch {
5522
+ return null;
5523
+ }
5524
+ }
5525
+ function providerIdFromReference(reference) {
5526
+ if (!reference.startsWith(SECRET_REFERENCE_PREFIX)) return null;
5527
+ const providerId = reference.slice(SECRET_REFERENCE_PREFIX.length);
5528
+ return providerId === "" ? null : providerId;
5529
+ }
5530
+ function createProviderSecretStore(directory) {
5531
+ mkdirSync(directory, { recursive: true });
5532
+ const keyPath = join(directory, "provider-secrets.key");
5533
+ const secretsPath = join(directory, "provider-secrets.json");
5534
+ function writeSecrets(data) {
5535
+ return writePrivateFile(secretsPath, JSON.stringify(data, null, 2));
5536
+ }
5537
+ return {
5538
+ isReference: (value) => providerIdFromReference(value) !== null,
5539
+ write: (providerId, secret) => {
5540
+ const key = readOrCreateKey(keyPath, secretsPath);
5541
+ if (key === null) return null;
5542
+ const encrypted = encrypt(secret, key);
5543
+ if (encrypted === null) return null;
5544
+ const data = readSecretFile(secretsPath);
5545
+ const next = {
5546
+ ...data,
5547
+ secrets: { ...data.secrets, [providerId]: encrypted }
5548
+ };
5549
+ if (!writeSecrets(next)) return null;
5550
+ return `${SECRET_REFERENCE_PREFIX}${providerId}`;
5551
+ },
5552
+ read: (reference) => {
5553
+ const providerId = providerIdFromReference(reference);
5554
+ if (providerId === null) return null;
5555
+ const key = readKey(keyPath);
5556
+ if (key === null) return null;
5557
+ const encrypted = readSecretFile(secretsPath).secrets[providerId];
5558
+ if (encrypted === void 0) return null;
5559
+ return decrypt(encrypted, key);
5560
+ },
5561
+ delete: (providerId) => {
5562
+ const data = readSecretFile(secretsPath);
5563
+ if (data.secrets[providerId] === void 0) return;
5564
+ const nextSecrets = { ...data.secrets };
5565
+ delete nextSecrets[providerId];
5566
+ void writeSecrets({ ...data, secrets: nextSecrets });
5567
+ }
5568
+ };
5569
+ }
5570
+ function secretValuesEqual(left, right) {
5571
+ const leftBuffer = Buffer.from(left);
5572
+ const rightBuffer = Buffer.from(right);
5573
+ return leftBuffer.byteLength === rightBuffer.byteLength && timingSafeEqual(leftBuffer, rightBuffer);
5574
+ }
5419
5575
  object({
5420
5576
  providers: array(ProviderConfigSchema)
5421
5577
  });
@@ -5562,6 +5718,60 @@ const store = new Conf({
5562
5718
  },
5563
5719
  ...configPath !== void 0 ? { path: configPath } : {}
5564
5720
  });
5721
+ const providerSecretStore = createProviderSecretStore(dirname(store.path));
5722
+ function providerWithResolvedSecret(provider) {
5723
+ if (!providerSecretStore.isReference(provider.apiKey)) return provider;
5724
+ const apiKey = providerSecretStore.read(provider.apiKey);
5725
+ if (apiKey !== null) return { ...provider, apiKey };
5726
+ logger.error(`[providers] Secret is missing or unreadable for provider "${provider.name}"`);
5727
+ return null;
5728
+ }
5729
+ function persistProviders(providers, removedProviderIds = /* @__PURE__ */ new Set()) {
5730
+ const existingReferences = /* @__PURE__ */ new Map();
5731
+ const unresolvedRecords = /* @__PURE__ */ new Map();
5732
+ for (const candidate of store.get("providers", [])) {
5733
+ const parsed = ProviderConfigSchema.safeParse(candidate);
5734
+ if (parsed.success && providerSecretStore.isReference(parsed.data.apiKey)) {
5735
+ existingReferences.set(parsed.data.id, parsed.data.apiKey);
5736
+ if (providerSecretStore.read(parsed.data.apiKey) === null) {
5737
+ unresolvedRecords.set(parsed.data.id, parsed.data);
5738
+ }
5739
+ }
5740
+ }
5741
+ const persisted = providers.map((provider) => {
5742
+ const existingReference = existingReferences.get(provider.id);
5743
+ if (existingReference !== void 0) {
5744
+ const existingSecret = providerSecretStore.read(existingReference);
5745
+ if (existingSecret !== null && secretValuesEqual(existingSecret, provider.apiKey)) {
5746
+ return { ...provider, apiKey: existingReference };
5747
+ }
5748
+ }
5749
+ const reference = providerSecretStore.write(provider.id, provider.apiKey);
5750
+ if (reference !== null) return { ...provider, apiKey: reference };
5751
+ logger.error(
5752
+ `[providers] SecretStore unavailable; provider "${provider.name}" remains in the legacy storage format`
5753
+ );
5754
+ return provider;
5755
+ });
5756
+ const persistedIds = new Set(persisted.map((provider) => provider.id));
5757
+ for (const [id, provider] of unresolvedRecords) {
5758
+ if (!persistedIds.has(id) && !removedProviderIds.has(id)) persisted.push(provider);
5759
+ }
5760
+ store.set("providers", persisted);
5761
+ }
5762
+ function migratePlaintextProviderSecrets() {
5763
+ const raw = store.get("providers", []);
5764
+ let changed = false;
5765
+ const migrated = raw.map((candidate) => {
5766
+ const parsed = ProviderConfigSchema.safeParse(candidate);
5767
+ if (!parsed.success || providerSecretStore.isReference(parsed.data.apiKey)) return candidate;
5768
+ const reference = providerSecretStore.write(parsed.data.id, parsed.data.apiKey);
5769
+ if (reference === null) return candidate;
5770
+ changed = true;
5771
+ return { ...parsed.data, apiKey: reference };
5772
+ });
5773
+ if (changed) store.set("providers", migrated);
5774
+ }
5565
5775
  migrateFromDataDirConfig(store, dataDir);
5566
5776
  migrateFromLegacyConfLocation(store);
5567
5777
  function migrateProvider(p) {
@@ -5663,13 +5873,15 @@ if (providersJson !== void 0) {
5663
5873
  } catch {
5664
5874
  }
5665
5875
  }
5876
+ migratePlaintextProviderSecrets();
5666
5877
  function getProviders() {
5667
5878
  const raw = store.get("providers", []);
5668
5879
  const valid = [];
5669
5880
  for (const candidate of raw) {
5670
5881
  const entry = ProviderConfigSchema.safeParse(candidate);
5671
5882
  if (entry.success) {
5672
- valid.push(entry.data);
5883
+ const resolved = providerWithResolvedSecret(entry.data);
5884
+ if (resolved !== null) valid.push(resolved);
5673
5885
  continue;
5674
5886
  }
5675
5887
  let candidateName = "<unknown>";
@@ -5690,7 +5902,7 @@ function getProvider(id) {
5690
5902
  function redactProvider(provider) {
5691
5903
  return {
5692
5904
  ...provider,
5693
- apiKey: maskApiKey(provider.apiKey)
5905
+ apiKey: "configured"
5694
5906
  };
5695
5907
  }
5696
5908
  function redactProviders(providers) {
@@ -5707,7 +5919,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
5707
5919
  const resolvedOpenaiResponsesUrl = openaiResponsesBaseUrl ?? "";
5708
5920
  const now = (/* @__PURE__ */ new Date()).toISOString();
5709
5921
  const existing = providers.find(
5710
- (p) => p.apiKey === normalizedKey && (p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") && (p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? "") && (p.openaiResponsesBaseUrl ?? "") === (resolvedOpenaiResponsesUrl ?? "")
5922
+ (p) => secretValuesEqual(p.apiKey, normalizedKey) && (p.anthropicBaseUrl ?? "") === (resolvedAnthropicUrl ?? "") && (p.openaiBaseUrl ?? "") === (resolvedOpenaiUrl ?? "") && (p.openaiResponsesBaseUrl ?? "") === (resolvedOpenaiResponsesUrl ?? "")
5711
5923
  );
5712
5924
  if (existing) {
5713
5925
  const existingIndex = providers.findIndex((p) => p.id === existing.id);
@@ -5724,7 +5936,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
5724
5936
  if (existingIndex >= 0) {
5725
5937
  providers[existingIndex] = enriched;
5726
5938
  }
5727
- store.set("providers", providers);
5939
+ persistProviders(providers);
5728
5940
  return enriched;
5729
5941
  }
5730
5942
  const newProvider = withBuiltinProviderModelMetadata(
@@ -5749,7 +5961,7 @@ function addProvider(name2, apiKey, format, baseUrl, models, authHeader, apiDocs
5749
5961
  now
5750
5962
  );
5751
5963
  providers.push(newProvider);
5752
- store.set("providers", providers);
5964
+ persistProviders(providers);
5753
5965
  return newProvider;
5754
5966
  }
5755
5967
  function updateProvider(id, updates) {
@@ -5781,14 +5993,15 @@ function updateProvider(id, updates) {
5781
5993
  );
5782
5994
  const index = providers.findIndex((p) => p.id === id);
5783
5995
  providers[index] = updated;
5784
- store.set("providers", providers);
5996
+ persistProviders(providers);
5785
5997
  return updated;
5786
5998
  }
5787
5999
  function deleteProvider(id) {
5788
6000
  const providers = getProviders();
5789
6001
  const filtered = providers.filter((p) => p.id !== id);
5790
6002
  if (filtered.length === providers.length) return false;
5791
- store.set("providers", filtered);
6003
+ persistProviders(filtered, /* @__PURE__ */ new Set([id]));
6004
+ providerSecretStore.delete(id);
5792
6005
  return true;
5793
6006
  }
5794
6007
  function exportProviders() {
@@ -5855,7 +6068,7 @@ function importProviders(json) {
5855
6068
  imported++;
5856
6069
  }
5857
6070
  if (newProviders.length > 0) {
5858
- store.set("providers", [...existingProviders, ...newProviders]);
6071
+ persistProviders([...existingProviders, ...newProviders]);
5859
6072
  }
5860
6073
  } catch (err) {
5861
6074
  return {
@@ -6486,6 +6699,145 @@ function toPersistedConfig(value) {
6486
6699
  timeDisplayFormat: value.timeDisplayFormat
6487
6700
  };
6488
6701
  }
6702
+ const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set([
6703
+ "localhost",
6704
+ "ip6-localhost",
6705
+ "ip6-loopback",
6706
+ "[::1]",
6707
+ "[::ffff:127.0.0.1]"
6708
+ ]);
6709
+ function isLoopbackHost(hostname) {
6710
+ const lower2 = hostname.toLowerCase().replace(/^\[|]$/g, "");
6711
+ if (LOOPBACK_HOSTNAMES.has(lower2)) return true;
6712
+ if (lower2 === "::1") return true;
6713
+ if (lower2.startsWith("::ffff:127.")) return true;
6714
+ if (lower2.startsWith("::ffff:7f00:")) return true;
6715
+ return /^127\./.test(lower2);
6716
+ }
6717
+ function mappedIpv4Address(hostname) {
6718
+ const lower2 = hostname.toLowerCase();
6719
+ if (!lower2.startsWith("::ffff:")) return null;
6720
+ const suffix = lower2.slice("::ffff:".length);
6721
+ if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(suffix)) return suffix;
6722
+ const match = /^([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(suffix);
6723
+ if (match === null) return null;
6724
+ const highText = match[1];
6725
+ const lowText = match[2];
6726
+ if (highText === void 0 || lowText === void 0) return null;
6727
+ const high = Number.parseInt(highText, 16);
6728
+ const low = Number.parseInt(lowText, 16);
6729
+ return `${String(high >> 8)}.${String(high & 255)}.${String(low >> 8)}.${String(low & 255)}`;
6730
+ }
6731
+ function isInternalHost(hostname) {
6732
+ if (LOOPBACK_HOSTNAMES.has(hostname.toLowerCase())) return true;
6733
+ const mappedIpv4 = mappedIpv4Address(hostname);
6734
+ if (mappedIpv4 !== null) return isInternalHost(mappedIpv4);
6735
+ const ipv4Match = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(hostname);
6736
+ if (ipv4Match !== null) {
6737
+ const [, a, b] = ipv4Match;
6738
+ if (a === void 0 || b === void 0) return false;
6739
+ const first = Number.parseInt(a, 10);
6740
+ const second = Number.parseInt(b, 10);
6741
+ if (first === 127) return true;
6742
+ if (first === 10) return true;
6743
+ if (first === 172 && second >= 16 && second <= 31) return true;
6744
+ if (first === 192 && second === 168) return true;
6745
+ if (first === 169 && second === 254) return true;
6746
+ if (first === 0) return true;
6747
+ if (first === 100 && second >= 64 && second <= 127) return true;
6748
+ if (first >= 224) return true;
6749
+ if (first === 198 && (second === 18 || second === 19)) return true;
6750
+ return false;
6751
+ }
6752
+ const lower2 = hostname.toLowerCase();
6753
+ if (lower2 === "::") return true;
6754
+ if (lower2 === "::1") return true;
6755
+ if (/^fe[89ab]/.test(lower2)) return true;
6756
+ if (lower2.startsWith("fc") || lower2.startsWith("fd")) return true;
6757
+ if (lower2.startsWith("ff")) return true;
6758
+ return false;
6759
+ }
6760
+ function evaluateUpstreamUrl(rawUrl, options = {}) {
6761
+ if (options.bypass === true) return { allow: true };
6762
+ let parsed;
6763
+ try {
6764
+ parsed = new URL(rawUrl);
6765
+ } catch {
6766
+ return { allow: false, reason: "Not a valid URL" };
6767
+ }
6768
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
6769
+ return { allow: false, reason: `Unsupported scheme "${parsed.protocol}"` };
6770
+ }
6771
+ if (options.allowLoopback === true && parsed.hostname === "localhost") {
6772
+ return { allow: true };
6773
+ }
6774
+ const hostname = parsed.hostname.replace(/^\[|]$/g, "");
6775
+ if (isInternalHost(hostname)) {
6776
+ if (options.allowLoopback === true) {
6777
+ if (isLoopbackHost(hostname)) return { allow: true };
6778
+ }
6779
+ return {
6780
+ allow: false,
6781
+ reason: `Refusing to proxy to internal address "${hostname}"`
6782
+ };
6783
+ }
6784
+ return { allow: true };
6785
+ }
6786
+ async function defaultResolver(hostname) {
6787
+ return lookup(hostname, { all: true, verbatim: true });
6788
+ }
6789
+ function isLoopbackAddress(hostname) {
6790
+ const lower2 = hostname.toLowerCase();
6791
+ return lower2 === "::1" || lower2.startsWith("::ffff:127.") || lower2.startsWith("::ffff:7f00:") || /^127\./.test(lower2);
6792
+ }
6793
+ async function evaluateResolvedUpstreamUrl(rawUrl, options = {}) {
6794
+ const initial = evaluateUpstreamUrl(rawUrl, options);
6795
+ if (!initial.allow || options.bypass === true) return initial;
6796
+ const parsed = new URL(rawUrl);
6797
+ const hostname = parsed.hostname.replace(/^\[|]$/g, "");
6798
+ if (isIP(hostname) !== 0 || hostname.toLowerCase() === "localhost") return initial;
6799
+ let addresses;
6800
+ try {
6801
+ addresses = await (options.resolver ?? defaultResolver)(hostname);
6802
+ } catch (error) {
6803
+ return {
6804
+ allow: false,
6805
+ reason: `Could not safely resolve upstream host "${hostname}": ${String(error)}`
6806
+ };
6807
+ }
6808
+ if (addresses.length === 0) {
6809
+ return { allow: false, reason: `Upstream host "${hostname}" resolved to no addresses` };
6810
+ }
6811
+ for (const resolved of addresses) {
6812
+ if (!isInternalHost(resolved.address)) continue;
6813
+ if (options.allowLoopback === true && isLoopbackAddress(resolved.address)) continue;
6814
+ return {
6815
+ allow: false,
6816
+ reason: `Refusing upstream host "${hostname}" because it resolves to internal address "${resolved.address}"`
6817
+ };
6818
+ }
6819
+ return { allow: true };
6820
+ }
6821
+ function isRedirectStatus(status) {
6822
+ return status >= 300 && status < 400;
6823
+ }
6824
+ async function safeFetch(rawUrl, init = {}, options = {}) {
6825
+ const decision = await evaluateResolvedUpstreamUrl(rawUrl, options);
6826
+ if (!decision.allow) {
6827
+ return { ok: false, kind: "network-policy", reason: decision.reason };
6828
+ }
6829
+ const response = await fetch(rawUrl, { ...init, redirect: "manual" });
6830
+ if (!isRedirectStatus(response.status)) return { ok: true, response };
6831
+ if (response.body !== null) {
6832
+ await response.body.cancel().catch(() => void 0);
6833
+ }
6834
+ const location = response.headers.get("location");
6835
+ return {
6836
+ ok: false,
6837
+ kind: "redirect",
6838
+ reason: location === null ? `Upstream returned blocked redirect HTTP ${String(response.status)}` : `Upstream redirect to "${location}" is blocked; configure the final API URL directly`
6839
+ };
6840
+ }
6489
6841
  const BILLING_HEADER_PREFIX = "x-anthropic-billing-header:";
6490
6842
  function isClaudeCodeBillingHeaderBlock(text) {
6491
6843
  const trimmed = text.trimStart();
@@ -6631,30 +6983,23 @@ function parseJson$1(raw) {
6631
6983
  return { ok: false };
6632
6984
  }
6633
6985
  }
6634
- function isRecord$2(value) {
6635
- return typeof value === "object" && value !== null && !Array.isArray(value);
6636
- }
6637
- function readProperty$2(value, key) {
6638
- if (!isRecord$2(value)) return void 0;
6639
- return Object.getOwnPropertyDescriptor(value, key)?.value;
6640
- }
6641
6986
  function hasOwnProperty(value, key) {
6642
6987
  return Object.getOwnPropertyDescriptor(value, key) !== void 0;
6643
6988
  }
6644
6989
  function readString(value, key) {
6645
- const property = readProperty$2(value, key);
6990
+ const property = readProperty$5(value, key);
6646
6991
  return typeof property === "string" ? property : null;
6647
6992
  }
6648
6993
  function readArray(value, key) {
6649
- const property = readProperty$2(value, key);
6994
+ const property = readProperty$5(value, key);
6650
6995
  return Array.isArray(property) ? property : [];
6651
6996
  }
6652
6997
  function readStringArray(value, key) {
6653
6998
  return readArray(value, key).filter((item) => typeof item === "string");
6654
6999
  }
6655
7000
  function readObject(value, key) {
6656
- const property = readProperty$2(value, key);
6657
- return isRecord$2(property) ? property : null;
7001
+ const property = readProperty$5(value, key);
7002
+ return isRecord(property) ? property : null;
6658
7003
  }
6659
7004
  function normalizeExpectedType(typeName) {
6660
7005
  switch (typeName) {
@@ -6671,7 +7016,7 @@ function normalizeExpectedType(typeName) {
6671
7016
  }
6672
7017
  }
6673
7018
  function schemaTypes(schema) {
6674
- const typeValue = readProperty$2(schema, "type");
7019
+ const typeValue = readProperty$5(schema, "type");
6675
7020
  const rawTypes = Array.isArray(typeValue) ? typeValue : [typeValue];
6676
7021
  const types = [];
6677
7022
  for (const rawType of rawTypes) {
@@ -6699,7 +7044,7 @@ function actualType(value) {
6699
7044
  case "function":
6700
7045
  case "object":
6701
7046
  case "symbol":
6702
- return isRecord$2(value) ? "object" : "unknown";
7047
+ return isRecord(value) ? "object" : "unknown";
6703
7048
  }
6704
7049
  return "unknown";
6705
7050
  }
@@ -6716,7 +7061,7 @@ function matchesExpectedType(value, expected) {
6716
7061
  case "number":
6717
7062
  return typeof value === "number" && Number.isFinite(value);
6718
7063
  case "object":
6719
- return isRecord$2(value);
7064
+ return isRecord(value);
6720
7065
  case "string":
6721
7066
  return typeof value === "string";
6722
7067
  }
@@ -6761,7 +7106,7 @@ function schemaAllowsObjectValidation(schema, expectedTypes) {
6761
7106
  return expectedTypes.includes("object");
6762
7107
  }
6763
7108
  function schemaAllowsArrayValidation(schema, expectedTypes) {
6764
- if (expectedTypes.length === 0) return readProperty$2(schema, "items") !== void 0;
7109
+ if (expectedTypes.length === 0) return readProperty$5(schema, "items") !== void 0;
6765
7110
  return expectedTypes.includes("array");
6766
7111
  }
6767
7112
  function addTypeWarning(warnings, toolName, path2, expectedTypes, value) {
@@ -6794,7 +7139,7 @@ function validateObjectSchema(schema, value, toolName, path2, warnings) {
6794
7139
  }
6795
7140
  }
6796
7141
  function validateArraySchema(schema, value, toolName, path2, warnings) {
6797
- const itemsSchema = readProperty$2(schema, "items");
7142
+ const itemsSchema = readProperty$5(schema, "items");
6798
7143
  if (itemsSchema === void 0) return;
6799
7144
  let index = 0;
6800
7145
  for (const item of value) {
@@ -6803,7 +7148,7 @@ function validateArraySchema(schema, value, toolName, path2, warnings) {
6803
7148
  }
6804
7149
  }
6805
7150
  function validateEnum(schema, value, toolName, path2, warnings) {
6806
- const enumValue = readProperty$2(schema, "enum");
7151
+ const enumValue = readProperty$5(schema, "enum");
6807
7152
  if (Array.isArray(enumValue) && enumValue.length > 0 && !enumMatches(value, enumValue)) {
6808
7153
  warnings.push(
6809
7154
  `Tool call "${toolName}" ${pathLabel(path2)} expected one of ${formatJsonValue(
@@ -6821,7 +7166,7 @@ function validateEnum(schema, value, toolName, path2, warnings) {
6821
7166
  }
6822
7167
  }
6823
7168
  function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6824
- if (!isRecord$2(schema)) return;
7169
+ if (!isRecord(schema)) return;
6825
7170
  const expectedTypes = schemaTypes(schema);
6826
7171
  if (expectedTypes.length > 0 && !matchesAnyExpectedType(value, expectedTypes)) {
6827
7172
  addTypeWarning(warnings, toolName, path2, expectedTypes, value);
@@ -6829,7 +7174,7 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6829
7174
  }
6830
7175
  validateEnum(schema, value, toolName, path2, warnings);
6831
7176
  if (schemaAllowsObjectValidation(schema, expectedTypes)) {
6832
- if (isRecord$2(value)) {
7177
+ if (isRecord(value)) {
6833
7178
  validateObjectSchema(schema, value, toolName, path2, warnings);
6834
7179
  } else if (expectedTypes.length === 0) {
6835
7180
  warnings.push(
@@ -6850,7 +7195,7 @@ function validateValueAgainstSchema(schema, value, toolName, path2, warnings) {
6850
7195
  function extractAnthropicToolSchemas(request) {
6851
7196
  const tools = /* @__PURE__ */ new Map();
6852
7197
  for (const tool of readArray(request, "tools")) {
6853
- if (!isRecord$2(tool)) continue;
7198
+ if (!isRecord(tool)) continue;
6854
7199
  const name2 = readString(tool, "name");
6855
7200
  if (name2 === null || name2 === "") continue;
6856
7201
  tools.set(name2, {
@@ -6920,7 +7265,7 @@ function extractOpenAIToolCalls(response) {
6920
7265
  for (const call of readArray(message, "tool_calls")) {
6921
7266
  const functionCall = readObject(call, "function");
6922
7267
  const name2 = functionCall === null ? null : readString(functionCall, "name");
6923
- const parsedArguments = functionCall === null ? { value: {}, issue: "function block is missing" } : parseOpenAIArguments(readProperty$2(functionCall, "arguments"));
7268
+ const parsedArguments = functionCall === null ? { value: {}, issue: "function block is missing" } : parseOpenAIArguments(readProperty$5(functionCall, "arguments"));
6924
7269
  calls.push({
6925
7270
  name: name2,
6926
7271
  arguments: parsedArguments.value,
@@ -6931,7 +7276,7 @@ function extractOpenAIToolCalls(response) {
6931
7276
  }
6932
7277
  const legacyFunctionCall = readObject(message, "function_call");
6933
7278
  if (legacyFunctionCall !== null) {
6934
- const parsedArguments = parseOpenAIArguments(readProperty$2(legacyFunctionCall, "arguments"));
7279
+ const parsedArguments = parseOpenAIArguments(readProperty$5(legacyFunctionCall, "arguments"));
6935
7280
  calls.push({
6936
7281
  name: readString(legacyFunctionCall, "name"),
6937
7282
  arguments: parsedArguments.value,
@@ -6947,7 +7292,7 @@ function extractOpenAIToolCalls(response) {
6947
7292
  outputIndex++;
6948
7293
  continue;
6949
7294
  }
6950
- const parsedArguments = parseOpenAIArguments(readProperty$2(item, "arguments"));
7295
+ const parsedArguments = parseOpenAIArguments(readProperty$5(item, "arguments"));
6951
7296
  calls.push({
6952
7297
  name: readString(item, "name"),
6953
7298
  arguments: parsedArguments.value,
@@ -6968,7 +7313,7 @@ function extractAnthropicToolCalls(response) {
6968
7313
  }
6969
7314
  calls.push({
6970
7315
  name: readString(block, "name"),
6971
- arguments: readProperty$2(block, "input") ?? {},
7316
+ arguments: readProperty$5(block, "input") ?? {},
6972
7317
  argumentIssue: null,
6973
7318
  location: `content[${contentIndex}]`
6974
7319
  });
@@ -7751,12 +8096,14 @@ function injectProviderAuth(headers, provider) {
7751
8096
  }
7752
8097
  headers.set(HEADER_AUTHORIZATION, `Bearer ${apiKey}`);
7753
8098
  }
8099
+ const ALLOW_LOOPBACK$2 = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
7754
8100
  function buildProxyHeaders(originalHeaders, captureRawHeaders) {
7755
8101
  const rawHeaders = captureRawHeaders ? {} : void 0;
7756
8102
  const headers = new Headers();
7757
8103
  if (rawHeaders !== void 0) {
7758
8104
  originalHeaders.forEach((value, key) => {
7759
- rawHeaders[key.toLowerCase()] = value;
8105
+ const lowerKey = key.toLowerCase();
8106
+ rawHeaders[lowerKey] = SENSITIVE_HEADERS.has(lowerKey) ? maskApiKey(value) : value;
7760
8107
  });
7761
8108
  }
7762
8109
  headers.set(HEADER_USER_AGENT, PROXY_IDENTITY);
@@ -7784,6 +8131,37 @@ function parseRequestPath(req, url) {
7784
8131
  function errorMessage(err) {
7785
8132
  return err instanceof Error ? err.message : String(err);
7786
8133
  }
8134
+ async function readBodyWithLimit(req, limitBytes) {
8135
+ if (req.body === null || req.method === "GET" || req.method === "HEAD") {
8136
+ return { ok: true, body: null };
8137
+ }
8138
+ const contentLengthHeader = req.headers.get(HEADER_CONTENT_LENGTH);
8139
+ if (contentLengthHeader !== null) {
8140
+ const declared = Number.parseInt(contentLengthHeader, 10);
8141
+ if (Number.isFinite(declared) && declared > limitBytes) {
8142
+ return { ok: false, reason: "too-large" };
8143
+ }
8144
+ }
8145
+ const reader = req.body.getReader();
8146
+ const decoder = new TextDecoder();
8147
+ const chunks = [];
8148
+ let totalBytes = 0;
8149
+ try {
8150
+ for (; ; ) {
8151
+ const { done, value } = await reader.read();
8152
+ if (done) break;
8153
+ totalBytes += value.byteLength;
8154
+ if (totalBytes > limitBytes) {
8155
+ return { ok: false, reason: "too-large" };
8156
+ }
8157
+ chunks.push(decoder.decode(value, { stream: true }));
8158
+ }
8159
+ chunks.push(decoder.decode());
8160
+ return { ok: true, body: chunks.join("") };
8161
+ } finally {
8162
+ reader.releaseLock();
8163
+ }
8164
+ }
7787
8165
  function scheduleLogFinalization(job) {
7788
8166
  void enqueueFinalizeLogJob(job).catch((err) => {
7789
8167
  logger.error(
@@ -7891,10 +8269,12 @@ function handleStreamingResponse(upstreamRes, req, startTime, upstreamUrl, log,
7891
8269
  async function handleProxy(req) {
7892
8270
  const url = new URL(req.url);
7893
8271
  const parsed = parseRequestPath(req, url);
7894
- let requestBody = null;
7895
- if (req.body && req.method !== "GET" && req.method !== "HEAD") {
7896
- requestBody = await req.text();
8272
+ const bodyRead = await readBodyWithLimit(req, MAX_REQUEST_BODY_BYTES);
8273
+ if (!bodyRead.ok) {
8274
+ logger.warn(`[handler] Rejected oversized request body: ${req.method} ${parsed.apiPath}`);
8275
+ return new Response("Request body too large", { status: STATUS_PAYLOAD_TOO_LARGE });
7897
8276
  }
8277
+ const requestBody = bodyRead.body;
7898
8278
  const config = getConfig();
7899
8279
  let bodyToForward = requestBody;
7900
8280
  if (config.stripClaudeCodeBillingHeader && requestBody !== null && parsed.isMessages) {
@@ -7941,7 +8321,8 @@ async function handleProxy(req) {
7941
8321
  const upstreamHeadersObj = captureFullDetails ? {} : void 0;
7942
8322
  if (upstreamHeadersObj !== void 0) {
7943
8323
  upstreamHeaders.forEach((value, key) => {
7944
- upstreamHeadersObj[key.toLowerCase()] = value;
8324
+ const lowerKey = key.toLowerCase();
8325
+ upstreamHeadersObj[lowerKey] = SENSITIVE_HEADERS.has(lowerKey) ? maskApiKey(value) : value;
7945
8326
  });
7946
8327
  }
7947
8328
  const log = await createLog(
@@ -7959,12 +8340,30 @@ async function handleProxy(req) {
7959
8340
  );
7960
8341
  let upstreamRes;
7961
8342
  try {
7962
- upstreamRes = await fetch(upstreamUrl, {
7963
- method: req.method,
7964
- headers: upstreamHeaders,
7965
- body: bodyToForward,
7966
- signal: req.signal
7967
- });
8343
+ const fetchResult = await safeFetch(
8344
+ upstreamUrl,
8345
+ {
8346
+ method: req.method,
8347
+ headers: upstreamHeaders,
8348
+ body: bodyToForward,
8349
+ signal: req.signal
8350
+ },
8351
+ { allowLoopback: ALLOW_LOOPBACK$2 }
8352
+ );
8353
+ if (!fetchResult.ok) {
8354
+ const status = fetchResult.kind === "network-policy" ? STATUS_FORBIDDEN : STATUS_BAD_GATEWAY;
8355
+ logger.warn(`[handler] Safe fetch blocked ${upstreamUrl}: ${fetchResult.reason}`);
8356
+ log.elapsedMs = Date.now() - startTime;
8357
+ log.responseStatus = status;
8358
+ log.responseText = fetchResult.reason;
8359
+ appendLogEntry({ ...buildFileLogEntry(log, upstreamUrl), error: fetchResult.reason });
8360
+ finalizeLogUpdate(log);
8361
+ return new Response(
8362
+ fetchResult.kind === "network-policy" ? "Forbidden: upstream URL is not permitted" : "Proxy error: upstream redirects are not permitted",
8363
+ { status }
8364
+ );
8365
+ }
8366
+ upstreamRes = fetchResult.response;
7968
8367
  } catch (err) {
7969
8368
  log.elapsedMs = Date.now() - startTime;
7970
8369
  if (err instanceof Error && err.name === "AbortError") {
@@ -9506,6 +9905,7 @@ const Route$H = createFileRoute("/api/runs")({
9506
9905
  }
9507
9906
  }
9508
9907
  });
9908
+ const ALLOW_LOOPBACK$1 = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
9509
9909
  const ProviderInputSchema = object({
9510
9910
  name: string().min(1, "Name is required"),
9511
9911
  apiKey: string().min(1, "API key is required"),
@@ -9524,17 +9924,25 @@ const ProviderInputSchema = object({
9524
9924
  const Route$G = createFileRoute("/api/providers")({
9525
9925
  server: {
9526
9926
  handlers: {
9527
- GET: ({ request }) => {
9528
- const url = new URL(request.url);
9529
- const includeSecrets = url.searchParams.get("includeSecrets") === "1";
9530
- const providers = getProviders();
9531
- return Response.json(includeSecrets ? providers : redactProviders(providers));
9927
+ GET: () => {
9928
+ return Response.json(redactProviders(getProviders()));
9532
9929
  },
9533
9930
  POST: async ({ request }) => {
9534
9931
  const parsed = ProviderInputSchema.safeParse(await request.json());
9535
9932
  if (!parsed.success) {
9536
9933
  return Response.json({ error: parsed.error.message }, { status: 400 });
9537
9934
  }
9935
+ for (const candidate of [
9936
+ parsed.data.anthropicBaseUrl,
9937
+ parsed.data.openaiBaseUrl,
9938
+ parsed.data.openaiResponsesBaseUrl
9939
+ ]) {
9940
+ if (candidate === void 0 || candidate === "") continue;
9941
+ const decision = evaluateUpstreamUrl(candidate, { allowLoopback: ALLOW_LOOPBACK$1 });
9942
+ if (!decision.allow) {
9943
+ return Response.json({ error: decision.reason }, { status: 400 });
9944
+ }
9945
+ }
9538
9946
  const newProvider = addProvider(
9539
9947
  parsed.data.name,
9540
9948
  parsed.data.apiKey,
@@ -9551,7 +9959,7 @@ const Route$G = createFileRoute("/api/providers")({
9551
9959
  parsed.data.modelMetadata,
9552
9960
  parsed.data.openaiResponsesBaseUrl
9553
9961
  );
9554
- return Response.json(newProvider, { status: 201 });
9962
+ return Response.json(redactProvider(newProvider), { status: 201 });
9555
9963
  }
9556
9964
  }
9557
9965
  }
@@ -9571,9 +9979,6 @@ const PiAgentResponseSchema = object({
9571
9979
  protocol: _enum(["openaiResponses", "openaiChat", "anthropic"]),
9572
9980
  logCount: number().int().nonnegative()
9573
9981
  });
9574
- function isRecord$1(value) {
9575
- return typeof value === "object" && value !== null && !Array.isArray(value);
9576
- }
9577
9982
  function normalizeAnthropicStopReason(value) {
9578
9983
  switch (value) {
9579
9984
  case "end_turn":
@@ -9612,20 +10017,20 @@ function extractStopReason(log) {
9612
10017
  if (typeof json === "string") {
9613
10018
  json = JSON.parse(json);
9614
10019
  }
9615
- if (!isRecord$1(json)) return null;
10020
+ if (!isRecord(json)) return null;
9616
10021
  if (typeof json.stop_reason === "string") {
9617
10022
  return normalizeAnthropicStopReason(json.stop_reason);
9618
10023
  }
9619
10024
  if (Array.isArray(json.choices)) {
9620
10025
  for (const choice of json.choices) {
9621
- if (!isRecord$1(choice) || typeof choice.finish_reason !== "string") continue;
10026
+ if (!isRecord(choice) || typeof choice.finish_reason !== "string") continue;
9622
10027
  const reason = normalizeOpenAIFinishReason(choice.finish_reason);
9623
10028
  if (reason !== null) return reason;
9624
10029
  }
9625
10030
  }
9626
10031
  if (Array.isArray(json.output)) {
9627
10032
  for (const item of json.output) {
9628
- if (isRecord$1(item) && item.type === "function_call") {
10033
+ if (isRecord(item) && item.type === "function_call") {
9629
10034
  return "tool_use";
9630
10035
  }
9631
10036
  }
@@ -9633,7 +10038,7 @@ function extractStopReason(log) {
9633
10038
  return "stop";
9634
10039
  }
9635
10040
  }
9636
- if (json.status === "incomplete" && isRecord$1(json.incomplete_details)) {
10041
+ if (json.status === "incomplete" && isRecord(json.incomplete_details)) {
9637
10042
  const reason = json.incomplete_details.reason;
9638
10043
  if (typeof reason === "string" && isResponsesLengthLimitReason(reason)) {
9639
10044
  return "length";
@@ -9980,7 +10385,7 @@ const Route$E = createFileRoute("/api/models")({
9980
10385
  }
9981
10386
  });
9982
10387
  const name = "@tonyclaw/agent-inspector";
9983
- const version = "3.0.26";
10388
+ const version = "3.0.27";
9984
10389
  const packageJson = {
9985
10390
  name,
9986
10391
  version
@@ -10062,8 +10467,7 @@ function isMcpWriteEnabled() {
10062
10467
  const readonlyFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_READONLY"]);
10063
10468
  if (readonlyFlag === true) return false;
10064
10469
  const writesFlag = parseFlag(process.env["AGENT_INSPECTOR_MCP_WRITES"]);
10065
- if (writesFlag === false) return false;
10066
- return true;
10470
+ return writesFlag === true;
10067
10471
  }
10068
10472
  function getMcpModeInfo() {
10069
10473
  const writesEnabled = isMcpWriteEnabled();
@@ -10074,7 +10478,7 @@ function getMcpModeInfo() {
10074
10478
  AGENT_INSPECTOR_MCP_READONLY: process.env["AGENT_INSPECTOR_MCP_READONLY"] ?? null,
10075
10479
  AGENT_INSPECTOR_MCP_WRITES: process.env["AGENT_INSPECTOR_MCP_WRITES"] ?? null
10076
10480
  },
10077
- note: writesEnabled ? "MCP write/action tools are enabled. Set AGENT_INSPECTOR_MCP_READONLY=1 to expose the same catalog while blocking mutations." : "MCP is in readonly mode. Read tools/resources still work, while write/action tools return an isError result."
10481
+ note: writesEnabled ? "MCP write/action tools are enabled explicitly by AGENT_INSPECTOR_MCP_WRITES=1." : "MCP is readonly by default. Read tools/resources still work; set AGENT_INSPECTOR_MCP_WRITES=1 to enable mutations."
10078
10482
  };
10079
10483
  }
10080
10484
  function groupEvidenceDir(groupId) {
@@ -10646,10 +11050,6 @@ function toolError(message) {
10646
11050
  function inspectorBaseUrl() {
10647
11051
  return `http://127.0.0.1:${String(getCurrentPort())}`;
10648
11052
  }
10649
- function textByteLength(value) {
10650
- if (value === null) return null;
10651
- return Buffer.byteLength(value, "utf8");
10652
- }
10653
11053
  function hasBodyText(value, byteLength) {
10654
11054
  if (value !== null && value.length > 0) return true;
10655
11055
  return byteLength !== void 0 && byteLength !== null && byteLength > 0;
@@ -10780,14 +11180,29 @@ function processSummary(clientPid, logs, total, limit) {
10780
11180
  }
10781
11181
  async function readCompactLogsPage(callApi2, params) {
10782
11182
  const path2 = `/api/logs?${params.toString()}`;
10783
- const res = await callApi2(path2);
10784
- if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
10785
- const rawBody = await res.json();
10786
- const parsed = LogsListResponseSchema.safeParse(rawBody);
10787
- if (!parsed.success) return toolError("GET /api/logs returned an unparseable logs shape");
11183
+ const parsed = await fetchApiJson(callApi2, path2, LogsListResponseSchema);
11184
+ if (!parsed.ok) return parsed.error;
10788
11185
  const logs = parsed.data.logs ?? [];
10789
11186
  return { logs, total: parsed.data.total ?? logs.length };
10790
11187
  }
11188
+ async function fetchApiJson(callApi2, path2, schema, init) {
11189
+ const res = await callApi2(path2, init);
11190
+ if (!res.ok) {
11191
+ return { ok: false, error: toolError(`${methodFor(init)} ${path2} returned ${res.status}`) };
11192
+ }
11193
+ const rawBody = await res.json();
11194
+ const parsed = schema.safeParse(rawBody);
11195
+ if (!parsed.success) {
11196
+ return {
11197
+ ok: false,
11198
+ error: toolError(`${methodFor(init)} ${path2} returned an unparseable shape`)
11199
+ };
11200
+ }
11201
+ return { ok: true, data: parsed.data };
11202
+ }
11203
+ function methodFor(init) {
11204
+ return (init?.method ?? "GET").toUpperCase();
11205
+ }
10791
11206
  function isToolResult(value) {
10792
11207
  return typeof value === "object" && value !== null && Object.getOwnPropertyDescriptor(value, "content") !== void 0;
10793
11208
  }
@@ -10847,13 +11262,8 @@ async function searchLogsImpl(callApi2, args) {
10847
11262
  });
10848
11263
  }
10849
11264
  async function getLogImpl(callApi2, id) {
10850
- const res = await callApi2(`/api/logs/${id}`);
10851
- if (!res.ok) return toolError(`GET /api/logs/${id} returned ${res.status}`);
10852
- const rawBody = await res.json();
10853
- const parsed = CapturedLogSchema.safeParse(rawBody);
10854
- if (!parsed.success) {
10855
- return toolError(`GET /api/logs/${id} returned an unparseable CapturedLog`);
10856
- }
11265
+ const parsed = await fetchApiJson(callApi2, `/api/logs/${id}`, CapturedLogSchema);
11266
+ if (!parsed.ok) return parsed.error;
10857
11267
  return textJson(buildBoundedLogDetail(parsed.data));
10858
11268
  }
10859
11269
  async function getLogChunksImpl(callApi2, id) {
@@ -10875,11 +11285,8 @@ async function getSessionImpl(callApi2, args) {
10875
11285
  params.set("includeHistory", "1");
10876
11286
  }
10877
11287
  const path2 = `/api/sessions?${params.toString()}`;
10878
- const res = await callApi2(path2);
10879
- if (!res.ok) return toolError(`GET ${path2} returned ${res.status}`);
10880
- const rawBody = await res.json();
10881
- const parsed = SessionInfoSchema.safeParse(rawBody);
10882
- if (!parsed.success) return toolError("GET /api/sessions returned an unparseable session info");
11288
+ const parsed = await fetchApiJson(callApi2, path2, SessionInfoSchema);
11289
+ if (!parsed.ok) return parsed.error;
10883
11290
  return textJson(parsed.data);
10884
11291
  }
10885
11292
  function createRunImpl(args) {
@@ -11089,13 +11496,8 @@ async function getProcessTimelineImpl(callApi2, args) {
11089
11496
  });
11090
11497
  }
11091
11498
  async function getLogHeadersImpl(callApi2, id) {
11092
- const res = await callApi2(`/api/logs/${id}`);
11093
- if (!res.ok) return toolError(`GET /api/logs/${id} returned ${res.status}`);
11094
- const rawBody = await res.json();
11095
- const parsed = CapturedLogSchema.safeParse(rawBody);
11096
- if (!parsed.success) {
11097
- return toolError(`GET /api/logs/${id} returned an unparseable CapturedLog`);
11098
- }
11499
+ const parsed = await fetchApiJson(callApi2, `/api/logs/${id}`, CapturedLogSchema);
11500
+ if (!parsed.ok) return parsed.error;
11099
11501
  const log = parsed.data;
11100
11502
  return textJson({
11101
11503
  id: log.id,
@@ -11346,18 +11748,13 @@ async function listModelsImpl(callApi2) {
11346
11748
  if (!res.ok) return toolError(`GET /api/models returned ${res.status}`);
11347
11749
  return textJson(await res.json());
11348
11750
  }
11349
- function includeSecretsQuery(includeSecrets) {
11350
- return includeSecrets === true ? "?includeSecrets=1" : "";
11351
- }
11352
- async function listProvidersImpl(callApi2, args = {}) {
11353
- const res = await callApi2(`/api/providers${includeSecretsQuery(args.includeSecrets)}`);
11751
+ async function listProvidersImpl(callApi2) {
11752
+ const res = await callApi2("/api/providers");
11354
11753
  if (!res.ok) return toolError(`GET /api/providers returned ${res.status}`);
11355
11754
  return textJson(await res.json());
11356
11755
  }
11357
- async function getProviderImpl(callApi2, id, args = {}) {
11358
- const res = await callApi2(
11359
- `/api/providers/${encodeURIComponent(id)}${includeSecretsQuery(args.includeSecrets)}`
11360
- );
11756
+ async function getProviderImpl(callApi2, id) {
11757
+ const res = await callApi2(`/api/providers/${encodeURIComponent(id)}`);
11361
11758
  if (!res.ok) return toolError(`GET /api/providers/${id} returned ${res.status}`);
11362
11759
  return textJson(await res.json());
11363
11760
  }
@@ -11417,9 +11814,6 @@ async function testProviderImpl(callApi2, id) {
11417
11814
  if (!res.ok) return toolError(`POST /api/providers/${id}/test returned ${res.status}`);
11418
11815
  return textJson(await res.json());
11419
11816
  }
11420
- function isRecord(value) {
11421
- return typeof value === "object" && value !== null && !Array.isArray(value);
11422
- }
11423
11817
  function stringField(record2, key) {
11424
11818
  const value = record2[key];
11425
11819
  return typeof value === "string" && value.length > 0 ? value : null;
@@ -12442,24 +12836,21 @@ function registerTools(server) {
12442
12836
  "inspector_list_providers",
12443
12837
  {
12444
12838
  title: "List configured LLM providers",
12445
- description: "Returns the configured ProviderConfig array with apiKey redacted by default. Set includeSecrets=true only when the user explicitly needs plaintext local provider keys; this exposes secrets from <dataDir>/providers.json.",
12446
- inputSchema: object({
12447
- includeSecrets: boolean().optional().describe("Return plaintext provider API keys. Exposes local secrets when true.")
12448
- })
12839
+ description: "Returns the configured ProviderConfig array with write-only API keys represented as configured.",
12840
+ inputSchema: object({})
12449
12841
  },
12450
- (args) => safeCall(() => listProvidersImpl(callApi, args))
12842
+ () => safeCall(() => listProvidersImpl(callApi))
12451
12843
  );
12452
12844
  server.registerTool(
12453
12845
  "inspector_get_provider",
12454
12846
  {
12455
12847
  title: "Get a single provider by id",
12456
- description: "Returns one ProviderConfig with apiKey redacted by default. Set includeSecrets=true only when the user explicitly needs the plaintext local provider key.",
12848
+ description: "Returns one ProviderConfig. API keys are write-only and represented as configured.",
12457
12849
  inputSchema: object({
12458
- id: string().describe("The provider id."),
12459
- includeSecrets: boolean().optional().describe("Return the plaintext provider API key. Exposes a local secret when true.")
12850
+ id: string().describe("The provider id.")
12460
12851
  })
12461
12852
  },
12462
- ({ id, includeSecrets }) => safeCall(() => getProviderImpl(callApi, id, { includeSecrets }))
12853
+ ({ id }) => safeCall(() => getProviderImpl(callApi, id))
12463
12854
  );
12464
12855
  server.registerTool(
12465
12856
  "inspector_replay_log",
@@ -14601,6 +14992,7 @@ const Route$u = createFileRoute("/api/providers/export")({
14601
14992
  }
14602
14993
  }
14603
14994
  });
14995
+ const ALLOW_LOOPBACK = process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1";
14604
14996
  const ProviderUpdateSchema = object({
14605
14997
  name: string().min(1, "Name is required").optional(),
14606
14998
  apiKey: string().min(1, "API key is required").optional(),
@@ -14620,26 +15012,36 @@ const ProviderUpdateSchema = object({
14620
15012
  const Route$t = createFileRoute("/api/providers/$providerId")({
14621
15013
  server: {
14622
15014
  handlers: {
14623
- GET: ({ params, request }) => {
14624
- const url = new URL(request.url);
14625
- const includeSecrets = url.searchParams.get("includeSecrets") === "1";
15015
+ GET: ({ params }) => {
14626
15016
  const providers = getProviders();
14627
15017
  const provider = providers.find((p) => p.id === params.providerId);
14628
15018
  if (!provider) {
14629
15019
  return Response.json({ error: "Provider not found" }, { status: 404 });
14630
15020
  }
14631
- return Response.json(includeSecrets ? provider : redactProvider(provider));
15021
+ return Response.json(redactProvider(provider));
14632
15022
  },
14633
15023
  PUT: async ({ params, request }) => {
14634
15024
  const parsed = ProviderUpdateSchema.safeParse(await request.json());
14635
15025
  if (!parsed.success) {
14636
15026
  return Response.json({ error: parsed.error.message }, { status: 400 });
14637
15027
  }
15028
+ for (const candidate of [
15029
+ parsed.data.baseUrl,
15030
+ parsed.data.anthropicBaseUrl,
15031
+ parsed.data.openaiBaseUrl,
15032
+ parsed.data.openaiResponsesBaseUrl
15033
+ ]) {
15034
+ if (candidate === void 0 || candidate === "") continue;
15035
+ const decision = evaluateUpstreamUrl(candidate, { allowLoopback: ALLOW_LOOPBACK });
15036
+ if (!decision.allow) {
15037
+ return Response.json({ error: decision.reason }, { status: 400 });
15038
+ }
15039
+ }
14638
15040
  const updated = updateProvider(params.providerId, parsed.data);
14639
15041
  if (!updated) {
14640
15042
  return Response.json({ error: "Provider not found" }, { status: 404 });
14641
15043
  }
14642
- return Response.json(updated);
15044
+ return Response.json(redactProvider(updated));
14643
15045
  },
14644
15046
  DELETE: ({ params }) => {
14645
15047
  const deleted = deleteProvider(params.providerId);
@@ -14725,6 +15127,8 @@ const Route$s = createFileRoute("/api/logs/stream")({
14725
15127
  }
14726
15128
  }
14727
15129
  });
15130
+ const MAX_ZIP_COMPRESSED_BYTES = 200 * 1024 * 1024;
15131
+ const MAX_ZIP_DECOMPRESSED_BYTES = 500 * 1024 * 1024;
14728
15132
  const ManifestSchema = object({
14729
15133
  version: string().optional(),
14730
15134
  exportedAt: string().optional(),
@@ -14925,12 +15329,15 @@ function legacyDraftFromParts(input) {
14925
15329
  error: null
14926
15330
  });
14927
15331
  }
14928
- async function readZipText(zip, path2) {
15332
+ async function readZipText(zip, path2, remaining) {
14929
15333
  const file = zip.file(path2);
14930
- return file === null ? null : await file.async("text");
15334
+ if (file === null) return null;
15335
+ const text = await file.async("text");
15336
+ remaining.bytes -= Buffer.byteLength(text, "utf8");
15337
+ return text;
14931
15338
  }
14932
- async function readManifest(zip) {
14933
- const text = await readZipText(zip, "manifest.json");
15339
+ async function readManifest(zip, remaining) {
15340
+ const text = await readZipText(zip, "manifest.json", remaining);
14934
15341
  const parsed = ManifestSchema.safeParse(parseJson(text));
14935
15342
  return parsed.success ? parsed.data : null;
14936
15343
  }
@@ -14976,10 +15383,19 @@ function validateManifestVersion(manifest) {
14976
15383
  }
14977
15384
  return { ok: true, warning: null };
14978
15385
  }
15386
+ function bombError(warnings = []) {
15387
+ return {
15388
+ ok: false,
15389
+ message: `Archive decompresses to more than ${MAX_ZIP_DECOMPRESSED_BYTES} bytes.`,
15390
+ warnings
15391
+ };
15392
+ }
14979
15393
  async function parseZipLogImport(data) {
14980
15394
  const warnings = [];
14981
15395
  const zip = await JSZip.loadAsync(data);
14982
- const manifest = await readManifest(zip);
15396
+ const remaining = { bytes: MAX_ZIP_DECOMPRESSED_BYTES };
15397
+ const manifest = await readManifest(zip, remaining);
15398
+ if (remaining.bytes < 0) return bombError();
14983
15399
  const versionCheck = validateManifestVersion(manifest);
14984
15400
  if (!versionCheck.ok) {
14985
15401
  return {
@@ -14995,15 +15411,21 @@ async function parseZipLogImport(data) {
14995
15411
  if (manifest?.redacted === true) {
14996
15412
  warnings.push("Imported redacted export. Secrets and some replay details may be unavailable.");
14997
15413
  }
14998
- const metadataText2 = await readZipText(zip, "logs.json");
15414
+ const metadataText2 = await readZipText(zip, "logs.json", remaining);
15415
+ if (remaining.bytes < 0) return bombError(warnings);
14999
15416
  const metadataLogs = metadataText2 === null ? null : parseJsonLogs(metadataText2);
15000
15417
  const logs = [];
15001
15418
  let skipped = 0;
15002
15419
  if (metadataLogs !== null) {
15003
15420
  for (const log of metadataLogs) {
15004
- const requestText = await readZipText(zip, `#${String(log.id)}.Request.json`);
15005
- const responseText = await readZipText(zip, `#${String(log.id)}.Response.json`);
15006
- const streamingText = await readZipText(zip, `#${String(log.id)}.SSE.Response.json`);
15421
+ const requestText = await readZipText(zip, `#${String(log.id)}.Request.json`, remaining);
15422
+ const responseText = await readZipText(zip, `#${String(log.id)}.Response.json`, remaining);
15423
+ const streamingText = await readZipText(
15424
+ zip,
15425
+ `#${String(log.id)}.SSE.Response.json`,
15426
+ remaining
15427
+ );
15428
+ if (remaining.bytes < 0) return bombError(warnings);
15007
15429
  logs.push(
15008
15430
  draftFromCapturedLog(
15009
15431
  log,
@@ -15016,9 +15438,14 @@ async function parseZipLogImport(data) {
15016
15438
  return { ok: true, logs, skipped, warnings };
15017
15439
  }
15018
15440
  for (const originalId of manifest?.logIds ?? []) {
15019
- const requestText = await readZipText(zip, `#${String(originalId)}.Request.json`);
15020
- const responseText = await readZipText(zip, `#${String(originalId)}.Response.json`);
15021
- const streamingText = await readZipText(zip, `#${String(originalId)}.SSE.Response.json`);
15441
+ const requestText = await readZipText(zip, `#${String(originalId)}.Request.json`, remaining);
15442
+ const responseText = await readZipText(zip, `#${String(originalId)}.Response.json`, remaining);
15443
+ const streamingText = await readZipText(
15444
+ zip,
15445
+ `#${String(originalId)}.SSE.Response.json`,
15446
+ remaining
15447
+ );
15448
+ if (remaining.bytes < 0) return bombError(warnings);
15022
15449
  if (requestText === null && responseText === null && streamingText === null) {
15023
15450
  skipped += 1;
15024
15451
  continue;
@@ -15073,8 +15500,22 @@ function parseTextLogImport(text) {
15073
15500
  async function parseImportedLogFile(input) {
15074
15501
  try {
15075
15502
  if (looksLikeZip(input.data, input.fileName)) {
15503
+ if (input.data.byteLength > MAX_ZIP_COMPRESSED_BYTES) {
15504
+ return {
15505
+ ok: false,
15506
+ message: `Archive exceeds maximum size of ${MAX_ZIP_COMPRESSED_BYTES} bytes.`,
15507
+ warnings: []
15508
+ };
15509
+ }
15076
15510
  return await parseZipLogImport(input.data);
15077
15511
  }
15512
+ if (input.data.byteLength > MAX_ZIP_COMPRESSED_BYTES) {
15513
+ return {
15514
+ ok: false,
15515
+ message: `Import file exceeds maximum size of ${MAX_ZIP_COMPRESSED_BYTES} bytes.`,
15516
+ warnings: []
15517
+ };
15518
+ }
15078
15519
  return parseTextLogImport(new TextDecoder("utf-8").decode(input.data));
15079
15520
  } catch (err) {
15080
15521
  return {
@@ -15352,19 +15793,25 @@ async function searchOpenClawGateway(query, project) {
15352
15793
  warning: "OpenClaw Gateway is not configured. Set OPENCLAW_GATEWAY_URL."
15353
15794
  };
15354
15795
  }
15355
- const response = await fetch(buildToolInvokeUrl(gatewayUrl), {
15356
- method: "POST",
15357
- headers: gatewayHeaders(),
15358
- body: JSON.stringify({
15359
- tool: "memory_search",
15360
- args: {
15361
- query: buildSearchQuery(query, project),
15362
- maxResults: 10,
15363
- corpus: "all"
15364
- },
15365
- sessionKey: getOpenClawSessionKey()
15366
- })
15367
- });
15796
+ const fetchResult = await safeFetch(
15797
+ buildToolInvokeUrl(gatewayUrl),
15798
+ {
15799
+ method: "POST",
15800
+ headers: gatewayHeaders(),
15801
+ body: JSON.stringify({
15802
+ tool: "memory_search",
15803
+ args: {
15804
+ query: buildSearchQuery(query, project),
15805
+ maxResults: 10,
15806
+ corpus: "all"
15807
+ },
15808
+ sessionKey: getOpenClawSessionKey()
15809
+ })
15810
+ },
15811
+ { bypass: true }
15812
+ );
15813
+ if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
15814
+ const response = fetchResult.response;
15368
15815
  const raw = await response.json().catch(() => null);
15369
15816
  if (!response.ok) {
15370
15817
  return {
@@ -15619,11 +16066,17 @@ async function promoteToOpenClaw(candidate) {
15619
16066
  error: "OpenClaw memory backend is not configured. Set OPENCLAW_WORKSPACE_DIR for file-backed memory or OPENCLAW_MEMORY_URL for a legacy HTTP backend."
15620
16067
  };
15621
16068
  }
15622
- const response = await fetch(buildMemoryUrl(endpoint), {
15623
- method: "POST",
15624
- headers: authHeaders(),
15625
- body: JSON.stringify(buildOpenClawPayload(candidate))
15626
- });
16069
+ const fetchResult = await safeFetch(
16070
+ buildMemoryUrl(endpoint),
16071
+ {
16072
+ method: "POST",
16073
+ headers: authHeaders(),
16074
+ body: JSON.stringify(buildOpenClawPayload(candidate))
16075
+ },
16076
+ { bypass: true }
16077
+ );
16078
+ if (!fetchResult.ok) return { success: false, error: fetchResult.reason };
16079
+ const response = fetchResult.response;
15627
16080
  if (!response.ok) {
15628
16081
  return { success: false, error: `OpenClaw memory write failed with ${response.status}` };
15629
16082
  }
@@ -15646,10 +16099,16 @@ async function searchOpenClaw(query, project) {
15646
16099
  warning: `OpenClaw search backend is not configured. Set OPENCLAW_GATEWAY_URL or OPENCLAW_MEMORY_URL.${workspaceHint}`
15647
16100
  };
15648
16101
  }
15649
- const response = await fetch(buildSearchUrl(endpoint, query, project), {
15650
- method: "GET",
15651
- headers: authHeaders()
15652
- });
16102
+ const fetchResult = await safeFetch(
16103
+ buildSearchUrl(endpoint, query, project),
16104
+ {
16105
+ method: "GET",
16106
+ headers: authHeaders()
16107
+ },
16108
+ { bypass: true }
16109
+ );
16110
+ if (!fetchResult.ok) return { results: [], warning: fetchResult.reason };
16111
+ const response = fetchResult.response;
15653
16112
  if (!response.ok) {
15654
16113
  return { results: [], warning: `OpenClaw search failed with ${response.status}` };
15655
16114
  }
@@ -16770,12 +17229,20 @@ async function testEndpoint(baseUrl, provider, path2, model, protocol) {
16770
17229
  const timeoutId = setTimeout(() => controller.abort(), getProviderTestTimeoutMs());
16771
17230
  try {
16772
17231
  requestUrl = buildUpstreamUrl(baseUrl, path2);
16773
- const response = await fetch(requestUrl, {
16774
- method: "POST",
16775
- headers: requestHeaders,
16776
- body,
16777
- signal: controller.signal
16778
- });
17232
+ const fetchResult = await safeFetch(
17233
+ requestUrl,
17234
+ {
17235
+ method: "POST",
17236
+ headers: requestHeaders,
17237
+ body,
17238
+ signal: controller.signal
17239
+ },
17240
+ { allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
17241
+ );
17242
+ if (!fetchResult.ok) {
17243
+ return createErrorResult(fetchResult.reason, Date.now() - startTime, false);
17244
+ }
17245
+ const response = fetchResult.response;
16779
17246
  const latencyMs = Date.now() - startTime;
16780
17247
  if (!response.ok) {
16781
17248
  const responseText2 = await readResponseText(response);
@@ -16894,12 +17361,20 @@ async function testStreamingEndpoint(baseUrl, provider, path2, model, protocol)
16894
17361
  const timeoutId = setTimeout(() => controller.abort(), getProviderTestTimeoutMs());
16895
17362
  try {
16896
17363
  requestUrl = buildUpstreamUrl(baseUrl, path2);
16897
- const response = await fetch(requestUrl, {
16898
- method: "POST",
16899
- headers: requestHeaders,
16900
- body,
16901
- signal: controller.signal
16902
- });
17364
+ const fetchResult = await safeFetch(
17365
+ requestUrl,
17366
+ {
17367
+ method: "POST",
17368
+ headers: requestHeaders,
17369
+ body,
17370
+ signal: controller.signal
17371
+ },
17372
+ { allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
17373
+ );
17374
+ if (!fetchResult.ok) {
17375
+ return createErrorResult(fetchResult.reason, Date.now() - startTime, true);
17376
+ }
17377
+ const response = fetchResult.response;
16903
17378
  const latencyMs = Date.now() - startTime;
16904
17379
  if (!response.ok) {
16905
17380
  const responseText = await readResponseText(response);
@@ -17281,7 +17756,16 @@ const Route$f = createFileRoute("/api/providers/$providerId/model-metadata")({
17281
17756
  }
17282
17757
  let response;
17283
17758
  try {
17284
- response = await fetch(sourceUrl, { headers: { Accept: "application/json" } });
17759
+ const fetchResult = await safeFetch(sourceUrl, {
17760
+ headers: { Accept: "application/json" }
17761
+ });
17762
+ if (!fetchResult.ok) {
17763
+ return Response.json(
17764
+ { error: `Model metadata URL blocked: ${fetchResult.reason}` },
17765
+ { status: 400 }
17766
+ );
17767
+ }
17768
+ response = fetchResult.response;
17285
17769
  } catch (err) {
17286
17770
  return Response.json(
17287
17771
  { error: `Failed to fetch model metadata: ${String(err)}` },
@@ -17393,11 +17877,22 @@ const Route$e = createFileRoute("/api/logs/$id/replay")({
17393
17877
  const startTime = Date.now();
17394
17878
  let upstreamRes;
17395
17879
  try {
17396
- upstreamRes = await fetch(upstreamUrl, {
17397
- method: "POST",
17398
- headers,
17399
- body: modifiedBody
17400
- });
17880
+ const fetchResult = await safeFetch(
17881
+ upstreamUrl,
17882
+ {
17883
+ method: "POST",
17884
+ headers,
17885
+ body: modifiedBody
17886
+ },
17887
+ { allowLoopback: process.env["AGENT_INSPECTOR_ALLOW_LOOPBACK"] === "1" }
17888
+ );
17889
+ if (!fetchResult.ok) {
17890
+ return Response.json({
17891
+ success: false,
17892
+ error: fetchResult.reason
17893
+ });
17894
+ }
17895
+ upstreamRes = fetchResult.response;
17401
17896
  } catch (err) {
17402
17897
  return Response.json({
17403
17898
  success: false,
@@ -18778,13 +19273,13 @@ const router = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProper
18778
19273
  export {
18779
19274
  PiAgentResponseSchema as $,
18780
19275
  AnthropicResponseSchema$1 as A,
18781
- GroupEvidenceExportResultSchema as B,
19276
+ DeleteInspectorGroupsResponseSchema as B,
18782
19277
  CapturedLogSchema as C,
18783
19278
  DEFAULT_CAPTURE_MODE as D,
18784
- DeleteInspectorGroupResponseSchema as E,
18785
- providerHasContextMetadata as F,
19279
+ GroupEvidenceExportResultSchema as E,
19280
+ DeleteInspectorGroupResponseSchema as F,
18786
19281
  GroupEvidenceReadResponseSchema as G,
18787
- maskApiKey as H,
19282
+ providerHasContextMetadata as H,
18788
19283
  InspectorGroupsListResponseSchema as I,
18789
19284
  findProviderModelMetadata as J,
18790
19285
  KnowledgeCandidateSchema as K,
@@ -18817,22 +19312,22 @@ export {
18817
19312
  persistThemeMode as f,
18818
19313
  readStoredUiScalePreference as g,
18819
19314
  persistUiScalePreference as h,
18820
- applyUiScalePreference as i,
18821
- normalizeUiScalePreference as j,
18822
- AnthropicRequestSchema as k,
18823
- parseOpenAIResponse as l,
18824
- parseOpenAIResponsesResponse as m,
19315
+ isRecord as i,
19316
+ applyUiScalePreference as j,
19317
+ normalizeUiScalePreference as k,
19318
+ AnthropicRequestSchema as l,
19319
+ parseOpenAIResponse as m,
18825
19320
  normalizeThemeMode as n,
18826
- OpenAIResponsesRequestSchema as o,
19321
+ parseOpenAIResponsesResponse as o,
18827
19322
  packageJson as p,
18828
- apiFormatForPath as q,
19323
+ OpenAIResponsesRequestSchema as q,
18829
19324
  readStoredThemeMode as r,
18830
19325
  safeGetOwnProperty as s,
18831
- getSessionPath as t,
18832
- stripClaudeCodeBillingHeader as u,
18833
- isTurnBoundary as v,
18834
- extractStopReason as w,
18835
- AlertSummarySchema as x,
18836
- AlertListResponseSchema as y,
18837
- DeleteInspectorGroupsResponseSchema as z
19326
+ apiFormatForPath as t,
19327
+ getSessionPath as u,
19328
+ stripClaudeCodeBillingHeader as v,
19329
+ isTurnBoundary as w,
19330
+ extractStopReason as x,
19331
+ AlertSummarySchema as y,
19332
+ AlertListResponseSchema as z
18838
19333
  };