@sagentlab/navarch-runtime 0.1.81 → 0.1.83
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 +9 -0
- package/dist/exit-conditions.cjs +1 -0
- package/dist/session.cjs +5 -0
- package/dist/worktree-guard.cjs +27 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -543,6 +543,15 @@ The Claude hook is a strong guardrail rather than a hard security boundary
|
|
|
543
543
|
because shell paths are screened lexically. Codex's permission profile is
|
|
544
544
|
enforced by its OS sandbox. For container-grade whole-process isolation use
|
|
545
545
|
`NAVARCH_SANDBOX_MODE=docker`; neither host guard is installed in Docker mode.
|
|
546
|
+
On Linux, Codex uses an explicit allowlist without a denied workspace-parent
|
|
547
|
+
mount, avoiding bubblewrap startup failures with linked Git worktrees. Its
|
|
548
|
+
`TMPDIR`, `TMP`, and `TEMP` point to a private `tmp` directory inside the session;
|
|
549
|
+
the profile does not grant all of `/tmp`. Extra/tool roots that contain the
|
|
550
|
+
workspace are omitted on Linux so they cannot expose sibling sessions or
|
|
551
|
+
session metadata. This also applies when the workspace itself lives under `/tmp`.
|
|
552
|
+
To verify the linked-worktree boundary on a Linux host with Codex installed,
|
|
553
|
+
run `NAVARCH_TEST_REAL_CODEX=1 npm test -- tests/codex-linux-sandbox.test.cts`
|
|
554
|
+
from `runtime/`. The smoke test uses local shell commands and makes no model calls.
|
|
546
555
|
An operator-supplied `--settings` in `NAVARCH_CLAUDE_EXTRA_ARGS`, or an
|
|
547
556
|
explicit Codex permission/sandbox option in `NAVARCH_CODEX_EXTRA_ARGS`, takes
|
|
548
557
|
precedence over the generated policy.
|
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
|
@@ -378,6 +378,11 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
|
|
|
378
378
|
}
|
|
379
379
|
}
|
|
380
380
|
else if (runtime === "codex") {
|
|
381
|
+
if (process.platform === "linux") {
|
|
382
|
+
const tmpDir = (0, worktree_guard_cjs_1.sessionCodexTmpDir)(workDir);
|
|
383
|
+
await node_fs_1.promises.mkdir(tmpDir, { recursive: true, mode: 0o700 });
|
|
384
|
+
Object.assign(sessionEnv, { TMPDIR: tmpDir, TMP: tmpDir, TEMP: tmpDir });
|
|
385
|
+
}
|
|
381
386
|
codexGuardArgs = (0, worktree_guard_cjs_1.codexWorktreeGuardArgs)({
|
|
382
387
|
workDir,
|
|
383
388
|
worktreePath: gitWorktree.worktreePath,
|
package/dist/worktree-guard.cjs
CHANGED
|
@@ -9,6 +9,7 @@ exports.prepareWorktreeGuard = prepareWorktreeGuard;
|
|
|
9
9
|
exports.codexToolReadRoots = codexToolReadRoots;
|
|
10
10
|
exports.macosDeveloperWriteRoots = macosDeveloperWriteRoots;
|
|
11
11
|
exports.sessionGhConfigDir = sessionGhConfigDir;
|
|
12
|
+
exports.sessionCodexTmpDir = sessionCodexTmpDir;
|
|
12
13
|
exports.codexWorktreeGuardArgs = codexWorktreeGuardArgs;
|
|
13
14
|
exports.geminiSandboxMounts = geminiSandboxMounts;
|
|
14
15
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -172,6 +173,9 @@ function macosDeveloperWriteRoots(env = process.env, platform = process.platform
|
|
|
172
173
|
function sessionGhConfigDir(workDir) {
|
|
173
174
|
return node_path_1.default.join(workDir, SESSION_GH_CONFIG_DIRNAME);
|
|
174
175
|
}
|
|
176
|
+
function sessionCodexTmpDir(workDir) {
|
|
177
|
+
return node_path_1.default.resolve(workDir, "tmp");
|
|
178
|
+
}
|
|
175
179
|
/**
|
|
176
180
|
* Builds one-off Codex permission-profile arguments for a host session.
|
|
177
181
|
*
|
|
@@ -180,33 +184,46 @@ function sessionGhConfigDir(workDir) {
|
|
|
180
184
|
* this boundary. Authentication still comes from CODEX_HOME. The profile
|
|
181
185
|
* grants only the runtime paths common tools need, the session worktree, the
|
|
182
186
|
* shared Git directory, temp space, and operator-approved extra roots. The
|
|
183
|
-
* surrounding Navarch workspace is denied
|
|
184
|
-
*
|
|
187
|
+
* surrounding Navarch workspace is denied (implicitly on Linux), with only
|
|
188
|
+
* this session's paths granted access. The worktree
|
|
185
189
|
* is marked untrusted for configuration purposes so a checked-in legacy
|
|
186
190
|
* sandbox_mode cannot disable the generated permission profile; repository
|
|
187
191
|
* instructions such as AGENTS.md still load normally.
|
|
188
192
|
*/
|
|
189
|
-
function codexWorktreeGuardArgs(options) {
|
|
193
|
+
function codexWorktreeGuardArgs(options, platform = process.platform) {
|
|
194
|
+
const workspaceRoot = node_path_1.default.resolve(options.workspaceRoot);
|
|
195
|
+
const linux = platform === "linux";
|
|
196
|
+
// On Linux, a denied ancestor is an empty read-only bwrap mount. Codex
|
|
197
|
+
// can then try to protect a linked worktree's gitdir before reopening the
|
|
198
|
+
// bare repository, failing to mkdir repository.git/worktrees/repo beneath
|
|
199
|
+
// that mount. Use restricted-read's implicit deny instead. Broad temp or
|
|
200
|
+
// ancestor grants would expose the workspace without the explicit mask,
|
|
201
|
+
// so Linux gets only a session temp root and non-overlapping extra roots.
|
|
190
202
|
const filesystem = {
|
|
191
203
|
":minimal": "read",
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
204
|
+
...(linux
|
|
205
|
+
? { [sessionCodexTmpDir(options.workDir)]: "write" }
|
|
206
|
+
: {
|
|
207
|
+
":tmpdir": "write",
|
|
208
|
+
":slash_tmp": "write",
|
|
209
|
+
[workspaceRoot]: "deny",
|
|
210
|
+
}),
|
|
195
211
|
[node_path_1.default.resolve(options.worktreePath)]: "write",
|
|
196
212
|
[node_path_1.default.resolve(options.repositoryPath)]: "write",
|
|
197
213
|
[sessionGhConfigDir(options.workDir)]: "read",
|
|
198
214
|
};
|
|
199
|
-
|
|
215
|
+
const overlapsWorkspace = (root) => isPathInside(root, workspaceRoot) || (linux && isPathInside(workspaceRoot, root));
|
|
216
|
+
for (const root of codexToolReadRoots(process.env, platform)) {
|
|
200
217
|
const resolved = node_path_1.default.resolve(root);
|
|
201
|
-
if (
|
|
218
|
+
if (overlapsWorkspace(resolved))
|
|
202
219
|
continue;
|
|
203
220
|
filesystem[resolved] = "read";
|
|
204
221
|
}
|
|
205
|
-
for (const root of [...macosDeveloperWriteRoots(), ...(options.extraRoots ?? [])]) {
|
|
222
|
+
for (const root of [...macosDeveloperWriteRoots(process.env, platform), ...(options.extraRoots ?? [])]) {
|
|
206
223
|
const resolved = node_path_1.default.resolve(root);
|
|
207
224
|
// Match the Claude hook's denied-root precedence: an extra root cannot
|
|
208
225
|
// reopen sibling sessions or metadata inside the Navarch workspace.
|
|
209
|
-
if (
|
|
226
|
+
if (overlapsWorkspace(resolved))
|
|
210
227
|
continue;
|
|
211
228
|
filesystem[resolved] = "write";
|
|
212
229
|
}
|
package/package.json
CHANGED