fix(security): harden sandbox docker config validation

This commit is contained in:
Peter Steinberger
2026-02-16 03:03:55 +01:00
parent d4bdcda324
commit 887b209db4
11 changed files with 691 additions and 6 deletions

View File

@@ -94,7 +94,7 @@ describe("buildSandboxCreateArgs", () => {
);
});
it("emits -v flags for custom binds", () => {
it("emits -v flags for safe custom binds", () => {
const cfg: SandboxDockerConfig = {
image: "openclaw-sandbox:bookworm-slim",
containerPrefix: "openclaw-sbx-",
@@ -103,7 +103,7 @@ describe("buildSandboxCreateArgs", () => {
tmpfs: [],
network: "none",
capDrop: [],
binds: ["/home/user/source:/source:rw", "/var/run/docker.sock:/var/run/docker.sock"],
binds: ["/home/user/source:/source:rw", "/var/data/myapp:/data:ro"],
};
const args = buildSandboxCreateArgs({
@@ -124,7 +124,116 @@ describe("buildSandboxCreateArgs", () => {
}
}
expect(vFlags).toContain("/home/user/source:/source:rw");
expect(vFlags).toContain("/var/run/docker.sock:/var/run/docker.sock");
expect(vFlags).toContain("/var/data/myapp:/data:ro");
});
it("throws on dangerous bind mounts (Docker socket)", () => {
const cfg: SandboxDockerConfig = {
image: "openclaw-sandbox:bookworm-slim",
containerPrefix: "openclaw-sbx-",
workdir: "/workspace",
readOnlyRoot: false,
tmpfs: [],
network: "none",
capDrop: [],
binds: ["/var/run/docker.sock:/var/run/docker.sock"],
};
expect(() =>
buildSandboxCreateArgs({
name: "openclaw-sbx-dangerous",
cfg,
scopeKey: "main",
createdAtMs: 1700000000000,
}),
).toThrow(/blocked path/);
});
it("throws on dangerous bind mounts (parent path)", () => {
const cfg: SandboxDockerConfig = {
image: "openclaw-sandbox:bookworm-slim",
containerPrefix: "openclaw-sbx-",
workdir: "/workspace",
readOnlyRoot: false,
tmpfs: [],
network: "none",
capDrop: [],
binds: ["/run:/run"],
};
expect(() =>
buildSandboxCreateArgs({
name: "openclaw-sbx-dangerous-parent",
cfg,
scopeKey: "main",
createdAtMs: 1700000000000,
}),
).toThrow(/blocked path/);
});
it("throws on network host mode", () => {
const cfg: SandboxDockerConfig = {
image: "openclaw-sandbox:bookworm-slim",
containerPrefix: "openclaw-sbx-",
workdir: "/workspace",
readOnlyRoot: false,
tmpfs: [],
network: "host",
capDrop: [],
};
expect(() =>
buildSandboxCreateArgs({
name: "openclaw-sbx-host",
cfg,
scopeKey: "main",
createdAtMs: 1700000000000,
}),
).toThrow(/network mode "host" is blocked/);
});
it("throws on seccomp unconfined", () => {
const cfg: SandboxDockerConfig = {
image: "openclaw-sandbox:bookworm-slim",
containerPrefix: "openclaw-sbx-",
workdir: "/workspace",
readOnlyRoot: false,
tmpfs: [],
network: "none",
capDrop: [],
seccompProfile: "unconfined",
};
expect(() =>
buildSandboxCreateArgs({
name: "openclaw-sbx-seccomp",
cfg,
scopeKey: "main",
createdAtMs: 1700000000000,
}),
).toThrow(/seccomp profile "unconfined" is blocked/);
});
it("throws on apparmor unconfined", () => {
const cfg: SandboxDockerConfig = {
image: "openclaw-sandbox:bookworm-slim",
containerPrefix: "openclaw-sbx-",
workdir: "/workspace",
readOnlyRoot: false,
tmpfs: [],
network: "none",
capDrop: [],
apparmorProfile: "unconfined",
};
expect(() =>
buildSandboxCreateArgs({
name: "openclaw-sbx-apparmor",
cfg,
scopeKey: "main",
createdAtMs: 1700000000000,
}),
).toThrow(/apparmor profile "unconfined" is blocked/);
});
it("omits -v flags when binds is empty or undefined", () => {