From dd098f1a446ea26f57ce55a5e8178e11296f8dc2 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 22:11:52 -0400 Subject: [PATCH 01/17] test(e2e): capture configured Docker mounts Signed-off-by: Julie Yaunches --- test/e2e/live/openshell-exact-main-driver-config.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/e2e/live/openshell-exact-main-driver-config.ts b/test/e2e/live/openshell-exact-main-driver-config.ts index e73e073e93..8f8cb633cb 100644 --- a/test/e2e/live/openshell-exact-main-driver-config.ts +++ b/test/e2e/live/openshell-exact-main-driver-config.ts @@ -345,6 +345,18 @@ async function assertRuntimeMounts( phase: string, ): Promise { const components = proof.components!; + await inspectJson( + host, + containerId, + "{{json .HostConfig.Mounts}}", + `exact-main-driver-configured-mounts-${phase}`, + ); + await inspectJson( + host, + containerId, + "{{json .HostConfig.Tmpfs}}", + `exact-main-driver-configured-tmpfs-${phase}`, + ); const mounts = await inspectJson( host, containerId, From d8277d9cc250f70f37b24da790d38057bd0dec9a Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 22:15:33 -0400 Subject: [PATCH 02/17] test(e2e): trace Docker mount inspect shape Signed-off-by: Julie Yaunches --- src/lib/onboard/docker-gpu-patch-recreate.ts | 7 +++++++ src/lib/onboard/docker-gpu-patch-types.ts | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/lib/onboard/docker-gpu-patch-recreate.ts b/src/lib/onboard/docker-gpu-patch-recreate.ts index 2584a9b42c..9c11f52ba4 100644 --- a/src/lib/onboard/docker-gpu-patch-recreate.ts +++ b/src/lib/onboard/docker-gpu-patch-recreate.ts @@ -190,6 +190,13 @@ export function recreateOpenShellDockerSandboxContainer( } context.oldContainerId = oldContainerId; const inspect = inspectDockerContainer(oldContainerId, deps); + console.log( + ` Docker mount inspect projection: ${JSON.stringify({ + configuredMounts: inspect.HostConfig?.Mounts ?? null, + configuredTmpfs: inspect.HostConfig?.Tmpfs ?? null, + runtimeStructuredMounts: inspect.Mounts?.filter((mount) => mount.Type !== "bind") ?? null, + })}`, + ); const configuredImage = String(inspect.Config?.Image || "").trim(); if (!configuredImage) { throw new Error("OpenShell sandbox container inspect did not include an image."); diff --git a/src/lib/onboard/docker-gpu-patch-types.ts b/src/lib/onboard/docker-gpu-patch-types.ts index 2fab00dd63..4b1469414d 100644 --- a/src/lib/onboard/docker-gpu-patch-types.ts +++ b/src/lib/onboard/docker-gpu-patch-types.ts @@ -188,6 +188,7 @@ export type DockerContainerInspect = { } | null; HostConfig?: { Binds?: string[] | null; + Tmpfs?: Record | null; Mounts?: Array<{ Type?: string; Source?: string; @@ -242,6 +243,16 @@ export type DockerContainerInspect = { ReadonlyPaths?: string[] | null; MaskedPaths?: string[] | null; } | null; + Mounts?: Array<{ + Type?: string; + Name?: string; + Source?: string; + Destination?: string; + Driver?: string; + Mode?: string; + RW?: boolean; + Propagation?: string; + }> | null; NetworkSettings?: { Networks?: Record< string, From 87e62b12951deb259ba80bd9b4380e89bb0b03c1 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 22:23:57 -0400 Subject: [PATCH 03/17] fix(onboard): keep tmpfs mounts structured Signed-off-by: Julie Yaunches --- .../onboard/docker-gpu-patch-clone.test.ts | 4 +- src/lib/onboard/docker-gpu-patch-clone.ts | 29 +++++++----- src/lib/onboard/docker-gpu-patch-recreate.ts | 7 --- src/lib/onboard/docker-gpu-patch-types.ts | 11 ----- .../docker-gpu-patch-validation.test.ts | 44 +++++++++++++++++++ 5 files changed, 64 insertions(+), 31 deletions(-) diff --git a/src/lib/onboard/docker-gpu-patch-clone.test.ts b/src/lib/onboard/docker-gpu-patch-clone.test.ts index a823986393..8ad39b3016 100644 --- a/src/lib/onboard/docker-gpu-patch-clone.test.ts +++ b/src/lib/onboard/docker-gpu-patch-clone.test.ts @@ -32,8 +32,8 @@ describe("Docker GPU clone envelope", () => { "openshell.ai/sandbox-name=alpha", "--volume", "/host:/container:rw", - "--tmpfs", - "/tmp/nemoclaw-exact-main-driver-config:noexec,size=16777216,mode=1777", + "--mount", + "type=tmpfs,dst=/tmp/nemoclaw-exact-main-driver-config,tmpfs-size=16777216,tmpfs-mode=1777", "--network", "openshell-docker", "--network-alias", diff --git a/src/lib/onboard/docker-gpu-patch-clone.ts b/src/lib/onboard/docker-gpu-patch-clone.ts index e6da25d159..a3a1a50941 100644 --- a/src/lib/onboard/docker-gpu-patch-clone.ts +++ b/src/lib/onboard/docker-gpu-patch-clone.ts @@ -16,6 +16,7 @@ const GPU_ENV_KEYS = new Set([ "NVIDIA_REQUIRE_CUDA", "NVIDIA_DISABLE_REQUIRE", ]); +const DOCKER_DEFAULT_TMPFS_OPTIONS = new Set(["noexec", "nosuid", "nodev"]); type DockerStructuredMount = NonNullable< NonNullable["Mounts"] >[number]; @@ -156,13 +157,18 @@ function dockerTmpfsMountValue(mount: DockerStructuredMount): string { if (!target.startsWith("/")) { throw new Error("Docker structured mount target must be an absolute container path."); } - const options: string[] = []; - if (optionalMountBoolean(mount.ReadOnly, "ReadOnly")) options.push("ro"); + const values = [`type=tmpfs`, `dst=${target}`]; + if (optionalMountBoolean(mount.ReadOnly, "ReadOnly")) values.push("readonly"); for (const parts of mount.TmpfsOptions?.Options ?? []) { - if (!Array.isArray(parts) || parts.length < 1 || parts.length > 2) { - throw new Error("Docker tmpfs mount options must contain one or two values."); + if (!Array.isArray(parts) || parts.length !== 1) { + throw new Error("Docker structured tmpfs options must contain exactly one value."); + } + const option = mountValue(parts[0], "tmpfs option"); + if (!DOCKER_DEFAULT_TMPFS_OPTIONS.has(option)) { + throw new Error( + `Docker structured tmpfs option '${option}' cannot be preserved during recreation.`, + ); } - options.push(parts.map((part) => mountValue(part, "tmpfs option")).join("=")); } const sizeBytes = mount.TmpfsOptions?.SizeBytes; @@ -170,16 +176,16 @@ function dockerTmpfsMountValue(mount: DockerStructuredMount): string { if (!Number.isSafeInteger(sizeBytes) || sizeBytes <= 0) { throw new Error("Docker tmpfs mount size must be a positive safe integer."); } - options.push(`size=${sizeBytes}`); + values.push(`tmpfs-size=${sizeBytes}`); } const mode = mount.TmpfsOptions?.Mode; if (mode !== undefined && mode !== null) { if (!Number.isSafeInteger(mode) || mode < 0 || mode > 0o7777) { throw new Error("Docker tmpfs mount mode must be a valid non-negative file mode."); } - options.push(`mode=${mode.toString(8)}`); + values.push(`tmpfs-mode=${mode.toString(8)}`); } - return options.length > 0 ? `${target}:${options.join(",")}` : target; + return values.join(","); } function dockerVolumeMountValue(mount: DockerStructuredMount): string { @@ -215,9 +221,10 @@ function dockerStructuredMountArgs(inspect: DockerContainerInspect): string[] { for (const mount of inspect.HostConfig?.Mounts ?? []) { switch (mount.Type) { case "tmpfs": - // The --tmpfs form preserves OpenShell's arbitrary TmpfsOptions.Options, - // such as noexec, in addition to the structured size and mode fields. - args.push("--tmpfs", dockerTmpfsMountValue(mount)); + // Docker applies noexec, nosuid, and nodev to tmpfs mounts by default. + // Keep the structured representation so Docker inspect and later + // recreation retain size and mode alongside that security posture. + args.push("--mount", dockerTmpfsMountValue(mount)); break; case "volume": args.push("--mount", dockerVolumeMountValue(mount)); diff --git a/src/lib/onboard/docker-gpu-patch-recreate.ts b/src/lib/onboard/docker-gpu-patch-recreate.ts index 9c11f52ba4..2584a9b42c 100644 --- a/src/lib/onboard/docker-gpu-patch-recreate.ts +++ b/src/lib/onboard/docker-gpu-patch-recreate.ts @@ -190,13 +190,6 @@ export function recreateOpenShellDockerSandboxContainer( } context.oldContainerId = oldContainerId; const inspect = inspectDockerContainer(oldContainerId, deps); - console.log( - ` Docker mount inspect projection: ${JSON.stringify({ - configuredMounts: inspect.HostConfig?.Mounts ?? null, - configuredTmpfs: inspect.HostConfig?.Tmpfs ?? null, - runtimeStructuredMounts: inspect.Mounts?.filter((mount) => mount.Type !== "bind") ?? null, - })}`, - ); const configuredImage = String(inspect.Config?.Image || "").trim(); if (!configuredImage) { throw new Error("OpenShell sandbox container inspect did not include an image."); diff --git a/src/lib/onboard/docker-gpu-patch-types.ts b/src/lib/onboard/docker-gpu-patch-types.ts index 4b1469414d..2fab00dd63 100644 --- a/src/lib/onboard/docker-gpu-patch-types.ts +++ b/src/lib/onboard/docker-gpu-patch-types.ts @@ -188,7 +188,6 @@ export type DockerContainerInspect = { } | null; HostConfig?: { Binds?: string[] | null; - Tmpfs?: Record | null; Mounts?: Array<{ Type?: string; Source?: string; @@ -243,16 +242,6 @@ export type DockerContainerInspect = { ReadonlyPaths?: string[] | null; MaskedPaths?: string[] | null; } | null; - Mounts?: Array<{ - Type?: string; - Name?: string; - Source?: string; - Destination?: string; - Driver?: string; - Mode?: string; - RW?: boolean; - Propagation?: string; - }> | null; NetworkSettings?: { Networks?: Record< string, diff --git a/src/lib/onboard/docker-gpu-patch-validation.test.ts b/src/lib/onboard/docker-gpu-patch-validation.test.ts index b80e2834fd..1ee255e7d4 100644 --- a/src/lib/onboard/docker-gpu-patch-validation.test.ts +++ b/src/lib/onboard/docker-gpu-patch-validation.test.ts @@ -181,6 +181,50 @@ describe("Docker GPU startup command validation (#6110)", () => { expect(dockerRunDetached).not.toHaveBeenCalled(); }); + it("rejects non-default structured tmpfs options before touching the original container", () => { + const inspect = inspectFixture(); + inspect.HostConfig!.Mounts = [ + { + Type: "tmpfs", + Target: "/tmp/sandbox", + TmpfsOptions: { Options: [["exec"]] }, + }, + ]; + const dockerStop = vi.fn(() => ({ status: 0 })); + const dockerRename = vi.fn(() => ({ status: 0 })); + const dockerRunDetached = vi.fn(() => ({ status: 0, stdout: "new-container-id\n" })); + + expect(() => + recreateOpenShellDockerSandboxWithGpu( + { + sandboxName: "alpha", + timeoutSecs: 1, + openshellSandboxCommand: ["env", "nemoclaw-start"], + }, + { + dockerCapture: vi.fn((args: readonly string[]) => + args[0] === "ps" + ? "old-container-id\n" + : args[0] === "inspect" + ? JSON.stringify([inspect]) + : "", + ), + detectSandboxFallbackDns: vi.fn(() => null), + dockerRun: vi.fn(() => ({ status: 0, stdout: "probe-id\n" })), + dockerRunDetached, + dockerRename, + dockerRm: vi.fn(() => ({ status: 0 })), + dockerStop, + readDir: vi.fn(() => null), + readFile: vi.fn(() => null), + }, + ), + ).toThrow("Docker structured tmpfs option 'exec' cannot be preserved during recreation"); + expect(dockerStop).not.toHaveBeenCalled(); + expect(dockerRename).not.toHaveBeenCalled(); + expect(dockerRunDetached).not.toHaveBeenCalled(); + }); + it.each([ ";", "&&", From a073019e67dd8729630f508817d91835199fc9dc Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 22:33:16 -0400 Subject: [PATCH 04/17] test(e2e): tolerate empty proc status values Signed-off-by: Julie Yaunches --- test/e2e/live/openshell-exact-main-exec.ts | 10 ++++--- ...enshell-exact-main-child-contracts.test.ts | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-exec.ts b/test/e2e/live/openshell-exact-main-exec.ts index d4414ead56..7fb8f961f8 100644 --- a/test/e2e/live/openshell-exact-main-exec.ts +++ b/test/e2e/live/openshell-exact-main-exec.ts @@ -42,8 +42,9 @@ proc = matches[0] status = {} for line in (proc / "status").read_text(encoding="utf-8").splitlines(): name, separator, value = line.partition(":") - if separator: - status[name] = value.strip().split()[0] + fields = value.strip().split() + if separator and fields: + status[name] = fields[0] cap_bnd = status.get("CapBnd", "") if not cap_bnd or set(cap_bnd) != {"0"}: raise SystemExit(f"entrypoint retained capability bounding set {cap_bnd or ''}") @@ -69,8 +70,9 @@ tls_names = {${SUPERVISOR_TLS_ENV_NAMES.map((name) => JSON.stringify(name)).join status = {} for line in Path("/proc/self/status").read_text(encoding="utf-8").splitlines(): name, separator, value = line.partition(":") - if separator: - status[name] = value.strip().split()[0] + fields = value.strip().split() + if separator and fields: + status[name] = fields[0] cap_bnd = status.get("CapBnd", "") if not cap_bnd or set(cap_bnd) != {"0"}: raise SystemExit(f"exec child retained capability bounding set {cap_bnd or ''}") diff --git a/test/e2e/support/openshell-exact-main-child-contracts.test.ts b/test/e2e/support/openshell-exact-main-child-contracts.test.ts index 6e2e28d3bf..a624ecbfeb 100644 --- a/test/e2e/support/openshell-exact-main-child-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-child-contracts.test.ts @@ -2,6 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 import { spawnSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import path from "node:path"; import { describe, expect, it, vi } from "vitest"; @@ -61,6 +64,7 @@ describe("OpenShell exact-main child contracts", () => { { encoding: "utf8" }, ); expect(compiled.status, compiled.stderr).toBe(0); + expect(source).not.toContain("value.strip().split()[0]"); } const parsed = spawnSync("bash", ["-n"], { encoding: "utf8", @@ -69,6 +73,28 @@ describe("OpenShell exact-main child contracts", () => { expect(parsed.status, parsed.stderr).toBe(0); }); + it("parses proc status entries with an empty value", () => { + const directory = mkdtempSync(path.join(tmpdir(), "nemoclaw-proc-status-")); + const statusPath = path.join(directory, "status"); + try { + writeFileSync(statusPath, "Name:\tpython3\nGroups:\t\nCapBnd:\t0000000000000000\n", "utf8"); + const source = EXEC_CHILD_PROBE.replace( + 'Path("/proc/self/status")', + `Path(${JSON.stringify(statusPath)})`, + ); + const executed = spawnSync("python3", ["-c", source], { encoding: "utf8" }); + + expect(executed.status, executed.stderr).toBe(0); + expect(JSON.parse(executed.stdout)).toEqual({ + capBnd: "0000000000000000", + supervisorTlsEnvNames: [], + surface: "exec", + }); + } finally { + rmSync(directory, { force: true, recursive: true }); + } + }); + it("proves entrypoint, exec, and forced-TTY connect children independently", async () => { const { command, host, nemoclaw } = fakeHost(); command From 831431779d6aae3d5fa07344f0de480a413387bd Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 22:41:08 -0400 Subject: [PATCH 05/17] test(e2e): accept u64 policy config revisions Signed-off-by: Julie Yaunches --- test/e2e/live/openshell-exact-main-runtime-contracts.ts | 5 ++++- .../openshell-exact-main-runtime-contracts.test.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index a5d5cbc342..ddd9a006ca 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -73,7 +73,10 @@ export function parseExactMainPolicyStatus(raw: string): ExactMainPolicyStatus { if (!isRecord(parsed)) throw new Error("exact-main policy status must be a JSON object"); const configRevision = parsed.config_revision; const policySource = parsed.policy_source; - if (configRevision !== undefined && !Number.isSafeInteger(configRevision)) { + if ( + configRevision !== undefined && + (typeof configRevision !== "number" || !Number.isInteger(configRevision)) + ) { throw new Error("exact-main policy config_revision must be an integer when present"); } if (policySource !== undefined && typeof policySource !== "string") { diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index fe3ee384e6..ed907a9a3e 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -115,6 +115,15 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", ).toMatchObject({ activeVersion: 17, status: "loaded", version: 17 }); }); + it("accepts OpenShell unsigned 64-bit config revisions as opaque JSON integers", () => { + const status = parseExactMainPolicyStatus( + '{"active_version":2,"config_revision":7692118364955054884,"hash":"sha256:effective","policy_source":"sandbox","sandbox":"e2e-mcp-dcode","status":"effective","version":2}', + ); + + expect(status.configRevision).toSatisfy(Number.isInteger); + expect(Number.isSafeInteger(status.configRevision)).toBe(false); + }); + it("requires the policy-accept chain, proxy/loopback accepts, and all four L4 rejects", () => { expect(inspectExactMainNftRuleset(JSON.stringify(completeRuleset()))).toEqual({ chainPolicy: "accept", From f09629edc4b3e99ca99828942eb7278b2c1e43fc Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 22:55:53 -0400 Subject: [PATCH 06/17] test(e2e): support sidecar nft topology Signed-off-by: Julie Yaunches --- .../openshell-exact-main-runtime-contracts.ts | 29 ++++++++++++++----- ...shell-exact-main-runtime-contracts.test.ts | 12 ++++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index ddd9a006ca..9336dc80ea 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -497,20 +497,35 @@ async function findSandboxContainer( return ids[0] ?? ""; } +export function buildExactMainNftInspectionScript(): string { + return [ + "namespace_count=0", + "namespace_path=", + "for candidate in /var/run/netns/sandbox-*; do", + ' [ -e "$candidate" ] || continue', + " namespace_count=$((namespace_count + 1))", + ' namespace_path="$candidate"', + "done", + 'if [ "$namespace_count" -gt 1 ]; then', + " printf 'expected at most one sandbox netns, got %s\\n' \"$namespace_count\" >&2", + " ip netns list >&2 || true", + " exit 1", + "fi", + 'if [ "$namespace_count" -eq 1 ]; then', + ' exec nsenter --net="$namespace_path" -- nft -j list table inet openshell_bypass', + "fi", + "exec nft -j list table inet openshell_bypass", + ].join("\n"); +} + async function inspectNftRules( host: HostCliClient, containerId: string, artifactName: string, ): Promise<{ inspection: ExactMainNftInspection; raw: string }> { - const script = [ - "set -- /var/run/netns/sandbox-*", - '[ "$#" -eq 1 ] && [ -e "$1" ] || { printf \'expected exactly one sandbox netns\\n\' >&2; exit 1; }', - "namespace=${1##*/}", - 'exec ip netns exec "$namespace" nft -j list table inet openshell_bypass', - ].join("\n"); const result = await host.command( "docker", - ["exec", "--user", "0", containerId, "sh", "-ceu", script], + ["exec", "--user", "0", containerId, "sh", "-ceu", buildExactMainNftInspectionScript()], { artifactName, env: buildAvailabilityProbeEnv(), timeoutMs: 30_000 }, ); expectExitZero(result, "inspect exact-main nft bypass rules"); diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index ed907a9a3e..444d362aa6 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -7,6 +7,7 @@ import { describe, expect, it } from "vitest"; import { buildExactMainLiveExeIdentityScript, + buildExactMainNftInspectionScript, inspectExactMainNftRuleset, parseExactMainPolicyStatus, } from "../live/openshell-exact-main-runtime-contracts.ts"; @@ -134,6 +135,17 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", }); }); + it("inspects nft rules in either supported OpenShell network topology", () => { + const script = buildExactMainNftInspectionScript(); + const syntax = spawnSync("sh", ["-n"], { encoding: "utf8", input: script }); + + expect(syntax.status, syntax.stderr).toBe(0); + expect(script).toContain("/var/run/netns/sandbox-*"); + expect(script).toContain('nsenter --net="$namespace_path"'); + expect(script).toContain("exec nft -j list table inet openshell_bypass"); + expect(script).toContain('if [ "$namespace_count" -gt 1 ]'); + }); + it("rejects the feasible sequential-install partial state instead of calling it green", () => { const partial = completeRuleset(); const entries = partial.nftables as Array>; From dd6fb6d13ba5ae2860d13c7acb877b28b51bc8af Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 23:03:54 -0400 Subject: [PATCH 07/17] test(e2e): select active sandbox netns Signed-off-by: Julie Yaunches --- .../live/openshell-exact-main-runtime-contracts.ts | 12 +++++++----- .../openshell-exact-main-runtime-contracts.test.ts | 3 ++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index 9336dc80ea..c9b90797ab 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -499,19 +499,21 @@ async function findSandboxContainer( export function buildExactMainNftInspectionScript(): string { return [ - "namespace_count=0", + "active_namespace_count=0", "namespace_path=", "for candidate in /var/run/netns/sandbox-*; do", ' [ -e "$candidate" ] || continue', - " namespace_count=$((namespace_count + 1))", + " namespace=${candidate##*/}", + ' [ -n "$(ip netns pids "$namespace" 2>/dev/null)" ] || continue', + " active_namespace_count=$((active_namespace_count + 1))", ' namespace_path="$candidate"', "done", - 'if [ "$namespace_count" -gt 1 ]; then', - " printf 'expected at most one sandbox netns, got %s\\n' \"$namespace_count\" >&2", + 'if [ "$active_namespace_count" -gt 1 ]; then', + " printf 'expected at most one active sandbox netns, got %s\\n' \"$active_namespace_count\" >&2", " ip netns list >&2 || true", " exit 1", "fi", - 'if [ "$namespace_count" -eq 1 ]; then', + 'if [ "$active_namespace_count" -eq 1 ]; then', ' exec nsenter --net="$namespace_path" -- nft -j list table inet openshell_bypass', "fi", "exec nft -j list table inet openshell_bypass", diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index 444d362aa6..ad14978f19 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -141,9 +141,10 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", expect(syntax.status, syntax.stderr).toBe(0); expect(script).toContain("/var/run/netns/sandbox-*"); + expect(script).toContain('ip netns pids "$namespace" 2>/dev/null'); expect(script).toContain('nsenter --net="$namespace_path"'); expect(script).toContain("exec nft -j list table inet openshell_bypass"); - expect(script).toContain('if [ "$namespace_count" -gt 1 ]'); + expect(script).toContain('if [ "$active_namespace_count" -gt 1 ]'); }); it("rejects the feasible sequential-install partial state instead of calling it green", () => { From 9bba6ce5861bd9ec0d104743138f2f61e5b8c833 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 23:10:56 -0400 Subject: [PATCH 08/17] test(e2e): resolve nft before entering netns Signed-off-by: Julie Yaunches --- test/e2e/live/openshell-exact-main-runtime-contracts.ts | 5 +++-- .../support/openshell-exact-main-runtime-contracts.test.ts | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index c9b90797ab..0eaa37302b 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -499,6 +499,7 @@ async function findSandboxContainer( export function buildExactMainNftInspectionScript(): string { return [ + "nft_path=$(command -v nft)", "active_namespace_count=0", "namespace_path=", "for candidate in /var/run/netns/sandbox-*; do", @@ -514,9 +515,9 @@ export function buildExactMainNftInspectionScript(): string { " exit 1", "fi", 'if [ "$active_namespace_count" -eq 1 ]; then', - ' exec nsenter --net="$namespace_path" -- nft -j list table inet openshell_bypass', + ' exec nsenter --net="$namespace_path" -- "$nft_path" -j list table inet openshell_bypass', "fi", - "exec nft -j list table inet openshell_bypass", + 'exec "$nft_path" -j list table inet openshell_bypass', ].join("\n"); } diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index ad14978f19..c83b20c1e3 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -140,10 +140,11 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", const syntax = spawnSync("sh", ["-n"], { encoding: "utf8", input: script }); expect(syntax.status, syntax.stderr).toBe(0); + expect(script).toContain("nft_path=$(command -v nft)"); expect(script).toContain("/var/run/netns/sandbox-*"); expect(script).toContain('ip netns pids "$namespace" 2>/dev/null'); - expect(script).toContain('nsenter --net="$namespace_path"'); - expect(script).toContain("exec nft -j list table inet openshell_bypass"); + expect(script).toContain('nsenter --net="$namespace_path" -- "$nft_path"'); + expect(script).toContain('exec "$nft_path" -j list table inet openshell_bypass'); expect(script).toContain('if [ "$active_namespace_count" -gt 1 ]'); }); From 37cccee1eb802d3b7839d4b12ba7d92ce7732b1d Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 23:18:21 -0400 Subject: [PATCH 09/17] test(e2e): use trusted nft search paths Signed-off-by: Julie Yaunches --- .../live/openshell-exact-main-runtime-contracts.ts | 14 +++++++++----- .../openshell-exact-main-runtime-contracts.test.ts | 3 ++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index 0eaa37302b..21c4f5463a 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -499,15 +499,19 @@ async function findSandboxContainer( export function buildExactMainNftInspectionScript(): string { return [ - "nft_path=$(command -v nft)", + "nft_path=", + "for candidate in /usr/sbin/nft /sbin/nft /usr/bin/nft; do", + ' if [ -x "$candidate" ]; then nft_path="$candidate"; break; fi', + "done", + "[ -n \"$nft_path\" ] || { printf 'nft executable not found\\n' >&2; exit 127; }", "active_namespace_count=0", "namespace_path=", - "for candidate in /var/run/netns/sandbox-*; do", - ' [ -e "$candidate" ] || continue', - " namespace=${candidate##*/}", + "for namespace_candidate in /var/run/netns/sandbox-*; do", + ' [ -e "$namespace_candidate" ] || continue', + " namespace=${namespace_candidate##*/}", ' [ -n "$(ip netns pids "$namespace" 2>/dev/null)" ] || continue', " active_namespace_count=$((active_namespace_count + 1))", - ' namespace_path="$candidate"', + ' namespace_path="$namespace_candidate"', "done", 'if [ "$active_namespace_count" -gt 1 ]; then', " printf 'expected at most one active sandbox netns, got %s\\n' \"$active_namespace_count\" >&2", diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index c83b20c1e3..fcad14d99a 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -140,7 +140,8 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", const syntax = spawnSync("sh", ["-n"], { encoding: "utf8", input: script }); expect(syntax.status, syntax.stderr).toBe(0); - expect(script).toContain("nft_path=$(command -v nft)"); + expect(script).toContain("/usr/sbin/nft /sbin/nft /usr/bin/nft"); + expect(script).toContain("nft executable not found"); expect(script).toContain("/var/run/netns/sandbox-*"); expect(script).toContain('ip netns pids "$namespace" 2>/dev/null'); expect(script).toContain('nsenter --net="$namespace_path" -- "$nft_path"'); From ed33c39518b40758ea0b4d54ae2364977d9e9445 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 23:26:11 -0400 Subject: [PATCH 10/17] fix(images): install nftables for bypass enforcement Signed-off-by: Julie Yaunches --- Dockerfile.base | 1 + agents/hermes/Dockerfile.base | 1 + agents/langchain-deepagents-code/Dockerfile.base | 1 + test/sandbox-provisioning.test.ts | 16 ++++++++++++++++ 4 files changed, 19 insertions(+) diff --git a/Dockerfile.base b/Dockerfile.base index 224879013d..b96754c334 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -68,6 +68,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates=20250419 \ iproute2=6.15.0-1 \ iptables=1.8.11-2 \ + nftables=1.1.3-1 \ libcap2-bin=1:2.75-10+deb13u1+b1 \ procps=2:4.0.4-9 \ e2fsprogs=1.47.2-3+b11 \ diff --git a/agents/hermes/Dockerfile.base b/agents/hermes/Dockerfile.base index 02ba8dd7e9..a94daeeaca 100644 --- a/agents/hermes/Dockerfile.base +++ b/agents/hermes/Dockerfile.base @@ -50,6 +50,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates=20250419 \ iproute2=6.15.0-1 \ iptables=1.8.11-2 \ + nftables=1.1.3-1 \ libcap2-bin=1:2.75-10+deb13u1+b1 \ procps=2:4.0.4-9 \ e2fsprogs=1.47.2-3+b11 \ diff --git a/agents/langchain-deepagents-code/Dockerfile.base b/agents/langchain-deepagents-code/Dockerfile.base index d89c5a29d2..4fa8f07ba3 100644 --- a/agents/langchain-deepagents-code/Dockerfile.base +++ b/agents/langchain-deepagents-code/Dockerfile.base @@ -22,6 +22,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates=20250419 \ iproute2=6.15.0-1 \ iptables=1.8.11-2 \ + nftables=1.1.3-1 \ libcap2-bin=1:2.75-10+deb13u1+b1 \ procps=2:4.0.4-9 \ e2fsprogs=1.47.2-3+b11 \ diff --git a/test/sandbox-provisioning.test.ts b/test/sandbox-provisioning.test.ts index 69301702e9..d20fc871a2 100644 --- a/test/sandbox-provisioning.test.ts +++ b/test/sandbox-provisioning.test.ts @@ -21,6 +21,12 @@ const DOCKERFILE_BASE = path.join(ROOT, "Dockerfile.base"); const DOCKERFILE_SANDBOX = path.join(ROOT, "test", "Dockerfile.sandbox"); const HERMES_DOCKERFILE = path.join(ROOT, "agents", "hermes", "Dockerfile"); const HERMES_DOCKERFILE_BASE = path.join(ROOT, "agents", "hermes", "Dockerfile.base"); +const DEEPAGENTS_DOCKERFILE_BASE = path.join( + ROOT, + "agents", + "langchain-deepagents-code", + "Dockerfile.base", +); function dockerRunCommandBetween( dockerfile: string, @@ -1026,6 +1032,16 @@ describe("sandbox provisioning: unified .openclaw layout (#2227)", () => { }); describe("sandbox provisioning: base runtime tools", () => { + it.each([ + ["OpenClaw", DOCKERFILE_BASE], + ["Hermes", HERMES_DOCKERFILE_BASE], + ["Deep Agents Code", DEEPAGENTS_DOCKERFILE_BASE], + ])("installs pinned nftables for OpenShell bypass enforcement in %s", (_agent, file) => { + const dockerfile = fs.readFileSync(file, "utf-8"); + + expect(dockerfile).toContain("nftables=1.1.3-1"); + }); + it("base apt layer requests procps, e2fsprogs, and the SFTP server", () => { const dockerfile = fs.readFileSync(DOCKERFILE_BASE, "utf-8"); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-base-apt-")); From 23cd4b7e216181f24f0175eb9bc50185c5550b8a Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 23:34:21 -0400 Subject: [PATCH 11/17] test(e2e): parse nft reject family output Signed-off-by: Julie Yaunches --- .../openshell-exact-main-runtime-contracts.ts | 17 ++++++++--------- ...enshell-exact-main-runtime-contracts.test.ts | 1 - 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index 21c4f5463a..48486e65e0 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -100,13 +100,6 @@ function containsObject(value: unknown, predicate: (candidate: JsonRecord) => bo return Object.values(value).some((entry) => containsObject(entry, predicate)); } -function containsScalar(value: unknown, expected: string | number): boolean { - if (value === expected) return true; - if (Array.isArray(value)) return value.some((entry) => containsScalar(entry, expected)); - if (!isRecord(value)) return false; - return Object.values(value).some((entry) => containsScalar(entry, expected)); -} - function hasVerdict(rule: JsonRecord, verdict: "accept" | "reject"): boolean { return containsObject(rule, (candidate) => Object.hasOwn(candidate, verdict)); } @@ -128,11 +121,17 @@ function nftRuleMatchesReject( family: "ipv4" | "ipv6", protocol: "tcp" | "udp", ): boolean { + const rejectType = family === "ipv4" ? "icmp" : "icmpv6"; return ( hasVerdict(rule, "reject") && - hasMatch(rule, { key: "nfproto" }, family) && hasMatch(rule, { key: "l4proto" }, protocol) && - containsScalar(rule, "port-unreachable") + containsObject( + rule, + (candidate) => + isRecord(candidate.reject) && + candidate.reject.type === rejectType && + candidate.reject.expr === "port-unreachable", + ) ); } diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index fcad14d99a..7e9530b9c2 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -21,7 +21,6 @@ function rejectRule(family: "ipv4" | "ipv6", protocol: "tcp" | "udp") { rule: { chain: "output", expr: [ - match({ meta: { key: "nfproto" } }, family), match({ meta: { key: "l4proto" } }, protocol), { reject: { From 7dc9fd80ccb58444e3c6c486d1a416d86f9f6bdb Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 23:41:09 -0400 Subject: [PATCH 12/17] test(e2e): accept immediate bypass denial errno Signed-off-by: Julie Yaunches --- test/e2e/live/openshell-exact-main-runtime-contracts.ts | 5 +++-- .../openshell-exact-main-runtime-contracts.test.ts | 8 ++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index 48486e65e0..aa9d6fadd0 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -422,7 +422,7 @@ while [ ! -s "$ready" ]; do done cat "$ready"`; -const DIRECT_BYPASS_PROBE_CODE = String.raw`import errno +export const DIRECT_BYPASS_PROBE_CODE = String.raw`import errno import json import socket import sys @@ -466,7 +466,8 @@ tcp_error = result["tcp"]["error"] or {} udp_error = result["udp"]["error"] or {} if result["tcp"]["connected"] or result["udp"]["echoed"]: raise SystemExit(1) -if tcp_error.get("errno") != errno.ECONNREFUSED or udp_error.get("errno") != errno.ECONNREFUSED: +denial_errnos = {errno.ECONNREFUSED, errno.EPERM} +if tcp_error.get("errno") not in denial_errnos or udp_error.get("errno") not in denial_errnos: raise SystemExit(1) if result["tcp"]["elapsedMs"] >= 2500 or result["udp"]["elapsedMs"] >= 2500: raise SystemExit(1)`; diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index 7e9530b9c2..438f8bef18 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -8,6 +8,7 @@ import { describe, expect, it } from "vitest"; import { buildExactMainLiveExeIdentityScript, buildExactMainNftInspectionScript, + DIRECT_BYPASS_PROBE_CODE, inspectExactMainNftRuleset, parseExactMainPolicyStatus, } from "../live/openshell-exact-main-runtime-contracts.ts"; @@ -148,6 +149,13 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", expect(script).toContain('if [ "$active_namespace_count" -gt 1 ]'); }); + it("accepts immediate refused and permission-denied bypass outcomes", () => { + expect(DIRECT_BYPASS_PROBE_CODE).toContain("denial_errnos = {errno.ECONNREFUSED, errno.EPERM}"); + expect(DIRECT_BYPASS_PROBE_CODE).toContain( + 'tcp_error.get("errno") not in denial_errnos or udp_error.get("errno") not in denial_errnos', + ); + }); + it("rejects the feasible sequential-install partial state instead of calling it green", () => { const partial = completeRuleset(); const entries = partial.nftables as Array>; From d3cf8bb39749f57442db5906902f02441be41678 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Fri, 17 Jul 2026 23:58:19 -0400 Subject: [PATCH 13/17] test(e2e): preserve exact binaries on rebuild Signed-off-by: Julie Yaunches --- test/e2e/live/mcp-bridge-onboard-env.ts | 25 +++++++++++------ test/e2e/live/mcp-bridge.test.ts | 12 ++++++-- .../support/mcp-bridge-onboard-env.test.ts | 28 ++++++++++++++++++- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/test/e2e/live/mcp-bridge-onboard-env.ts b/test/e2e/live/mcp-bridge-onboard-env.ts index 1ef3ef1e9a..6b210bd8e0 100644 --- a/test/e2e/live/mcp-bridge-onboard-env.ts +++ b/test/e2e/live/mcp-bridge-onboard-env.ts @@ -10,24 +10,34 @@ const EXACT_MAIN_OVERLAY_KEYS = new Set([ "NEMOCLAW_OPENSHELL_SANDBOX_BIN", ]); -export function buildMcpBridgeOnboardEnv(options: { - agent: "openclaw" | "hermes" | "langchain-deepagents-code"; +export function buildMcpBridgeExactMainEnv(options: { baseEnv?: NodeJS.ProcessEnv; - compatibleKey: string; - compatibleModel: string; - endpointUrl: string; envOverlay?: NodeJS.ProcessEnv; - sandboxName: string; }): NodeJS.ProcessEnv { const envOverlay = options.envOverlay ?? {}; for (const key of Object.keys(envOverlay)) { if (!EXACT_MAIN_OVERLAY_KEYS.has(key)) { - throw new Error(`MCP exact-main onboarding does not allow env overlay key '${key}'`); + throw new Error(`MCP exact-main command does not allow env overlay key '${key}'`); } } return { ...buildAvailabilityProbeEnv(options.baseEnv), + ...envOverlay, + }; +} + +export function buildMcpBridgeOnboardEnv(options: { + agent: "openclaw" | "hermes" | "langchain-deepagents-code"; + baseEnv?: NodeJS.ProcessEnv; + compatibleKey: string; + compatibleModel: string; + endpointUrl: string; + envOverlay?: NodeJS.ProcessEnv; + sandboxName: string; +}): NodeJS.ProcessEnv { + return { + ...buildMcpBridgeExactMainEnv(options), COMPATIBLE_API_KEY: options.compatibleKey, NVIDIA_INFERENCE_API_KEY: options.compatibleKey, NEMOCLAW_AGENT: options.agent, @@ -38,6 +48,5 @@ export function buildMcpBridgeOnboardEnv(options: { NEMOCLAW_PROVIDER: "custom", NEMOCLAW_SANDBOX_NAME: options.sandboxName, NEMOCLAW_RECREATE_SANDBOX: "1", - ...envOverlay, }; } diff --git a/test/e2e/live/mcp-bridge.test.ts b/test/e2e/live/mcp-bridge.test.ts index 2a217dc77a..0fcc16823b 100644 --- a/test/e2e/live/mcp-bridge.test.ts +++ b/test/e2e/live/mcp-bridge.test.ts @@ -28,7 +28,7 @@ import { assertHermesInspectionRejectsUnmanagedFields, assertHermesRemovalSurvivesGatewayRestart, } from "./mcp-bridge-hermes-lifecycle.ts"; -import { buildMcpBridgeOnboardEnv } from "./mcp-bridge-onboard-env.ts"; +import { buildMcpBridgeExactMainEnv, buildMcpBridgeOnboardEnv } from "./mcp-bridge-onboard-env.ts"; import { retryAfterHermesRestartTransportFailure } from "./mcp-bridge-reliability.ts"; import { buildMcpDnsRebindingProbeScript, @@ -827,11 +827,12 @@ async function rebuildWithoutMcpHostSecret( host: HostCliClient, sandboxName: string, artifactPrefix: string, + envOverlay: NodeJS.ProcessEnv = {}, ): Promise { const rebuild = await host.nemoclaw([sandboxName, "rebuild", "--yes"], { artifactName: `${artifactPrefix}-rebuild-with-provider-backed-mcp`, env: { - ...buildAvailabilityProbeEnv(), + ...buildMcpBridgeExactMainEnv({ envOverlay }), COMPATIBLE_API_KEY: COMPATIBLE_KEY, NVIDIA_INFERENCE_API_KEY: COMPATIBLE_KEY, }, @@ -1439,7 +1440,12 @@ mcpBridgeShardTest("deepagents")( [HOST_SECRET, ROTATED_HOST_SECRET], "deepagents-assert-secrets-absent-after-rotation", ); - await rebuildWithoutMcpHostSecret(host, DEEPAGENTS_SANDBOX_NAME, "deepagents"); + await rebuildWithoutMcpHostSecret( + host, + DEEPAGENTS_SANDBOX_NAME, + "deepagents", + exactMainProof.envOverlay, + ); await exactMainProof.afterRebuild(); await assertDeepAgentsConfig(sandbox, DEEPAGENTS_SANDBOX_NAME, mcpUrl); await assertSecretAbsentFromSandbox( diff --git a/test/e2e/support/mcp-bridge-onboard-env.test.ts b/test/e2e/support/mcp-bridge-onboard-env.test.ts index abc1008074..b3b04464f2 100644 --- a/test/e2e/support/mcp-bridge-onboard-env.test.ts +++ b/test/e2e/support/mcp-bridge-onboard-env.test.ts @@ -3,7 +3,10 @@ import { describe, expect, it } from "vitest"; -import { buildMcpBridgeOnboardEnv } from "../live/mcp-bridge-onboard-env.ts"; +import { + buildMcpBridgeExactMainEnv, + buildMcpBridgeOnboardEnv, +} from "../live/mcp-bridge-onboard-env.ts"; const ONBOARD_OPTIONS = { agent: "langchain-deepagents-code" as const, @@ -15,6 +18,29 @@ const ONBOARD_OPTIONS = { }; describe("MCP bridge onboarding environment", () => { + it("restores exact-main OpenShell overrides after child environment sanitization", () => { + const env = buildMcpBridgeExactMainEnv({ + baseEnv: { + HOME: "/tmp/home", + PATH: "/usr/bin", + NEMOCLAW_OPENSHELL_BIN: "/dropped/openshell", + }, + envOverlay: { + PATH: "/tmp/exact-main:/usr/bin", + NEMOCLAW_OPENSHELL_BIN: "/tmp/exact-main/openshell", + NEMOCLAW_OPENSHELL_GATEWAY_BIN: "/usr/local/bin/openshell-gateway", + NEMOCLAW_OPENSHELL_SANDBOX_BIN: "/usr/local/bin/openshell-sandbox", + }, + }); + + expect(env).toMatchObject({ + PATH: "/tmp/exact-main:/usr/bin", + NEMOCLAW_OPENSHELL_BIN: "/tmp/exact-main/openshell", + NEMOCLAW_OPENSHELL_GATEWAY_BIN: "/usr/local/bin/openshell-gateway", + NEMOCLAW_OPENSHELL_SANDBOX_BIN: "/usr/local/bin/openshell-sandbox", + }); + }); + it("passes only exact-main OpenShell overrides after fixed onboarding values", () => { const env = buildMcpBridgeOnboardEnv({ ...ONBOARD_OPTIONS, From 798e84836cd1b12a586405044f9e9dc27c84e00f Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Sat, 18 Jul 2026 00:12:46 -0400 Subject: [PATCH 14/17] test(e2e): wait for live executable identity Signed-off-by: Julie Yaunches --- .../openshell-exact-main-runtime-contracts.ts | 18 ++++++++++++------ ...nshell-exact-main-runtime-contracts.test.ts | 5 ++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index aa9d6fadd0..5bbad99655 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -278,7 +278,15 @@ connect_status() { fi printf '%s\n' "$status" } -connect_status > "$first_result" +first_status=000 +first_attempt=0 +while [ "$first_attempt" -lt 50 ]; do + first_attempt=$((first_attempt + 1)) + first_status=$(connect_status) + [ "$first_status" = 200 ] && break + sleep 0.1 +done +printf '%s\n' "$first_status" > "$first_result" IFS= read -r _ < "$trigger" connect_status > "$second_result"`; @@ -342,7 +350,7 @@ export function buildExactMainLiveExeIdentityScript(mcpUrl: string): string { "}", 'wait_for_result "$first"', "old_first=$(tr -d '\\r\\n' < \"$first\")", - '[ "$old_first" = 200 ]', + '[ "$old_first" = 200 ] || { printf \'expected initial live-exe CONNECT 200, got %s\\n\' "$old_first" >&2; exit 1; }', 'replacement="$root/live-bash.next"', 'cp "$live" "$replacement"', "printf '\\nNEMOCLAW_EXACT_MAIN_REPLACEMENT\\n' >> \"$replacement\"", @@ -355,7 +363,7 @@ export function buildExactMainLiveExeIdentityScript(mcpUrl: string): string { "printf 'go\\n' > \"$trigger\"", 'wait_for_result "$second"', "old_second=$(tr -d '\\r\\n' < \"$second\")", - '[ "$old_second" = 200 ]', + '[ "$old_second" = 200 ] || { printf \'expected retained live-exe CONNECT 200, got %s\\n\' "$old_second" >&2; exit 1; }', 'wait "$old_pid"', 'old_pid=""', `new_status=$("$live" -c "$oneshot_script" exact-main-new ${shellQuote(endpoint.hostname)} ${endpointPort} | tr -d '\\r\\n')`, @@ -365,9 +373,7 @@ export function buildExactMainLiveExeIdentityScript(mcpUrl: string): string { "printf 'NEMOCLAW_LIVE_EXE_OLD_LINK=%s\\n' \"$old_exe\"", "printf 'NEMOCLAW_LIVE_EXE_OLD_HASH=%s\\n' \"$old_hash\"", "printf 'NEMOCLAW_LIVE_EXE_NEW_HASH=%s\\n' \"$new_hash\"", - '[ "$old_first" = 200 ]', - '[ "$old_second" = 200 ]', - '[ "$new_status" = 403 ]', + '[ "$new_status" = 403 ] || { printf \'expected replacement live-exe CONNECT 403, got %s\\n\' "$new_status" >&2; exit 1; }', ].join("\n"); } diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index 438f8bef18..ba7ef78b7f 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -174,6 +174,9 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", expect(script).toContain("NEMOCLAW_LIVE_EXE_OLD_FIRST=%s"); expect(script).toContain("NEMOCLAW_LIVE_EXE_OLD_SECOND=%s"); expect(script).toContain("NEMOCLAW_LIVE_EXE_NEW=%s"); - expect(script).toContain('[ "$new_status" = 403 ]'); + expect(script).toContain('while [ "$first_attempt" -lt 50 ]'); + expect(script).toContain('[ "$first_status" = 200 ] && break'); + expect(script).toContain("expected initial live-exe CONNECT 200"); + expect(script).toContain("expected replacement live-exe CONNECT 403"); }); }); From 326f7cf5e6c034385d4b25173497dec92b95edc1 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Sat, 18 Jul 2026 00:20:24 -0400 Subject: [PATCH 15/17] test(e2e): retry retained executable probe Signed-off-by: Julie Yaunches --- .../openshell-exact-main-runtime-contracts.ts | 24 +++++++++++-------- ...shell-exact-main-runtime-contracts.test.ts | 6 +++-- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index 5bbad99655..71249b809c 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -278,17 +278,21 @@ connect_status() { fi printf '%s\n' "$status" } -first_status=000 -first_attempt=0 -while [ "$first_attempt" -lt 50 ]; do - first_attempt=$((first_attempt + 1)) - first_status=$(connect_status) - [ "$first_status" = 200 ] && break - sleep 0.1 -done -printf '%s\n' "$first_status" > "$first_result" +connect_until_allowed() { + result_path=$1 + allowed_status=000 + allowed_attempt=0 + while [ "$allowed_attempt" -lt 50 ]; do + allowed_attempt=$((allowed_attempt + 1)) + allowed_status=$(connect_status) + [ "$allowed_status" = 200 ] && break + sleep 0.1 + done + printf '%s\n' "$allowed_status" > "$result_path" +} +connect_until_allowed "$first_result" IFS= read -r _ < "$trigger" -connect_status > "$second_result"`; +connect_until_allowed "$second_result"`; const LIVE_EXE_ONESHOT_SCRIPT = String.raw`set -eu endpoint_host=$1 diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index ba7ef78b7f..2be645edac 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -174,8 +174,10 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", expect(script).toContain("NEMOCLAW_LIVE_EXE_OLD_FIRST=%s"); expect(script).toContain("NEMOCLAW_LIVE_EXE_OLD_SECOND=%s"); expect(script).toContain("NEMOCLAW_LIVE_EXE_NEW=%s"); - expect(script).toContain('while [ "$first_attempt" -lt 50 ]'); - expect(script).toContain('[ "$first_status" = 200 ] && break'); + expect(script).toContain('while [ "$allowed_attempt" -lt 50 ]'); + expect(script).toContain('[ "$allowed_status" = 200 ] && break'); + expect(script).toContain('connect_until_allowed "$first_result"'); + expect(script).toContain('connect_until_allowed "$second_result"'); expect(script).toContain("expected initial live-exe CONNECT 200"); expect(script).toContain("expected replacement live-exe CONNECT 403"); }); From 3d6f5d29d1452611c4308835b1e783100b8ede17 Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Sat, 18 Jul 2026 00:54:35 -0400 Subject: [PATCH 16/17] test(e2e): tighten live proof assertions Signed-off-by: Julie Yaunches --- .../openshell-exact-main-driver-config.ts | 26 +++++++++++++++++-- .../openshell-exact-main-runtime-contracts.ts | 18 ++++++++----- ...shell-exact-main-runtime-contracts.test.ts | 7 +++-- test/sandbox-provisioning.test.ts | 21 ++++++++++++++- 4 files changed, 58 insertions(+), 14 deletions(-) diff --git a/test/e2e/live/openshell-exact-main-driver-config.ts b/test/e2e/live/openshell-exact-main-driver-config.ts index 8f8cb633cb..fdd716a9aa 100644 --- a/test/e2e/live/openshell-exact-main-driver-config.ts +++ b/test/e2e/live/openshell-exact-main-driver-config.ts @@ -91,6 +91,12 @@ type DockerMount = { Type?: unknown; }; +type ConfiguredDockerMount = { + Target?: unknown; + TmpfsOptions?: unknown; + Type?: unknown; +}; + type RuntimeSnapshot = { bridgeAddress: string; config: ParsedGatewayConfig; @@ -345,18 +351,34 @@ async function assertRuntimeMounts( phase: string, ): Promise { const components = proof.components!; - await inspectJson( + const configuredMounts = await inspectJson( host, containerId, "{{json .HostConfig.Mounts}}", `exact-main-driver-configured-mounts-${phase}`, ); - await inspectJson( + expect(Array.isArray(configuredMounts), "Docker HostConfig.Mounts must be structured").toBe(true); + expect(configuredMounts?.find((mount) => mount.Target === EXACT_MAIN_TMPFS_TARGET)).toMatchObject( + { + Type: "tmpfs", + Target: EXACT_MAIN_TMPFS_TARGET, + TmpfsOptions: { + Mode: EXACT_MAIN_TMPFS_MOUNT.mode, + SizeBytes: EXACT_MAIN_TMPFS_MOUNT.size_bytes, + }, + }, + ); + const configuredTmpfs = await inspectJson( host, containerId, "{{json .HostConfig.Tmpfs}}", `exact-main-driver-configured-tmpfs-${phase}`, ); + expect( + configuredTmpfs === null || + (isRecord(configuredTmpfs) && !Object.hasOwn(configuredTmpfs, EXACT_MAIN_TMPFS_TARGET)), + "the reviewed tmpfs must not use Docker's legacy HostConfig.Tmpfs path", + ).toBe(true); const mounts = await inspectJson( host, containerId, diff --git a/test/e2e/live/openshell-exact-main-runtime-contracts.ts b/test/e2e/live/openshell-exact-main-runtime-contracts.ts index 71249b809c..931a9df0f9 100644 --- a/test/e2e/live/openshell-exact-main-runtime-contracts.ts +++ b/test/e2e/live/openshell-exact-main-runtime-contracts.ts @@ -32,7 +32,7 @@ type JsonRecord = Record; export interface ExactMainPolicyStatus { activeVersion: number; - configRevision?: number; + configRevision?: string; hash: string; policySource?: string; sandbox: string; @@ -73,18 +73,22 @@ export function parseExactMainPolicyStatus(raw: string): ExactMainPolicyStatus { if (!isRecord(parsed)) throw new Error("exact-main policy status must be a JSON object"); const configRevision = parsed.config_revision; const policySource = parsed.policy_source; - if ( - configRevision !== undefined && - (typeof configRevision !== "number" || !Number.isInteger(configRevision)) - ) { - throw new Error("exact-main policy config_revision must be an integer when present"); + let exactConfigRevision: string | undefined; + if (configRevision !== undefined) { + const matches = [...raw.matchAll(/"config_revision"\s*:\s*(0|[1-9][0-9]*)(?=\s*[,}])/gu)]; + if (matches.length !== 1 || matches[0][1] === undefined) { + throw new Error( + "exact-main policy config_revision must be one non-negative JSON integer when present", + ); + } + exactConfigRevision = matches[0][1]; } if (policySource !== undefined && typeof policySource !== "string") { throw new Error("exact-main policy policy_source must be a string when present"); } return { activeVersion: requiredInteger(parsed, "active_version"), - ...(configRevision === undefined ? {} : { configRevision: Number(configRevision) }), + ...(exactConfigRevision === undefined ? {} : { configRevision: exactConfigRevision }), hash: requiredString(parsed, "hash"), ...(policySource === undefined ? {} : { policySource }), sandbox: requiredString(parsed, "sandbox"), diff --git a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts index 2be645edac..649a142d46 100644 --- a/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts +++ b/test/e2e/support/openshell-exact-main-runtime-contracts.test.ts @@ -96,7 +96,7 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", ), ).toEqual({ activeVersion: 17, - configRevision: 23, + configRevision: "23", hash: "sha256:effective", policySource: "sandbox", sandbox: "e2e-mcp-dcode", @@ -116,13 +116,12 @@ describe("OpenShell exact-main policy, nft, and process-identity proof helpers", ).toMatchObject({ activeVersion: 17, status: "loaded", version: 17 }); }); - it("accepts OpenShell unsigned 64-bit config revisions as opaque JSON integers", () => { + it("preserves OpenShell unsigned 64-bit config revisions as exact decimal strings", () => { const status = parseExactMainPolicyStatus( '{"active_version":2,"config_revision":7692118364955054884,"hash":"sha256:effective","policy_source":"sandbox","sandbox":"e2e-mcp-dcode","status":"effective","version":2}', ); - expect(status.configRevision).toSatisfy(Number.isInteger); - expect(Number.isSafeInteger(status.configRevision)).toBe(false); + expect(status.configRevision).toBe("7692118364955054884"); }); it("requires the policy-accept chain, proxy/loopback accepts, and all four L4 rejects", () => { diff --git a/test/sandbox-provisioning.test.ts b/test/sandbox-provisioning.test.ts index d20fc871a2..88fbde1b39 100644 --- a/test/sandbox-provisioning.test.ts +++ b/test/sandbox-provisioning.test.ts @@ -60,6 +60,21 @@ function dockerRunCommandBetween( .replace(/\\\n/g, " "); } +function dockerRunCommandStartingWith(dockerfile: string, startMarker: string): string { + const start = dockerfile.indexOf(startMarker); + if (start === -1) throw new Error(`Expected Dockerfile RUN instruction ${startMarker}`); + const runLines: string[] = []; + for (const line of dockerfile.slice(start).split("\n")) { + runLines.push(line); + if (!line.trimEnd().endsWith("\\")) break; + } + const command = runLines.join("\n"); + if (!command.startsWith("RUN ") || command.trimEnd().endsWith("\\")) { + throw new Error(`Expected complete Dockerfile RUN instruction ${startMarker}`); + } + return command; +} + function dockerHealthCommandBetween( dockerfile: string, startMarker: string, @@ -1038,8 +1053,12 @@ describe("sandbox provisioning: base runtime tools", () => { ["Deep Agents Code", DEEPAGENTS_DOCKERFILE_BASE], ])("installs pinned nftables for OpenShell bypass enforcement in %s", (_agent, file) => { const dockerfile = fs.readFileSync(file, "utf-8"); + const aptInstall = dockerRunCommandStartingWith( + dockerfile, + "RUN apt-get update && apt-get install -y --no-install-recommends", + ); - expect(dockerfile).toContain("nftables=1.1.3-1"); + expect(aptInstall).toContain("nftables=1.1.3-1"); }); it("base apt layer requests procps, e2fsprogs, and the SFTP server", () => { From 77219a86a0ffa051d12fe5702fa9c2f2b15a1b2e Mon Sep 17 00:00:00 2001 From: Julie Yaunches Date: Sat, 18 Jul 2026 00:59:09 -0400 Subject: [PATCH 17/17] test: satisfy conditional growth guardrail Signed-off-by: Julie Yaunches --- test/sandbox-provisioning.test.ts | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/test/sandbox-provisioning.test.ts b/test/sandbox-provisioning.test.ts index 88fbde1b39..ef6d176da3 100644 --- a/test/sandbox-provisioning.test.ts +++ b/test/sandbox-provisioning.test.ts @@ -60,21 +60,6 @@ function dockerRunCommandBetween( .replace(/\\\n/g, " "); } -function dockerRunCommandStartingWith(dockerfile: string, startMarker: string): string { - const start = dockerfile.indexOf(startMarker); - if (start === -1) throw new Error(`Expected Dockerfile RUN instruction ${startMarker}`); - const runLines: string[] = []; - for (const line of dockerfile.slice(start).split("\n")) { - runLines.push(line); - if (!line.trimEnd().endsWith("\\")) break; - } - const command = runLines.join("\n"); - if (!command.startsWith("RUN ") || command.trimEnd().endsWith("\\")) { - throw new Error(`Expected complete Dockerfile RUN instruction ${startMarker}`); - } - return command; -} - function dockerHealthCommandBetween( dockerfile: string, startMarker: string, @@ -1053,11 +1038,11 @@ describe("sandbox provisioning: base runtime tools", () => { ["Deep Agents Code", DEEPAGENTS_DOCKERFILE_BASE], ])("installs pinned nftables for OpenShell bypass enforcement in %s", (_agent, file) => { const dockerfile = fs.readFileSync(file, "utf-8"); - const aptInstall = dockerRunCommandStartingWith( - dockerfile, - "RUN apt-get update && apt-get install -y --no-install-recommends", - ); + const aptInstall = dockerfile.match( + /^RUN apt-get update && apt-get install -y --no-install-recommends \\\n(?:.*\\\n)*.*$/m, + )?.[0]; + expect(aptInstall).toBeDefined(); expect(aptInstall).toContain("nftables=1.1.3-1"); });