diff --git a/src/security/audit.test.ts b/src/security/audit.test.ts index 7666633cf18..de7f001de74 100644 --- a/src/security/audit.test.ts +++ b/src/security/audit.test.ts @@ -850,120 +850,113 @@ description: test skill ).toBe(true); }); - it("warns when sandbox browser containers have missing or stale hash labels", async () => { - const { stateDir, configPath } = await createFilesystemAuditFixture("browser-hash-labels"); + it("evaluates sandbox browser container findings", async () => { + const cases = [ + { + name: "warns when sandbox browser containers have missing or stale hash labels", + fixtureLabel: "browser-hash-labels", + execDockerRawFn: (async (args: string[]) => { + if (args[0] === "ps") { + return { + stdout: Buffer.from("openclaw-sbx-browser-old\nopenclaw-sbx-browser-missing-hash\n"), + stderr: Buffer.alloc(0), + code: 0, + }; + } + if (args[0] === "inspect" && args.at(-1) === "openclaw-sbx-browser-old") { + return { + stdout: Buffer.from("abc123\tepoch-v0\n"), + stderr: Buffer.alloc(0), + code: 0, + }; + } + if (args[0] === "inspect" && args.at(-1) === "openclaw-sbx-browser-missing-hash") { + return { + stdout: Buffer.from("\t\n"), + stderr: Buffer.alloc(0), + code: 0, + }; + } + return { + stdout: Buffer.alloc(0), + stderr: Buffer.from("not found"), + code: 1, + }; + }) as NonNullable, + assert: (res: SecurityAuditReport) => { + expect(hasFinding(res, "sandbox.browser_container.hash_label_missing", "warn")).toBe( + true, + ); + expect(hasFinding(res, "sandbox.browser_container.hash_epoch_stale", "warn")).toBe(true); + const staleEpoch = res.findings.find( + (f) => f.checkId === "sandbox.browser_container.hash_epoch_stale", + ); + expect(staleEpoch?.detail).toContain("openclaw-sbx-browser-old"); + }, + }, + { + name: "skips sandbox browser hash label checks when docker inspect is unavailable", + fixtureLabel: "browser-hash-labels-skip", + execDockerRawFn: (async () => { + throw new Error("spawn docker ENOENT"); + }) as NonNullable, + assert: (res: SecurityAuditReport) => { + expect(hasFinding(res, "sandbox.browser_container.hash_label_missing")).toBe(false); + expect(hasFinding(res, "sandbox.browser_container.hash_epoch_stale")).toBe(false); + }, + }, + { + name: "flags sandbox browser containers with non-loopback published ports", + fixtureLabel: "browser-non-loopback-publish", + execDockerRawFn: (async (args: string[]) => { + if (args[0] === "ps") { + return { + stdout: Buffer.from("openclaw-sbx-browser-exposed\n"), + stderr: Buffer.alloc(0), + code: 0, + }; + } + if (args[0] === "inspect" && args.at(-1) === "openclaw-sbx-browser-exposed") { + return { + stdout: Buffer.from("hash123\t2026-02-21-novnc-auth-default\n"), + stderr: Buffer.alloc(0), + code: 0, + }; + } + if (args[0] === "port" && args.at(-1) === "openclaw-sbx-browser-exposed") { + return { + stdout: Buffer.from("6080/tcp -> 0.0.0.0:49101\n9222/tcp -> 127.0.0.1:49100\n"), + stderr: Buffer.alloc(0), + code: 0, + }; + } + return { + stdout: Buffer.alloc(0), + stderr: Buffer.from("not found"), + code: 1, + }; + }) as NonNullable, + assert: (res: SecurityAuditReport) => { + expect( + hasFinding(res, "sandbox.browser_container.non_loopback_publish", "critical"), + ).toBe(true); + }, + }, + ] as const; - const execDockerRawFn = (async (args: string[]) => { - if (args[0] === "ps") { - return { - stdout: Buffer.from("openclaw-sbx-browser-old\nopenclaw-sbx-browser-missing-hash\n"), - stderr: Buffer.alloc(0), - code: 0, - }; - } - if (args[0] === "inspect" && args.at(-1) === "openclaw-sbx-browser-old") { - return { - stdout: Buffer.from("abc123\tepoch-v0\n"), - stderr: Buffer.alloc(0), - code: 0, - }; - } - if (args[0] === "inspect" && args.at(-1) === "openclaw-sbx-browser-missing-hash") { - return { - stdout: Buffer.from("\t\n"), - stderr: Buffer.alloc(0), - code: 0, - }; - } - return { - stdout: Buffer.alloc(0), - stderr: Buffer.from("not found"), - code: 1, - }; - }) as NonNullable; - - const res = await runSecurityAudit({ - config: {}, - includeFilesystem: true, - includeChannelSecurity: false, - stateDir, - configPath, - execDockerRawFn, - }); - - expect(hasFinding(res, "sandbox.browser_container.hash_label_missing", "warn")).toBe(true); - expect(hasFinding(res, "sandbox.browser_container.hash_epoch_stale", "warn")).toBe(true); - const staleEpoch = res.findings.find( - (f) => f.checkId === "sandbox.browser_container.hash_epoch_stale", - ); - expect(staleEpoch?.detail).toContain("openclaw-sbx-browser-old"); - }); - - it("skips sandbox browser hash label checks when docker inspect is unavailable", async () => { - const { stateDir, configPath } = await createFilesystemAuditFixture("browser-hash-labels-skip"); - - const execDockerRawFn = (async () => { - throw new Error("spawn docker ENOENT"); - }) as NonNullable; - - const res = await runSecurityAudit({ - config: {}, - includeFilesystem: true, - includeChannelSecurity: false, - stateDir, - configPath, - execDockerRawFn, - }); - - expect(hasFinding(res, "sandbox.browser_container.hash_label_missing")).toBe(false); - expect(hasFinding(res, "sandbox.browser_container.hash_epoch_stale")).toBe(false); - }); - - it("flags sandbox browser containers with non-loopback published ports", async () => { - const { stateDir, configPath } = await createFilesystemAuditFixture( - "browser-non-loopback-publish", - ); - - const execDockerRawFn = (async (args: string[]) => { - if (args[0] === "ps") { - return { - stdout: Buffer.from("openclaw-sbx-browser-exposed\n"), - stderr: Buffer.alloc(0), - code: 0, - }; - } - if (args[0] === "inspect" && args.at(-1) === "openclaw-sbx-browser-exposed") { - return { - stdout: Buffer.from("hash123\t2026-02-21-novnc-auth-default\n"), - stderr: Buffer.alloc(0), - code: 0, - }; - } - if (args[0] === "port" && args.at(-1) === "openclaw-sbx-browser-exposed") { - return { - stdout: Buffer.from("6080/tcp -> 0.0.0.0:49101\n9222/tcp -> 127.0.0.1:49100\n"), - stderr: Buffer.alloc(0), - code: 0, - }; - } - return { - stdout: Buffer.alloc(0), - stderr: Buffer.from("not found"), - code: 1, - }; - }) as NonNullable; - - const res = await runSecurityAudit({ - config: {}, - includeFilesystem: true, - includeChannelSecurity: false, - stateDir, - configPath, - execDockerRawFn, - }); - - expect(hasFinding(res, "sandbox.browser_container.non_loopback_publish", "critical")).toBe( - true, + await Promise.all( + cases.map(async (testCase) => { + const { stateDir, configPath } = await createFilesystemAuditFixture(testCase.fixtureLabel); + const res = await runSecurityAudit({ + config: {}, + includeFilesystem: true, + includeChannelSecurity: false, + stateDir, + configPath, + execDockerRawFn: testCase.execDockerRawFn, + }); + testCase.assert(res); + }), ); });