@tangle-network/agent-runtime 0.94.1 → 0.94.2

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 (44) hide show
  1. package/dist/agent.js +3 -3
  2. package/dist/{agentic-generator-D9ZmAuiL.d.ts → agentic-generator-B7Cyl6cn.d.ts} +1 -1
  3. package/dist/{chunk-HPCJBBLS.js → chunk-7ESP4C3O.js} +2 -2
  4. package/dist/{chunk-K7OPNHU4.js → chunk-DNK3L7QB.js} +2 -2
  5. package/dist/{chunk-PJNGPWTA.js → chunk-JFAGXZKR.js} +383 -67
  6. package/dist/chunk-JFAGXZKR.js.map +1 -0
  7. package/dist/{chunk-D336OZHK.js → chunk-L4CACVIJ.js} +2 -2
  8. package/dist/{chunk-JEP2WM24.js → chunk-OAVVQAOQ.js} +2 -2
  9. package/dist/{chunk-RTSZNYXM.js → chunk-PV6Q3OKU.js} +3 -3
  10. package/dist/{chunk-URMJTSXB.js → chunk-TTWMSNFZ.js} +14 -2
  11. package/dist/chunk-TTWMSNFZ.js.map +1 -0
  12. package/dist/{chunk-SJHFJOCY.js → chunk-ZLPKMFOE.js} +2 -2
  13. package/dist/{completion-gate-BAj3dXeW.d.ts → completion-gate-Bz7VYs7a.d.ts} +9 -1
  14. package/dist/{coordination-B3ZuApR_.d.ts → coordination-CdU8LyvB.d.ts} +4 -2
  15. package/dist/{improve-r8SvIxYv.d.ts → improve-mrYmy_1e.d.ts} +2 -2
  16. package/dist/index.d.ts +10 -10
  17. package/dist/index.js +8 -8
  18. package/dist/intelligence.d.ts +3 -3
  19. package/dist/intelligence.js +3 -3
  20. package/dist/knowledge.d.ts +4 -4
  21. package/dist/knowledge.js +3 -3
  22. package/dist/lifecycle.d.ts +3 -3
  23. package/dist/lifecycle.js +2 -2
  24. package/dist/{local-harness-B7bNuNJw.d.ts → local-harness-C2HDGiOY.d.ts} +16 -0
  25. package/dist/{loop-runner-bin-D4bMIxmB.d.ts → loop-runner-bin-BSvtOPMv.d.ts} +1 -1
  26. package/dist/loop-runner-bin.d.ts +4 -4
  27. package/dist/loop-runner-bin.js +4 -4
  28. package/dist/loops.d.ts +9 -9
  29. package/dist/loops.js +3 -3
  30. package/dist/mcp/bin.js +2 -2
  31. package/dist/mcp/index.d.ts +6 -6
  32. package/dist/mcp/index.js +4 -4
  33. package/dist/{mcp-serve-verifier-BuXjaZDE.d.ts → mcp-serve-verifier-C65X30mY.d.ts} +1 -1
  34. package/dist/{supervise-DTNcLDl2.d.ts → supervise-U6DTnQu6.d.ts} +2 -2
  35. package/dist/{worktree-fanout-IMKqdV_E.d.ts → worktree-fanout-DqzIBWMW.d.ts} +5 -2
  36. package/package.json +1 -1
  37. package/dist/chunk-PJNGPWTA.js.map +0 -1
  38. package/dist/chunk-URMJTSXB.js.map +0 -1
  39. /package/dist/{chunk-HPCJBBLS.js.map → chunk-7ESP4C3O.js.map} +0 -0
  40. /package/dist/{chunk-K7OPNHU4.js.map → chunk-DNK3L7QB.js.map} +0 -0
  41. /package/dist/{chunk-D336OZHK.js.map → chunk-L4CACVIJ.js.map} +0 -0
  42. /package/dist/{chunk-JEP2WM24.js.map → chunk-OAVVQAOQ.js.map} +0 -0
  43. /package/dist/{chunk-RTSZNYXM.js.map → chunk-PV6Q3OKU.js.map} +0 -0
  44. /package/dist/{chunk-SJHFJOCY.js.map → chunk-ZLPKMFOE.js.map} +0 -0
@@ -1,10 +1,22 @@
1
1
  // src/mcp/local-harness.ts
2
2
  import { spawn } from "child_process";
3
- import { createHash } from "crypto";
4
- import { constants } from "fs";
5
- import { access, chmod, mkdtemp, rm, symlink, writeFile } from "fs/promises";
3
+ import { createHash, randomUUID } from "crypto";
4
+ import { constants, createReadStream } from "fs";
5
+ import {
6
+ access,
7
+ chmod,
8
+ copyFile,
9
+ mkdir,
10
+ mkdtemp,
11
+ open,
12
+ readFile,
13
+ realpath,
14
+ rm,
15
+ symlink,
16
+ writeFile
17
+ } from "fs/promises";
6
18
  import { homedir, tmpdir } from "os";
