sandbox: allow directory boundary checks for mkdirp

This commit is contained in:
glitch418x
2026-03-01 15:01:25 +03:00
committed by Peter Steinberger
parent 4fc7ecf088
commit 687f5779d1
5 changed files with 112 additions and 8 deletions

View File

@@ -173,6 +173,31 @@ describe("sandbox fs bridge shell compatibility", () => {
expect(mockedExecDockerRaw).not.toHaveBeenCalled();
});
it("allows mkdirp for existing in-boundary subdirectories", async () => {
const stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-fs-bridge-mkdirp-"));
try {
const workspaceDir = path.join(stateDir, "workspace");
const nestedDir = path.join(workspaceDir, "memory", "kemik");
await fs.mkdir(nestedDir, { recursive: true });
const bridge = createSandboxFsBridge({
sandbox: createSandbox({
workspaceDir,
agentWorkspaceDir: workspaceDir,
}),
});
await expect(bridge.mkdirp({ filePath: "memory/kemik" })).resolves.toBeUndefined();
const mkdirCall = findCallByScriptFragment('mkdir -p -- "$1"');
expect(mkdirCall).toBeDefined();
const mkdirPath = mkdirCall ? getDockerPathArg(mkdirCall[0]) : "";
expect(mkdirPath).toBe("/workspace/memory/kemik");
} 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

@@ -1,6 +1,7 @@
import fs from "node:fs";
import { openBoundaryFile } from "../../infra/boundary-file-read.js";
import { PATH_ALIAS_POLICIES, type PathAliasPolicy } from "../../infra/path-alias-guards.js";
import type { SafeOpenSyncAllowedType } from "../../infra/safe-open-sync.js";
import { execDockerRaw, type ExecDockerRawResult } from "./docker.js";
import {
buildSandboxFsMounts,
@@ -23,6 +24,7 @@ type PathSafetyOptions = {
aliasPolicy?: PathAliasPolicy;
requireWritable?: boolean;
allowMissingTarget?: boolean;
allowedTypes?: readonly SafeOpenSyncAllowedType[];
};
export type SandboxResolvedPath = {
@@ -132,7 +134,11 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
async mkdirp(params: { filePath: string; cwd?: string; signal?: AbortSignal }): Promise<void> {
const target = this.resolveResolvedPath(params);
this.ensureWriteAccess(target, "create directories");
await this.assertPathSafety(target, { action: "create directories", requireWritable: true });
await this.assertPathSafety(target, {
action: "create directories",
requireWritable: true,
allowedTypes: ["directory"],
});
await this.runCommand('set -eu; mkdir -p -- "$1"', {
args: [target.containerPath],
signal: params.signal,
@@ -258,6 +264,7 @@ class SandboxFsBridgeImpl implements SandboxFsBridge {
rootPath: lexicalMount.hostRoot,
boundaryLabel: "sandbox mount root",
aliasPolicy: options.aliasPolicy,
allowedTypes: options.allowedTypes,
});
if (!guarded.ok) {
if (guarded.reason !== "path" || options.allowMissingTarget === false) {