perf(test): cache sandbox bind policy paths

This commit is contained in:
Peter Steinberger
2026-04-20 15:39:11 +01:00
parent fc56cd135f
commit b5a16e263d
2 changed files with 38 additions and 17 deletions

View File

@@ -92,12 +92,13 @@ describe("validateBindMounts", () => {
});
it("allows legitimate project directory mounts", () => {
const projectRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-safe-"));
expect(() =>
validateBindMounts([
"/home/user/source:/source:rw",
"/home/user/projects:/projects:ro",
"/var/data/myapp:/data",
"/opt/myapp/config:/config:ro",
`${join(projectRoot, "source")}:/source:rw`,
`${join(projectRoot, "projects")}:/projects:ro`,
`${join(projectRoot, "data")}:/data`,
`${join(projectRoot, "config")}:/config:ro`,
]),
).not.toThrow();
});
@@ -256,42 +257,46 @@ describe("validateBindMounts", () => {
});
it("blocks bind sources outside allowed roots when allowlist is configured", () => {
const allowedRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-allowed-root-"));
const externalRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-external-"));
expect(() =>
validateBindMounts(["/opt/external:/data:ro"], {
allowedSourceRoots: ["/home/user/project"],
validateBindMounts([`${externalRoot}:/data:ro`], {
allowedSourceRoots: [allowedRoot],
}),
).toThrow(/outside allowed roots/);
});
it("allows bind sources in allowed roots when allowlist is configured", () => {
const projectRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-allowed-"));
expect(() =>
validateBindMounts(["/home/user/project/cache:/data:ro"], {
allowedSourceRoots: ["/home/user/project"],
validateBindMounts([`${join(projectRoot, "cache")}:/data:ro`], {
allowedSourceRoots: [projectRoot],
}),
).not.toThrow();
});
it("allows bind sources outside allowed roots with explicit dangerous override", () => {
const allowedRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-allowed-root-"));
const externalRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-external-"));
expect(() =>
validateBindMounts(["/opt/external:/data:ro"], {
allowedSourceRoots: ["/home/user/project"],
validateBindMounts([`${externalRoot}:/data:ro`], {
allowedSourceRoots: [allowedRoot],
allowSourcesOutsideAllowedRoots: true,
}),
).not.toThrow();
});
it("blocks reserved container target paths by default", () => {
const projectRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-reserved-default-"));
expect(() =>
validateBindMounts([
"/home/user/project:/workspace:rw",
"/home/user/project:/agent/cache:rw",
]),
validateBindMounts([`${projectRoot}:/workspace:rw`, `${projectRoot}:/agent/cache:rw`]),
).toThrow(/reserved container path/);
});
it("allows reserved container target paths with explicit dangerous override", () => {
const projectRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-reserved-"));
expect(() =>
validateBindMounts(["/home/user/project:/workspace:rw"], {
validateBindMounts([`${projectRoot}:/workspace:rw`], {
allowReservedContainerTargets: true,
}),
).not.toThrow();
@@ -379,9 +384,10 @@ describe("profile hardening", () => {
describe("validateSandboxSecurity", () => {
it("passes with safe config", () => {
const projectRoot = mkdtempSync(join(tmpdir(), "openclaw-sbx-safe-config-"));
expect(() =>
validateSandboxSecurity({
binds: ["/home/user/src:/src:rw"],
binds: [`${projectRoot}:/src:rw`],
network: "none",
seccompProfile: "/tmp/seccomp.json",
apparmorProfile: "openclaw-sandbox",

View File

@@ -50,6 +50,12 @@ const BLOCKED_HOME_SUBPATHS = [
const BLOCKED_SECCOMP_PROFILES = new Set(["unconfined"]);
const BLOCKED_APPARMOR_PROFILES = new Set(["unconfined"]);
const RESERVED_CONTAINER_TARGET_PATHS = ["/workspace", SANDBOX_AGENT_WORKSPACE_MOUNT];
let blockedHostPathsCache:
| {
key: string;
paths: string[];
}
| undefined;
export type ValidateBindMountsOptions = {
allowedSourceRoots?: string[];
@@ -146,13 +152,22 @@ export function getBlockedReasonForSourcePath(
}
function getBlockedHostPaths(): string[] {
const cacheKey = JSON.stringify({
home: process.env.HOME,
openclawHome: process.env.OPENCLAW_HOME,
osHome: os.homedir(),
});
if (blockedHostPathsCache?.key === cacheKey) {
return blockedHostPathsCache.paths;
}
const blocked = new Set(BLOCKED_HOST_PATHS.map(normalizeHostPath));
for (const home of getBlockedHomeRoots()) {
for (const suffix of BLOCKED_HOME_SUBPATHS) {
blocked.add(normalizeHostPath(path.posix.join(home, suffix)));
}
}
return [...blocked];
blockedHostPathsCache = { key: cacheKey, paths: [...blocked] };
return blockedHostPathsCache.paths;
}
function getBlockedHomeRoots(): string[] {