fix: tighten sandbox mkdirp boundary checks (#30610) (thanks @glitch418x)

This commit is contained in:
Agent
2026-03-01 21:41:24 +00:00
committed by Peter Steinberger
parent 687f5779d1
commit 3be1343e00
6 changed files with 40 additions and 17 deletions

View File

@@ -198,6 +198,30 @@ describe("sandbox fs bridge shell compatibility", () => {
}
});
it("rejects mkdirp when target exists as a file", async () => {
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-fs-bridge-mkdirp-file-"));
try {
const workspaceDir = path.join(stateDir, "workspace");
const filePath = path.join(workspaceDir, "memory", "kemik");
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, "not a directory");
const bridge = createSandboxFsBridge({
sandbox: createSandbox({
workspaceDir,
agentWorkspaceDir: workspaceDir,
}),
});
await expect(bridge.mkdirp({ filePath: "memory/kemik" })).rejects.toThrow(
/cannot create directories/i,
);
expect(mockedExecDockerRaw).not.toHaveBeenCalled();
} finally {
await fs.rm(stateDir, { recursive: true, force: true });
}
});
it("rejects pre-existing host symlink escapes before docker exec", async () => {
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-fs-bridge-"));
const workspaceDir = path.join(stateDir, "workspace");

View File

@@ -24,7 +24,7 @@ type PathSafetyOptions = {
aliasPolicy?: PathAliasPolicy;
requireWritable?: boolean;
allowMissingTarget?: boolean;
allowedTypes?: readonly SafeOpenSyncAllowedType[];
allowedType?: SafeOpenSyncAllowedType;
};
export type SandboxResolvedPath = {
@@ -137,7 +137,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
await this.assertPathSafety(target, {
action: "create directories",
requireWritable: true,
allowedTypes: ["directory"],
allowedType: "directory",
});
await this.runCommand('set -eu; mkdir -p -- "$1"', {
args: [target.containerPath],
@@ -264,7 +264,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
rootPath: lexicalMount.hostRoot,
boundaryLabel: "sandbox mount root",
aliasPolicy: options.aliasPolicy,
allowedTypes: options.allowedTypes,
allowedType: options.allowedType,
});
if (!guarded.ok) {
if (guarded.reason !== "path" || options.allowMissingTarget === false) {