diff --git a/test/helpers/hermes-restart-config-seal-fixture.ts b/test/helpers/hermes-restart-config-seal-fixture.ts index 1d5fbf65500..dae04b4954e 100644 --- a/test/helpers/hermes-restart-config-seal-fixture.ts +++ b/test/helpers/hermes-restart-config-seal-fixture.ts @@ -17,6 +17,8 @@ export const RUNTIME_CONFIG_GUARD = path.join( "runtime-config-guard.py", ); +const HERMES_GUARD_TIMEOUT_MS = 90_000; + export interface RestartFixture { root: string; sandboxDir: string; @@ -123,7 +125,7 @@ export function runWriteConfig(fixture: RestartFixture, expectedDigest: string, "--expected-config-sha256", expectedDigest, ], - { encoding: "utf-8", input: content, timeout: 5000 }, + { encoding: "utf-8", input: content, timeout: HERMES_GUARD_TIMEOUT_MS }, ); } @@ -155,7 +157,7 @@ export function runGuard(action: "seal-restart" | "unseal-restart", fixture: Res args.push(...(action === "seal-restart" ? ["--hash-file", fixture.hashPath] : [])); return spawnSync("python3", args, { encoding: "utf-8", - timeout: 5000, + timeout: HERMES_GUARD_TIMEOUT_MS, }); } @@ -231,7 +233,10 @@ export function runShieldsTransactionAction( ...(options.rollbackMode ? ["--rollback-shields-mode", options.rollbackMode] : []), ...(options.token ? ["--lock-token", options.token] : []), ); - return spawnSync("python3", args, { encoding: "utf-8", timeout: 5000 }); + return spawnSync("python3", args, { + encoding: "utf-8", + timeout: HERMES_GUARD_TIMEOUT_MS, + }); } export function strictHashIsValid(fixture: RestartFixture): boolean { diff --git a/test/hermes-restart-config-seal-recovery.test.ts b/test/hermes-restart-config-seal-recovery.test.ts index 60b441c8853..649f8f0de2d 100644 --- a/test/hermes-restart-config-seal-recovery.test.ts +++ b/test/hermes-restart-config-seal-recovery.test.ts @@ -359,17 +359,47 @@ describe.skipIf(process.platform === "win32")("Hermes mutable restart input seal let restoreTempRootMode: (() => void) | undefined; try { + const sandboxUidResult = spawnSync("id", ["-u", "sandbox"], { + encoding: "utf-8", + timeout: 5000, + }); + const sandboxGidResult = spawnSync("id", ["-g", "sandbox"], { + encoding: "utf-8", + timeout: 5000, + }); + expect(sandboxUidResult.status, sandboxUidResult.stderr).toBe(0); + expect(sandboxGidResult.status, sandboxGidResult.stderr).toBe(0); + const sandboxUid = Number(sandboxUidResult.stdout.trim()); + const sandboxGid = Number(sandboxGidResult.stdout.trim()); + expect(Number.isSafeInteger(sandboxUid)).toBe(true); + expect(Number.isSafeInteger(sandboxGid)).toBe(true); + + for (const pathname of [ + fixture.sandboxDir, + fixture.hermesDir, + fixture.configPath, + fixture.envPath, + fixture.compatHashPath, + ]) { + fs.chownSync(pathname, sandboxUid, sandboxGid); + } + // chown clears setgid, so restore the canonical mutable Hermes mode. + fs.chmodSync(fixture.hermesDir, 0o3770); + restoreTempRootMode = allowRestartFixturePeerTraversal(fixture); const sealed = runGuard("seal-restart", fixture); expect(sealed.status, sealed.stderr).toBe(0); - const hermesGid = fs.statSync(fixture.hermesDir).gid; + const sealedHermes = fs.statSync(fixture.hermesDir); + expect(sealedHermes.uid).toBe(0); + expect(sealedHermes.gid).toBe(sandboxGid); + expect(mode(fixture.hermesDir)).toBe(0o3770); const peer = spawnSync( "setpriv", [ "--reuid=65534", "--regid=65534", - `--groups=${hermesGid}`, + `--groups=${sandboxGid}`, "sh", "-c", 'touch "$1/peer-runtime-state" || exit 10; rm "$1/config.yaml" 2>/dev/null && exit 20; test -f "$1/config.yaml"', @@ -385,6 +415,17 @@ describe.skipIf(process.platform === "win32")("Hermes mutable restart input seal const unsealed = runGuard("unseal-restart", fixture); expect(unsealed.status, unsealed.stderr).toBe(0); + for (const pathname of [ + fixture.sandboxDir, + fixture.hermesDir, + fixture.configPath, + fixture.envPath, + fixture.compatHashPath, + ]) { + const restored = fs.statSync(pathname); + expect(restored.uid).toBe(sandboxUid); + expect(restored.gid).toBe(sandboxGid); + } } finally { try { fs.rmSync(fixture.root, { recursive: true, force: true }); diff --git a/test/nemoclaw-start-perms.test.ts b/test/nemoclaw-start-perms.test.ts index eeef41ba16a..f9ac7fea056 100644 --- a/test/nemoclaw-start-perms.test.ts +++ b/test/nemoclaw-start-perms.test.ts @@ -594,15 +594,29 @@ describe("nemoclaw-start mutable config reclaim", () => { expect(fs.statSync(configDir).uid.toString()).toBe(nobodyUid); expect(fs.statSync(configDir).gid.toString()).toBe(nobodyGid); - const writeCheck = spawnSync("setpriv", [ - `--reuid=${nobodyUid}`, - `--regid=${nobodyGid}`, - "--clear-groups", - "--", - "touch", - path.join(configDir, "nemoclaw-write-check"), - ]); - expect(writeCheck.status).toBe(0); + const tempRoot = path.dirname(root); + const tempRootMode = mode(tempRoot); + const writeCheck = (() => { + // Vitest's shared temp root is private; expose traversal only for this peer probe. + fs.chmodSync(tempRoot, tempRootMode | 0o001); + try { + return spawnSync( + "setpriv", + [ + `--reuid=${nobodyUid}`, + `--regid=${nobodyGid}`, + "--clear-groups", + "--", + "touch", + path.join(configDir, "nemoclaw-write-check"), + ], + { encoding: "utf-8" }, + ); + } finally { + fs.chmodSync(tempRoot, tempRootMode); + } + })(); + expect(writeCheck.status, writeCheck.stderr).toBe(0); } finally { fs.rmSync(root, { recursive: true, force: true }); }