fix(security): force sandbox browser hash migration and audit stale labels

This commit is contained in:
Peter Steinberger
2026-02-21 13:25:35 +01:00
parent b2d84528f8
commit 1835dec200
12 changed files with 254 additions and 6 deletions

View File

@@ -419,6 +419,85 @@ describe("security audit", () => {
).toBe(true);
});
it("warns when sandbox browser containers have missing or stale hash labels", async () => {
const tmp = await makeTmpDir("browser-hash-labels");
const stateDir = path.join(tmp, "state");
await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });
const configPath = path.join(stateDir, "openclaw.json");
await fs.writeFile(configPath, "{}\n", "utf-8");
await fs.chmod(configPath, 0o600);
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("<no value>\t<no value>\n"),
stderr: Buffer.alloc(0),
code: 0,
};
}
return {
stdout: Buffer.alloc(0),
stderr: Buffer.from("not found"),
code: 1,
};
}) as NonNullable<SecurityAuditOptions["execDockerRawFn"]>;
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 tmp = await makeTmpDir("browser-hash-labels-skip");
const stateDir = path.join(tmp, "state");
await fs.mkdir(stateDir, { recursive: true, mode: 0o700 });
const configPath = path.join(stateDir, "openclaw.json");
await fs.writeFile(configPath, "{}\n", "utf-8");
await fs.chmod(configPath, 0o600);
const execDockerRawFn = (async () => {
throw new Error("spawn docker ENOENT");
}) as NonNullable<SecurityAuditOptions["execDockerRawFn"]>;
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("uses symlink target permissions for config checks", async () => {
if (isWindows) {
return;