@sagentlab/navarch-runtime 0.1.82 → 0.1.84
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/README.md +5 -0
- package/dist/browser-probe.cjs +8 -1
- package/dist/exit-conditions.cjs +1 -0
- package/dist/session.cjs +1 -0
- package/dist/worktree-guard.cjs +72 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -549,6 +549,11 @@ mount, avoiding bubblewrap startup failures with linked Git worktrees. Its
|
|
|
549
549
|
the profile does not grant all of `/tmp`. Extra/tool roots that contain the
|
|
550
550
|
workspace are omitted on Linux so they cannot expose sibling sessions or
|
|
551
551
|
session metadata. This also applies when the workspace itself lives under `/tmp`.
|
|
552
|
+
Because bubblewrap starts from an empty `/` and Codex re-executes its own binary
|
|
553
|
+
inside it, the profile also grants read access to the Codex executable's
|
|
554
|
+
install directory (resolved from `NAVARCH_CODEX_BIN` or `PATH`, following
|
|
555
|
+
symlinks) plus mise and nvm tool installs, so a Codex or Node installed under
|
|
556
|
+
`$HOME` still starts. `$HOME` and `CODEX_HOME` themselves are never granted.
|
|
552
557
|
To verify the linked-worktree boundary on a Linux host with Codex installed,
|
|
553
558
|
run `NAVARCH_TEST_REAL_CODEX=1 npm test -- tests/codex-linux-sandbox.test.cts`
|
|
554
559
|
from `runtime/`. The smoke test uses local shell commands and makes no model calls.
|
package/dist/browser-probe.cjs
CHANGED
|
@@ -155,6 +155,7 @@ function codexSandboxProbeArgs(config, roots) {
|
|
|
155
155
|
repositoryPath: node_path_1.default.join(workspaceRoot, "repo"),
|
|
156
156
|
workspaceRoot,
|
|
157
157
|
extraRoots: [...config.guardExtraRoots, ...roots.readRoots],
|
|
158
|
+
codexBin: config.codexBin,
|
|
158
159
|
}).filter((arg) => arg !== "--ignore-user-config");
|
|
159
160
|
}
|
|
160
161
|
const PROBE_SCRIPT = `
|
|
@@ -200,9 +201,15 @@ async function probeCodexSandbox(config, deps) {
|
|
|
200
201
|
return null;
|
|
201
202
|
const output = `${result.stderr}\n${result.stdout}`;
|
|
202
203
|
if (!result.stdout.includes(PROBE_START_MARKER)) {
|
|
204
|
+
// bwrap could not find the program it was asked to start inside the
|
|
205
|
+
// sandbox view: a path outside the granted read roots, not an old CLI.
|
|
206
|
+
const hidden = /bwrap: execvp .*No such file or directory/.test(output);
|
|
203
207
|
return ("Could not verify a browser inside the Codex sandbox: `codex sandbox` did not run the probe " +
|
|
204
208
|
`(${result.timedOut ? "timed out" : output.trim() ? summarizeLaunchFailure(output) : `exit ${result.code}`}). ` +
|
|
205
|
-
|
|
209
|
+
(hidden
|
|
210
|
+
? "The sandbox cannot see that executable; add its install directory to NAVARCH_GUARD_EXTRA_ROOTS, " +
|
|
211
|
+
"or run browser tasks with another agent."
|
|
212
|
+
: "Update the Codex CLI, or run browser tasks with another agent."));
|
|
206
213
|
}
|
|
207
214
|
const detail = result.timedOut ? "the launch timed out." : summarizeLaunchFailure(output);
|
|
208
215
|
const machPort = /bootstrap_check_in|MachPortRendezvous|mach_port_rendezvous/i.test(output);
|
package/dist/exit-conditions.cjs
CHANGED
|
@@ -39,6 +39,7 @@ function parseClaudeJsonResult(stdout) {
|
|
|
39
39
|
const isUsableResult = (value) => typeof value === "object" &&
|
|
40
40
|
value !== null &&
|
|
41
41
|
!Array.isArray(value) &&
|
|
42
|
+
(!("result" in value) || typeof value.result === "string") &&
|
|
42
43
|
("result" in value || "usage" in value || "cost_usd" in value || "total_cost_usd" in value);
|
|
43
44
|
try {
|
|
44
45
|
const parsed = JSON.parse(trimmed);
|
package/dist/session.cjs
CHANGED
|
@@ -389,6 +389,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
389
389
|
repositoryPath: gitWorktree.repositoryPath,
|
|
390
390
|
workspaceRoot: config.workspaceRoot,
|
|
391
391
|
extraRoots: config.guardExtraRoots,
|
|
392
|
+
codexBin: config.codexBin,
|
|
392
393
|
});
|
|
393
394
|
}
|
|
394
395
|
else if (runtime === "gemini") {
|
package/dist/worktree-guard.cjs
CHANGED
|
@@ -7,6 +7,7 @@ exports.guardHookScriptPath = guardHookScriptPath;
|
|
|
7
7
|
exports.readClaudeApiKeyHelper = readClaudeApiKeyHelper;
|
|
8
8
|
exports.prepareWorktreeGuard = prepareWorktreeGuard;
|
|
9
9
|
exports.codexToolReadRoots = codexToolReadRoots;
|
|
10
|
+
exports.codexExecutableReadRoots = codexExecutableReadRoots;
|
|
10
11
|
exports.macosDeveloperWriteRoots = macosDeveloperWriteRoots;
|
|
11
12
|
exports.sessionGhConfigDir = sessionGhConfigDir;
|
|
12
13
|
exports.sessionCodexTmpDir = sessionCodexTmpDir;
|
|
@@ -131,9 +132,75 @@ function codexToolReadRoots(env = process.env, platform = process.platform) {
|
|
|
131
132
|
"/System/Library/CoreServices/SystemAppearance.bundle",
|
|
132
133
|
node_path_1.default.join(homeDir, "Library", "Caches", "ms-playwright"),
|
|
133
134
|
node_path_1.default.join(homeDir, ".cache", "ms-playwright"),
|
|
135
|
+
// Per-user toolchain installs (node/npm, Codex itself). Shell commands
|
|
136
|
+
// under the Linux sandbox see only granted roots, so a mise- or
|
|
137
|
+
// nvm-managed `node` on PATH is otherwise "command not found".
|
|
138
|
+
node_path_1.default.join(miseDataDir(env, homeDir), "installs"),
|
|
139
|
+
node_path_1.default.join(env.NVM_DIR?.trim() || node_path_1.default.join(homeDir, ".nvm"), "versions"),
|
|
134
140
|
...(platform === "darwin" ? MACOS_TOOLCHAIN_READ_ROOTS : []),
|
|
135
141
|
];
|
|
136
142
|
}
|
|
143
|
+
function miseDataDir(env, homeDir) {
|
|
144
|
+
const explicit = env.MISE_DATA_DIR?.trim();
|
|
145
|
+
if (explicit)
|
|
146
|
+
return explicit;
|
|
147
|
+
return node_path_1.default.join(env.XDG_DATA_HOME?.trim() || node_path_1.default.join(homeDir, ".local", "share"), "mise");
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Directories holding the Codex executable itself.
|
|
151
|
+
*
|
|
152
|
+
* On Linux a restricted-read profile starts bwrap from an empty `/` with only
|
|
153
|
+
* granted roots mounted, and Codex re-executes its own binary inside that view
|
|
154
|
+
* to install seccomp. A CLI installed outside `:minimal` (mise, nvm, an npm
|
|
155
|
+
* prefix under $HOME) then fails every sandboxed command with
|
|
156
|
+
* `bwrap: execvp <codex>: No such file or directory`. Grants the PATH entry,
|
|
157
|
+
* its symlink target's directory, and an npm package root (whose vendored
|
|
158
|
+
* native binary is what actually re-executes). Never returns $HOME, CODEX_HOME,
|
|
159
|
+
* or an ancestor of either: those would expose credentials read-only.
|
|
160
|
+
*/
|
|
161
|
+
function codexExecutableReadRoots(bin, env = process.env) {
|
|
162
|
+
const resolved = resolveExecutable(bin, env);
|
|
163
|
+
if (!resolved)
|
|
164
|
+
return [];
|
|
165
|
+
let real = resolved;
|
|
166
|
+
try {
|
|
167
|
+
real = (0, node_fs_1.realpathSync)(resolved);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Keep the logical path; bwrap mounts it as given.
|
|
171
|
+
}
|
|
172
|
+
const roots = [node_path_1.default.dirname(resolved), node_path_1.default.dirname(real)];
|
|
173
|
+
const packageRoot = /^(.*\/node_modules\/(?:@[^/]+\/)?[^/]+)\//.exec(real)?.[1];
|
|
174
|
+
if (packageRoot)
|
|
175
|
+
roots.push(packageRoot);
|
|
176
|
+
const homeDir = env.HOME?.trim() || node_os_1.default.homedir();
|
|
177
|
+
const sensitive = [homeDir, env.CODEX_HOME?.trim() || node_path_1.default.join(homeDir, ".codex")].flatMap((dir) => {
|
|
178
|
+
try {
|
|
179
|
+
return [node_path_1.default.resolve(dir), (0, node_fs_1.realpathSync)(dir)];
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return [node_path_1.default.resolve(dir)];
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return [...new Set(roots.map((root) => node_path_1.default.resolve(root)))].filter((root) => !sensitive.some((target) => isPathInside(target, root)));
|
|
186
|
+
}
|
|
187
|
+
function resolveExecutable(bin, env) {
|
|
188
|
+
const candidates = bin.includes("/")
|
|
189
|
+
? [node_path_1.default.resolve(bin)]
|
|
190
|
+
: (env.PATH ?? "").split(node_path_1.default.delimiter).filter(Boolean).map((dir) => node_path_1.default.join(dir, bin));
|
|
191
|
+
for (const candidate of candidates) {
|
|
192
|
+
try {
|
|
193
|
+
if (!(0, node_fs_1.statSync)(candidate).isFile())
|
|
194
|
+
continue;
|
|
195
|
+
(0, node_fs_1.accessSync)(candidate, node_fs_1.constants.X_OK);
|
|
196
|
+
return node_path_1.default.resolve(candidate);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
// Not here; keep searching PATH.
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
137
204
|
/**
|
|
138
205
|
* World-readable macOS toolchain locations. `xcrun simctl` / `xcodebuild`
|
|
139
206
|
* load /Library/Developer/PrivateFrameworks/CoreSimulator.framework and the
|
|
@@ -213,7 +280,11 @@ function codexWorktreeGuardArgs(options, platform = process.platform) {
|
|
|
213
280
|
[sessionGhConfigDir(options.workDir)]: "read",
|
|
214
281
|
};
|
|
215
282
|
const overlapsWorkspace = (root) => isPathInside(root, workspaceRoot) || (linux && isPathInside(workspaceRoot, root));
|
|
216
|
-
|
|
283
|
+
const readRoots = [
|
|
284
|
+
...codexToolReadRoots(process.env, platform),
|
|
285
|
+
...(linux && options.codexBin ? codexExecutableReadRoots(options.codexBin) : []),
|
|
286
|
+
];
|
|
287
|
+
for (const root of readRoots) {
|
|
217
288
|
const resolved = node_path_1.default.resolve(root);
|
|
218
289
|
if (overlapsWorkspace(resolved))
|
|
219
290
|
continue;
|
package/package.json
CHANGED