7
- import { basename, join } from "path";
19
+ import { basename, delimiter, dirname, isAbsolute, join, resolve, sep } from "path";
8
20
  var codexReasoningEffort = {
9
21
  none: "none",
10
22
  minimal: "minimal",
@@ -117,6 +129,7 @@ function harnessInvocation(harness, profile, taskPrompt, options = {}) {
117
129
  return { command: invocation.command, args };
118
130
  }
119
131
  var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
132
+ var processKillGraceMs = 250;
120
133
  async function runLocalHarness(options) {
121
134
  const { harness, cwd, taskPrompt } = options;
122
135
  if (options.codexReproducible && harness !== "codex") {
@@ -125,6 +138,9 @@ async function runLocalHarness(options) {
125
138
  if (options.codexReproducible && process.platform !== "linux") {
126
139
  throw new Error("runLocalHarness: codexReproducible currently requires Linux");
127
140
  }
141
+ if (options.codexReadDeniedPaths !== void 0 && !options.codexReproducible) {
142
+ throw new Error("runLocalHarness: codexReadDeniedPaths requires codexReproducible");
143
+ }
128
144
  const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
129
145
  const spawnImpl = options.spawn ?? spawn;
130
146
  const invocation = HARNESS_INVOCATIONS[harness];
@@ -132,19 +148,34 @@ async function runLocalHarness(options) {
132
148
  throw new Error(`runLocalHarness: unknown harness ${String(harness)}`);
133
149
  }
134
150
  const startedAt = Date.now();
135
- const command = options.invocation?.command ?? invocation.command;
151
+ const requestedCommand = options.invocation?.command ?? invocation.command;
136
152
  const args = options.invocation ? [...options.invocation.args] : buildHarnessArgs(harness, taskPrompt, options);
137
- if (options.codexReproducible) assertCodexReproducibleInvocation(command, args);
153
+ if (options.codexReproducible) assertCodexReproducibleInvocation(requestedCommand, args);
138
154
  const baseEnv = options.env ?? process.env;
139
- const isolated = options.codexReproducible ? await isolateCodexHome(baseEnv, cwd) : {
155
+ const isolated = options.codexReproducible ? await isolateCodexHome({
156
+ baseEnv,
157
+ cwd,
158
+ command: requestedCommand,
159
+ readDeniedPaths: options.codexReadDeniedPaths ?? [],
160
+ resolveExecutable: options.resolveCodexExecutable ?? resolveCodexNativeExecutable
161
+ }) : {
140
162
  env: baseEnv,
163
+ command: requestedCommand,
141
164
  cleanup: async () => void 0,
142
165
  controlledConfigSha256: "",
166
+ executableSha256: "",
167
+ readDeniedPathsSha256: "",
168
+ readDeniedPathCount: 0,
169
+ readDeniedPaths: [],
143
170
  ambientAuth: "",
144
171
  isolatedAuth: "",
145
- writeProbe: ""
172
+ writeProbe: "",
173
+ stagedWriteProbe: "",
174
+ deniedProbePaths: []
146
175
  };
147
176
  const env = isolated.env;
177
+ const command = isolated.command;
178
+ if (options.codexReproducible) assertCodexReproducibleInvocation(command, args);
148
179
  try {
149
180
  const evidence = options.codexReproducible ? await collectCodexExecutionEvidence({
150
181
  command,
@@ -153,15 +184,26 @@ async function runLocalHarness(options) {
153
184
  env,
154
185
  spawn: spawnImpl,
155
186
  controlledConfigSha256: isolated.controlledConfigSha256,
187
+ executableSha256: isolated.executableSha256,
188
+ readDeniedPathsSha256: isolated.readDeniedPathsSha256,
189
+ readDeniedPathCount: isolated.readDeniedPathCount,
190
+ readDeniedPaths: isolated.readDeniedPaths,
156
191
  ambientAuth: isolated.ambientAuth,
157
192
  isolatedAuth: isolated.isolatedAuth,
158
193
  writeProbe: isolated.writeProbe,
194
+ stagedWriteProbe: isolated.stagedWriteProbe,
195
+ deniedProbePaths: isolated.deniedProbePaths,
159
196
  ...options.signal ? { signal: options.signal } : {}
160
197
  }) : void 0;
161
- return await new Promise((resolve, reject) => {
198
+ return await new Promise((resolve2, reject) => {
162
199
  let child;
163
200
  try {
164
- child = spawnImpl(command, args, { cwd, env, stdio: "pipe" });
201
+ child = spawnImpl(command, args, {
202
+ cwd,
203
+ env,
204
+ stdio: "pipe",
205
+ detached: process.platform !== "win32"
206
+ });
165
207
  } catch (err) {
166
208
  reject(err instanceof Error ? err : new Error(String(err)));
167
209
  return;
@@ -171,16 +213,25 @@ async function runLocalHarness(options) {
171
213
  let stderr = "";
172
214
  let timedOut = false;
173
215
  let settled = false;
216
+ let forceKillTimer = null;
217
+ let terminationStarted = false;
218
+ const terminate = () => {
219
+ if (terminationStarted) return;
220
+ terminationStarted = true;
221
+ signalProcessTree(child, "SIGTERM");
222
+ forceKillTimer = setTimeout(() => signalProcessTree(child, "SIGKILL"), processKillGraceMs);
223
+ forceKillTimer.unref?.();
224
+ };
174
225
  const timer = timeoutMs > 0 ? setTimeout(() => {
175
226
  timedOut = true;
176
- if (!child.killed) child.kill("SIGTERM");
227
+ terminate();
177
228
  }, timeoutMs) : null;
178
229
  if (timer && typeof timer.unref === "function") {
179
230
  ;
180
231
  timer.unref();
181
232
  }
182
233
  const onAbort = () => {
183
- if (!child.killed) child.kill("SIGTERM");
234
+ terminate();
184
235
  };
185
236
  if (options.signal) {
186
237
  if (options.signal.aborted) onAbort();
@@ -196,13 +247,15 @@ async function runLocalHarness(options) {
196
247
  if (settled) return;
197
248
  settled = true;
198
249
  if (timer) clearTimeout(timer);
250
+ if (forceKillTimer) clearTimeout(forceKillTimer);
199
251
  options.signal?.removeEventListener("abort", onAbort);
200
- resolve(result);
252
+ resolve2(result);
201
253
  };
202
254
  child.on("error", (err) => {
203
255
  if (settled) return;
204
256
  settled = true;
205
257
  if (timer) clearTimeout(timer);
258
+ if (forceKillTimer) clearTimeout(forceKillTimer);
206
259
  options.signal?.removeEventListener("abort", onAbort);
207
260
  reject(err);
208
261
  });
@@ -223,6 +276,7 @@ async function runLocalHarness(options) {
223
276
  if (settled) return;
224
277
  settled = true;
225
278
  if (timer) clearTimeout(timer);
279
+ if (forceKillTimer) clearTimeout(forceKillTimer);
226
280
  options.signal?.removeEventListener("abort", onAbort);
227
281
  reject(err instanceof Error ? err : new Error(String(err)));
228
282
  }
@@ -250,9 +304,14 @@ var CODEX_EXECUTION_POLICY = {
250
304
  shellEnvironment: "core-filtered",
251
305
  loginShell: false,
252
306
  credentialsReadable: false,
307
+ hostHomeReadable: false,
308
+ procEnvironment: "private-sanitized",
309
+ sensitiveEnvironmentNamesVisible: false,
253
310
  parentRepoRead: false,
254
311
  gitMetadata: false,
255
312
  temporaryDirectory: "workspace-private",
313
+ stagedExecutable: "static-elf-read-only",
314
+ callerReadDeniedPaths: "enforced",
256
315
  containerSockets: false
257
316
  };
258
317
  var FORBIDDEN_CODEX_PROMPT_MARKERS = [
@@ -284,11 +343,16 @@ async function collectCodexExecutionEvidence(opts) {
284
343
  const sandboxProof = [
285
344
  hiddenEnvironmentChecks,
286
345
  "test -r .",
346
+ "! test -r .git",
287
347
  'touch "$1"',
288
348
  'rm "$1"',
289
- '! test -r "$2"',
290
- '! test -r "$3"',
291
- '! test -r "$4"',
349
+ 'staged_dir=$(dirname "$2")',
350
+ 'test -x "$staged_dir/codex"',
351
+ '! chmod 700 "$staged_dir" 2>/dev/null',
352
+ '! rm "$staged_dir/codex" 2>/dev/null',
353
+ '! touch "$2" 2>/dev/null',
354
+ "shift 2",
355
+ 'for path in "$@"; do ! test -r "$path" || exit 1; done',
292
356
  'touch "$TMPDIR/.agent-runtime-tmp-proof"',
293
357
  'rm "$TMPDIR/.agent-runtime-tmp-proof"'
294
358
  ].join(" && ");
@@ -303,9 +367,24 @@ async function collectCodexExecutionEvidence(opts) {
303
367
  sandboxProof,
304
368
  "sh",
305
369
  opts.writeProbe,
306
- opts.ambientAuth,
307
- opts.isolatedAuth,
308
- join(homedir(), "code")
370
+ opts.stagedWriteProbe,
371
+ ...opts.deniedProbePaths
372
+ ]);
373
+ await runCodexProbe(opts, [
374
+ "sandbox",
375
+ "-P",
376
+ CODEX_PERMISSION_PROFILE,
377
+ "-C",
378
+ opts.cwd,
379
+ "python3",
380
+ "-c",
381
+ `import os,re
382
+ pattern=re.compile(br'(TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL|AUTH|COOKIE|CODEX_HOME)', re.I)
383
+ paths=('/proc/1/environ', '/proc/self/environ', f'/proc/{os.getppid()}/environ')
384
+ for path in dict.fromkeys(paths):
385
+ data=open(path,'rb').read().split(b'\\0')
386
+ names=(item.split(b'=',1)[0] for item in data if b'=' in item)
387
+ assert not any(pattern.search(name) for name in names), path`
309
388
  ]);
310
389
  await runCodexProbe(opts, [
311
390
  "sandbox",
@@ -344,14 +423,18 @@ async function collectCodexExecutionEvidence(opts) {
344
423
  }
345
424
  }
346
425
  const nonPromptArgs = [
347
- opts.command,
426
+ "codex",
348
427
  ...opts.args.map((arg, index) => index === 1 ? "<PROMPT>" : arg)
349
428
  ];
350
429
  return {
351
430
  cliVersion,
431
+ executableSha256: opts.executableSha256,
352
432
  effectivePromptSha256: sha256(renderedPrompt),
353
433
  nonPromptArgsSha256: sha256(JSON.stringify(nonPromptArgs)),
354
434
  controlledConfigSha256: opts.controlledConfigSha256,
435
+ readDeniedPathsSha256: opts.readDeniedPathsSha256,
436
+ readDeniedPathCount: opts.readDeniedPathCount,
437
+ readDeniedPaths: [...opts.readDeniedPaths],
355
438
  policy: { ...CODEX_EXECUTION_POLICY }
356
439
  };
357
440
  }
@@ -395,10 +478,15 @@ function codexPromptDebugArgs(execArgs, prompt) {
395
478
  return debugArgs;
396
479
  }
397
480
  function runCodexProbe(opts, args) {
398
- return new Promise((resolve, reject) => {
481
+ return new Promise((resolve2, reject) => {
399
482
  let child;
400
483
  try {
401
- child = opts.spawn(opts.command, args, { cwd: opts.cwd, env: opts.env, stdio: "pipe" });
484
+ child = opts.spawn(opts.command, args, {
485
+ cwd: opts.cwd,
486
+ env: opts.env,
487
+ stdio: "pipe",
488
+ detached: process.platform !== "win32"
489
+ });
402
490
  } catch (err) {
403
491
  reject(err instanceof Error ? err : new Error(String(err)));
404
492
  return;
@@ -407,12 +495,19 @@ function runCodexProbe(opts, args) {
407
495
  let stdout = "";
408
496
  let stderr = "";
409
497
  let settled = false;
498
+ let forceKillTimer = null;
410
499
  const timer = setTimeout(() => {
411
- if (!child.killed) child.kill("SIGTERM");
500
+ signalProcessTree(child, "SIGTERM");
501
+ forceKillTimer = setTimeout(() => signalProcessTree(child, "SIGKILL"), processKillGraceMs);
502
+ forceKillTimer.unref?.();
412
503
  }, 1e4);
413
504
  timer.unref?.();
414
505
  const onAbort = () => {
415
- if (!child.killed) child.kill("SIGTERM");
506
+ signalProcessTree(child, "SIGTERM");
507
+ if (!forceKillTimer) {
508
+ forceKillTimer = setTimeout(() => signalProcessTree(child, "SIGKILL"), processKillGraceMs);
509
+ forceKillTimer.unref?.();
510
+ }
416
511
  };
417
512
  if (opts.signal) {
418
513
  if (opts.signal.aborted) onAbort();
@@ -428,6 +523,7 @@ function runCodexProbe(opts, args) {
428
523
  if (settled) return;
429
524
  settled = true;
430
525
  clearTimeout(timer);
526
+ if (forceKillTimer) clearTimeout(forceKillTimer);
431
527
  opts.signal?.removeEventListener("abort", onAbort);
432
528
  reject(err);
433
529
  });
@@ -435,6 +531,7 @@ function runCodexProbe(opts, args) {
435
531
  if (settled) return;
436
532
  settled = true;
437
533
  clearTimeout(timer);
534
+ if (forceKillTimer) clearTimeout(forceKillTimer);
438
535
  opts.signal?.removeEventListener("abort", onAbort);
439
536
  if (code !== 0) {
440
537
  const safeStderr = redactCodexHome(stderr, opts.env.CODEX_HOME);
@@ -445,7 +542,7 @@ function runCodexProbe(opts, args) {
445
542
  );
446
543
  return;
447
544
  }
448
- resolve({ stdout, stderr });
545
+ resolve2({ stdout, stderr });
449
546
  });
450
547
  });
451
548
  }
@@ -472,51 +569,120 @@ function redactJsonText(value, text) {
472
569
  return value;
473
570
  }
474
571
  var CODEX_AUTH_ENV = ["CODEX_ACCESS_TOKEN", "CODEX_API_KEY", "OPENAI_API_KEY"];
572
+ var sensitiveEnvironmentName = /(TOKEN|KEY|SECRET|PASSWORD|CREDENTIAL|AUTH|COOKIE)/i;
475
573
  var CODEX_PERMISSION_PROFILE = "agent_runtime_reproducible";
476
- async function isolateCodexHome(baseEnv, cwd) {
574
+ async function isolateCodexHome(opts) {
477
575
  const isolatedHome = await mkdtemp(join(tmpdir(), "agent-runtime-codex-"));
478
576
  await chmod(isolatedHome, 448);
479
577
  let workspaceTemp = "";
480
- const writeProbe = join(cwd, ".agent-runtime-sandbox-write-proof");
578
+ let stagedExecutableDir = "";
579
+ let ownsStagedExecutableDir = false;
580
+ let writeProbe = "";
481
581
  const cleanup = async () => {
582
+ if (ownsStagedExecutableDir) {
583
+ await chmod(stagedExecutableDir, 448).catch(() => void 0);
584
+ }
482
585
  await Promise.all([
483
- rm(writeProbe, { force: true }),
586
+ ...writeProbe ? [rm(writeProbe, { force: true })] : [],
484
587
  ...workspaceTemp ? [rm(workspaceTemp, { recursive: true, force: true })] : [],
588
+ ...ownsStagedExecutableDir ? [rm(stagedExecutableDir, { recursive: true, force: true })] : [],
485
589
  rm(isolatedHome, { recursive: true, force: true })
486
590
  ]);
487
591
  };
488
592
  try {
489
- workspaceTemp = await mkdtemp(join(cwd, ".agent-runtime-tmp-"));
593
+ const readDeniedPaths = await normalizeReadDeniedPaths(opts.readDeniedPaths);
594
+ const candidateRoot = await realpath(opts.cwd);
595
+ writeProbe = join(candidateRoot, `.agent-runtime-write-proof-${randomUUID()}`);
596
+ const stagedPath = join(candidateRoot, ".agent-runtime-bin");
597
+ if (readDeniedPaths.some(
598
+ (path) => isSameOrDescendant(candidateRoot, path) || isSameOrDescendant(stagedPath, path) || isSameOrDescendant(path, stagedPath)
599
+ )) {
600
+ throw new Error(
601
+ "runLocalHarness: codexReadDeniedPaths cannot contain the candidate root or staged executable"
602
+ );
603
+ }
604
+ const nativeExecutable = await opts.resolveExecutable(opts.command, opts.baseEnv);
605
+ if (!await isStaticElfExecutable(nativeExecutable)) {
606
+ throw new Error(
607
+ "runLocalHarness: reproducible Codex mode requires a statically linked Linux Codex ELF"
608
+ );
609
+ }
610
+ const sourceExecutableSha256 = await sha256File(nativeExecutable);
611
+ stagedExecutableDir = stagedPath;
612
+ try {
613
+ await mkdir(stagedExecutableDir, { mode: 448 });
614
+ ownsStagedExecutableDir = true;
615
+ } catch (err) {
616
+ throw new Error(
617
+ `runLocalHarness: candidate-private .agent-runtime-bin must not already exist: ${String(err.code ?? err)}`
618
+ );
619
+ }
620
+ const stagedExecutable = join(stagedExecutableDir, "codex");
621
+ await copyFile(nativeExecutable, stagedExecutable, constants.COPYFILE_FICLONE);
622
+ await chmod(stagedExecutable, 320);
623
+ const executableSha256 = await sha256File(stagedExecutable);
624
+ if (executableSha256 !== sourceExecutableSha256 || !await isStaticElfExecutable(stagedExecutable)) {
625
+ throw new Error("runLocalHarness: staged Codex executable digest mismatch");
626
+ }
627
+ await chmod(stagedExecutableDir, 320);
628
+ workspaceTemp = await mkdtemp(join(candidateRoot, ".agent-runtime-tmp-"));
490
629
  await chmod(workspaceTemp, 448);
491
- const ambientHome = baseEnv.CODEX_HOME?.trim() || join(homedir(), ".codex");
630
+ const ambientHome = resolve(opts.baseEnv.CODEX_HOME?.trim() || join(homedir(), ".codex"));
492
631
  const ambientAuth = join(ambientHome, "auth.json");
493
632
  const isolatedAuth = join(isolatedHome, "auth.json");
494
- let linkedAuth = false;
495
633
  try {
496
634
  await access(ambientAuth, constants.R_OK);
497
635
  await symlink(ambientAuth, isolatedAuth);
498
- linkedAuth = true;
499
636
  } catch (err) {
500
- if (err.code !== "ENOENT") throw err;
501
- }
502
- if (!linkedAuth && !CODEX_AUTH_ENV.some((name) => Boolean(baseEnv[name]))) {
503
637
  throw new Error(
504
- "runLocalHarness: reproducible Codex mode requires readable auth.json or an API token environment variable"
638
+ `runLocalHarness: reproducible Codex mode requires readable auth.json: ${String(err.code ?? err)}`
505
639
  );
506
640
  }
641
+ const deniedHostRoots = [homedir(), opts.baseEnv.HOME?.trim(), ambientHome].filter((path) => Boolean(path)).map((path) => resolve(path)).filter((path, index, paths) => paths.indexOf(path) === index);
507
642
  const controlledConfig = codexControlledConfig({
508
- ambientHome,
509
- ambientTmp: baseEnv.TMPDIR?.trim() || tmpdir(),
510
- workspaceTemp
643
+ ambientTmp: opts.baseEnv.TMPDIR?.trim() || tmpdir(),
644
+ deniedHostRoots,
645
+ workspaceTemp,
646
+ stagedExecutableDir,
647
+ readDeniedPaths
511
648
  });
512
649
  await writeFile(join(isolatedHome, "config.toml"), controlledConfig, { mode: 384 });
650
+ const env = { ...opts.baseEnv };
651
+ for (const name of Object.keys(env)) {
652
+ if (sensitiveEnvironmentName.test(name) || CODEX_AUTH_ENV.some((authName) => authName === name)) {
653
+ delete env[name];
654
+ }
655
+ }
656
+ env.CODEX_HOME = isolatedHome;
657
+ const home = homedir();
658
+ const deniedProbePaths = [
659
+ ...deniedHostRoots,
660
+ ambientAuth,
661
+ isolatedAuth,
662
+ join(home, ".claude", ".credentials.json"),
663
+ join(home, ".claude.json"),
664
+ join(home, ".local", "share", "opencode", "auth.json"),
665
+ join(home, ".agents"),
666
+ join(home, "Documents"),
667
+ join(home, "Downloads"),
668
+ join(home, ".npm"),
669
+ join(home, "code"),
670
+ ...readDeniedPaths
671
+ ].filter((path, index, paths) => paths.indexOf(path) === index);
513
672
  return {
514
- env: { ...baseEnv, CODEX_HOME: isolatedHome },
673
+ env,
674
+ command: stagedExecutable,
515
675
  cleanup,
516
676
  controlledConfigSha256: sha256(controlledConfig),
677
+ executableSha256,
678
+ readDeniedPaths,
679
+ readDeniedPathsSha256: sha256(JSON.stringify(readDeniedPaths)),
680
+ readDeniedPathCount: readDeniedPaths.length,
517
681
  ambientAuth,
518
682
  isolatedAuth,
519
- writeProbe
683
+ writeProbe,
684
+ stagedWriteProbe: join(stagedExecutableDir, "write-proof"),
685
+ deniedProbePaths
520
686
  };
521
687
  } catch (err) {
522
688
  await cleanup();
@@ -524,30 +690,26 @@ async function isolateCodexHome(baseEnv, cwd) {
524
690
  }
525
691
  }
526
692
  function codexControlledConfig(opts) {
527
- const home = homedir();
528
- const deniedPaths = [
529
- opts.ambientHome,
693
+ const rules = /* @__PURE__ */ new Map();
694
+ const broadDeniedPaths = minimalPathRoots([
695
+ ...opts.deniedHostRoots.map((path) => resolve(path)),
696
+ tmpdir(),
530
697
  opts.ambientTmp,
531
- join(home, "code"),
532
- join(home, "company"),
533
- join(home, ".ssh"),
534
- join(home, ".aws"),
535
- join(home, ".config"),
536
- join(home, ".cache"),
537
- join(home, ".docker"),
538
- join(home, ".kube"),
539
- join(home, ".gnupg"),
540
- join(home, ".pki"),
541
- join(home, ".npmrc"),
542
- join(home, ".netrc"),
543
- join(home, ".git-credentials"),
698
+ "/proc",
544
699
  "/run/docker",
545
- "/run/containerd",
546
- opts.workspaceTemp
547
- ];
548
- const filesystemRules = deniedPaths.map(
549
- (path) => `${JSON.stringify(path)} = ${path === opts.workspaceTemp ? '"write"' : '"deny"'}`
550
- ).join("\n");
700
+ "/run/containerd"
701
+ ]);
702
+ for (const path of broadDeniedPaths) {
703
+ rules.set(path, "deny");
704
+ }
705
+ const workspaceRoot = dirname(opts.workspaceTemp);
706
+ for (const path of opts.readDeniedPaths) {
707
+ const alreadyDenied = broadDeniedPaths.some((root) => isSameOrDescendant(path, root));
708
+ if (!alreadyDenied || isSameOrDescendant(path, workspaceRoot)) rules.set(path, "deny");
709
+ }
710
+ if (!rules.has(opts.workspaceTemp)) rules.set(opts.workspaceTemp, "write");
711
+ if (!rules.has(opts.stagedExecutableDir)) rules.set(opts.stagedExecutableDir, "read");
712
+ const filesystemRules = [...rules].sort(([a], [b]) => a.localeCompare(b)).map(([path, access2]) => `${JSON.stringify(path)} = ${JSON.stringify(access2)}`).join("\n");
551
713
  return `default_permissions = "${CODEX_PERMISSION_PROFILE}"
552
714
  approval_policy = "never"
553
715
  allow_login_shell = false
@@ -559,15 +721,14 @@ ignore_default_excludes = false
559
721
  exclude = ["^CODEX_HOME$", "(?i).*TOKEN.*", "(?i).*KEY.*", "(?i).*SECRET.*", "(?i).*PASSWORD.*", "(?i).*CREDENTIAL.*", "(?i).*AUTH.*", "(?i).*COOKIE.*"]
560
722
  set = { TMPDIR = ${JSON.stringify(opts.workspaceTemp)} }
561
723
 
562
- [permissions.${CODEX_PERMISSION_PROFILE}]
563
- extends = ":workspace"
564
-
565
724
  [permissions.${CODEX_PERMISSION_PROFILE}.filesystem]
725
+ ":minimal" = "read"
566
726
  ${filesystemRules}
567
727
  ":slash_tmp" = "deny"
568
728
 
569
729
  [permissions.${CODEX_PERMISSION_PROFILE}.filesystem.":workspace_roots"]
570
730
  "." = "write"
731
+ ".git" = "deny"
571
732
 
572
733
  [permissions.${CODEX_PERMISSION_PROFILE}.network]
573
734
  enabled = false
@@ -579,6 +740,161 @@ enabled = false
579
740
  "/run/containerd/containerd.sock" = "deny"
580
741
  `;
581
742
  }
743
+ function isSameOrDescendant(path, root) {
744
+ const normalizedPath = resolve(path);
745
+ const normalizedRoot = resolve(root);
746
+ return normalizedPath === normalizedRoot || normalizedPath.startsWith(`${normalizedRoot}${sep}`);
747
+ }
748
+ function minimalPathRoots(paths) {
749
+ const ordered = [...new Set(paths.map((path) => resolve(path)))].sort(
750
+ (a, b) => a.length - b.length || a.localeCompare(b)
751
+ );
752
+ return ordered.filter(
753
+ (path, index) => !ordered.slice(0, index).some((root) => isSameOrDescendant(path, root))
754
+ );
755
+ }
756
+ async function normalizeReadDeniedPaths(paths) {
757
+ if (!Array.isArray(paths)) {
758
+ throw new Error("runLocalHarness: codexReadDeniedPaths must be an array of absolute paths");
759
+ }
760
+ const normalized = /* @__PURE__ */ new Set();
761
+ for (const path of paths) {
762
+ if (typeof path !== "string" || path.length === 0 || path.includes("\0") || !isAbsolute(path)) {
763
+ throw new Error("runLocalHarness: codexReadDeniedPaths must contain absolute paths");
764
+ }
765
+ const absolute = resolve(path);
766
+ normalized.add(absolute);
767
+ try {
768
+ normalized.add(await realpath(absolute));
769
+ } catch (err) {
770
+ if (err.code !== "ENOENT") throw err;
771
+ }
772
+ }
773
+ return [...normalized].sort();
774
+ }
775
+ async function resolveCodexNativeExecutable(command, env) {
776
+ const candidates = command.includes(sep) || isAbsolute(command) ? [resolve(command)] : (env.PATH ?? "").split(delimiter).filter(Boolean).map((entry) => join(entry, command));
777
+ const seen = /* @__PURE__ */ new Set();
778
+ for (const candidate of candidates) {
779
+ let resolved;
780
+ try {
781
+ await access(candidate, constants.X_OK);
782
+ resolved = await realpath(candidate);
783
+ } catch {
784
+ continue;
785
+ }
786
+ if (seen.has(resolved)) continue;
787
+ seen.add(resolved);
788
+ if (await isStaticElfExecutable(resolved)) return resolved;
789
+ const packageRoot = await findCodexPackageRoot(resolved);
790
+ if (!packageRoot) continue;
791
+ const bundled = await findBundledCodexExecutable(packageRoot);
792
+ if (bundled) return bundled;
793
+ }
794
+ throw new Error(
795
+ "runLocalHarness: could not resolve the native Codex executable from the requested command"
796
+ );
797
+ }
798
+ async function findCodexPackageRoot(file) {
799
+ let cursor = dirname(file);
800
+ while (true) {
801
+ try {
802
+ const parsed = JSON.parse(await readFile(join(cursor, "package.json"), "utf8"));
803
+ if (parsed.name === "@openai/codex") return cursor;
804
+ } catch {
805
+ }
806
+ const parent = dirname(cursor);
807
+ if (parent === cursor) return void 0;
808
+ cursor = parent;
809
+ }
810
+ }
811
+ async function findBundledCodexExecutable(packageRoot) {
812
+ const target = linuxCodexTarget();
813
+ const candidates = [
814
+ join(
815
+ packageRoot,
816
+ "node_modules",
817
+ "@openai",
818
+ target.packageName,
819
+ "vendor",
820
+ target.triple,
821
+ "bin",
822
+ "codex"
823
+ ),
824
+ join(packageRoot, "vendor", target.triple, "bin", "codex")
825
+ ];
826
+ for (const executable of candidates) {
827
+ if (await isStaticElfExecutable(executable)) return executable;
828
+ }
829
+ return void 0;
830
+ }
831
+ function linuxCodexTarget() {
832
+ if (process.platform !== "linux") {
833
+ throw new Error("runLocalHarness: reproducible Codex mode requires Linux");
834
+ }
835
+ if (process.arch === "x64") {
836
+ return { packageName: "codex-linux-x64", triple: "x86_64-unknown-linux-musl" };
837
+ }
838
+ if (process.arch === "arm64") {
839
+ return { packageName: "codex-linux-arm64", triple: "aarch64-unknown-linux-musl" };
840
+ }
841
+ throw new Error(`runLocalHarness: unsupported Linux architecture ${process.arch}`);
842
+ }
843
+ async function isStaticElfExecutable(path) {
844
+ let handle;
845
+ try {
846
+ await access(path, constants.X_OK);
847
+ handle = await open(path, "r");
848
+ const header = Buffer.alloc(64);
849
+ const { bytesRead } = await handle.read(header, 0, header.length, 0);
850
+ if (bytesRead !== header.length || !header.subarray(0, 4).equals(Buffer.from([127, 69, 76, 70])) || header[4] !== 2 || header[5] !== 1) {
851
+ return false;
852
+ }
853
+ const programHeaderOffset = Number(header.readBigUInt64LE(32));
854
+ const programHeaderSize = header.readUInt16LE(54);
855
+ const programHeaderCount = header.readUInt16LE(56);
856
+ const expectedMachine = process.arch === "x64" ? 62 : process.arch === "arm64" ? 183 : -1;
857
+ if (![2, 3].includes(header.readUInt16LE(16)) || header.readUInt16LE(18) !== expectedMachine || !Number.isSafeInteger(programHeaderOffset) || programHeaderOffset < 64 || programHeaderSize < 56 || programHeaderSize > 1024 || programHeaderCount === 0 || programHeaderCount > 1024) {
858
+ return false;
859
+ }
860
+ const tableSize = programHeaderSize * programHeaderCount;
861
+ const table = Buffer.alloc(tableSize);
862
+ const tableRead = await handle.read(table, 0, tableSize, programHeaderOffset);
863
+ if (tableRead.bytesRead !== tableSize) return false;
864
+ for (let index = 0; index < programHeaderCount; index += 1) {
865
+ const type = table.readUInt32LE(index * programHeaderSize);
866
+ if (type === 3) return false;
867
+ }
868
+ return true;
869
+ } catch {
870
+ return false;
871
+ } finally {
872
+ await handle?.close();
873
+ }
874
+ }
875
+ function sha256File(path) {
876
+ return new Promise((resolveDigest, reject) => {
877
+ const hash = createHash("sha256");
878
+ const stream = createReadStream(path);
879
+ stream.on("error", reject);
880
+ stream.on("data", (chunk) => hash.update(chunk));
881
+ stream.on("end", () => resolveDigest(hash.digest("hex")));
882
+ });
883
+ }
884
+ function signalProcessTree(child, signal) {
885
+ if (process.platform !== "win32" && typeof child.pid === "number") {
886
+ try {
887
+ process.kill(-child.pid, signal);
888
+ return;
889
+ } catch (err) {
890
+ if (err.code === "ESRCH") return;
891
+ }
892
+ }
893
+ try {
894
+ child.kill(signal);
895
+ } catch {
896
+ }
897
+ }
582
898
  function parseCodexTokenUsage(stdout) {
583
899
  const completed = [];
584
900
  for (const [index, line] of stdout.split(/\r?\n/).entries()) {
@@ -631,4 +947,4 @@ export {
631
947
  runLocalHarness,
632
948
  parseCodexTokenUsage
633
949
  };
634
- //# sourceMappingURL=chunk-PJNGPWTA.js.map
950
+ //# sourceMappingURL=chunk-JFAGXZKR.js.map