@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.
- package/dist/agent.js +3 -3
- package/dist/{agentic-generator-D9ZmAuiL.d.ts → agentic-generator-B7Cyl6cn.d.ts} +1 -1
- package/dist/{chunk-HPCJBBLS.js → chunk-7ESP4C3O.js} +2 -2
- package/dist/{chunk-K7OPNHU4.js → chunk-DNK3L7QB.js} +2 -2
- package/dist/{chunk-PJNGPWTA.js → chunk-JFAGXZKR.js} +383 -67
- package/dist/chunk-JFAGXZKR.js.map +1 -0
- package/dist/{chunk-D336OZHK.js → chunk-L4CACVIJ.js} +2 -2
- package/dist/{chunk-JEP2WM24.js → chunk-OAVVQAOQ.js} +2 -2
- package/dist/{chunk-RTSZNYXM.js → chunk-PV6Q3OKU.js} +3 -3
- package/dist/{chunk-URMJTSXB.js → chunk-TTWMSNFZ.js} +14 -2
- package/dist/chunk-TTWMSNFZ.js.map +1 -0
- package/dist/{chunk-SJHFJOCY.js → chunk-ZLPKMFOE.js} +2 -2
- package/dist/{completion-gate-BAj3dXeW.d.ts → completion-gate-Bz7VYs7a.d.ts} +9 -1
- package/dist/{coordination-B3ZuApR_.d.ts → coordination-CdU8LyvB.d.ts} +4 -2
- package/dist/{improve-r8SvIxYv.d.ts → improve-mrYmy_1e.d.ts} +2 -2
- package/dist/index.d.ts +10 -10
- package/dist/index.js +8 -8
- package/dist/intelligence.d.ts +3 -3
- package/dist/intelligence.js +3 -3
- package/dist/knowledge.d.ts +4 -4
- package/dist/knowledge.js +3 -3
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +2 -2
- package/dist/{local-harness-B7bNuNJw.d.ts → local-harness-C2HDGiOY.d.ts} +16 -0
- package/dist/{loop-runner-bin-D4bMIxmB.d.ts → loop-runner-bin-BSvtOPMv.d.ts} +1 -1
- package/dist/loop-runner-bin.d.ts +4 -4
- package/dist/loop-runner-bin.js +4 -4
- package/dist/loops.d.ts +9 -9
- package/dist/loops.js +3 -3
- package/dist/mcp/bin.js +2 -2
- package/dist/mcp/index.d.ts +6 -6
- package/dist/mcp/index.js +4 -4
- package/dist/{mcp-serve-verifier-BuXjaZDE.d.ts → mcp-serve-verifier-C65X30mY.d.ts} +1 -1
- package/dist/{supervise-DTNcLDl2.d.ts → supervise-U6DTnQu6.d.ts} +2 -2
- package/dist/{worktree-fanout-IMKqdV_E.d.ts → worktree-fanout-DqzIBWMW.d.ts} +5 -2
- package/package.json +1 -1
- package/dist/chunk-PJNGPWTA.js.map +0 -1
- package/dist/chunk-URMJTSXB.js.map +0 -1
- /package/dist/{chunk-HPCJBBLS.js.map → chunk-7ESP4C3O.js.map} +0 -0
- /package/dist/{chunk-K7OPNHU4.js.map → chunk-DNK3L7QB.js.map} +0 -0
- /package/dist/{chunk-D336OZHK.js.map → chunk-L4CACVIJ.js.map} +0 -0
- /package/dist/{chunk-JEP2WM24.js.map → chunk-OAVVQAOQ.js.map} +0 -0
- /package/dist/{chunk-RTSZNYXM.js.map → chunk-PV6Q3OKU.js.map} +0 -0
- /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 {
|
|
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
|
|
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(
|
|
153
|
+
if (options.codexReproducible) assertCodexReproducibleInvocation(requestedCommand, args);
|
|
138
154
|
const baseEnv = options.env ?? process.env;
|
|
139
|
-
const isolated = options.codexReproducible ? await isolateCodexHome(
|
|
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((
|
|
198
|
+
return await new Promise((resolve2, reject) => {
|
|
162
199
|
let child;
|
|
163
200
|
try {
|
|
164
|
-
child = spawnImpl(command, args, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
'
|
|
290
|
-
'
|
|
291
|
-
'!
|
|
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.
|
|
307
|
-
opts.
|
|
308
|
-
|
|
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
|
-
|
|
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((
|
|
481
|
+
return new Promise((resolve2, reject) => {
|
|
399
482
|
let child;
|
|
400
483
|
try {
|
|
401
|
-
child = opts.spawn(opts.command, args, {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
509
|
-
|
|
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
|
|
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
|
|
528
|
-
const
|
|
529
|
-
opts.
|
|
693
|
+
const rules = /* @__PURE__ */ new Map();
|
|
694
|
+
const broadDeniedPaths = minimalPathRoots([
|
|
695
|
+
...opts.deniedHostRoots.map((path) => resolve(path)),
|
|
696
|
+
tmpdir(),
|
|
530
697
|
opts.ambientTmp,
|
|
531
|
-
|
|
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
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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-
|
|
950
|
+
//# sourceMappingURL=chunk-JFAGXZKR.js.map
|