mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
fix: harden sandboxed patch parent paths
This commit is contained in:
@@ -452,7 +452,7 @@ describe("applyPatch", () => {
|
||||
timing: "before-realpath",
|
||||
run: async () => {
|
||||
await expect(applyPatch(patch, { cwd: dir })).rejects.toThrow(
|
||||
/under root|unable to resolve opened file path/i,
|
||||
/path alias under sandbox root|path escapes sandbox root|under root|unable to resolve opened file path/i,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -152,6 +152,7 @@ export async function applyPatch(
|
||||
|
||||
if (hunk.kind === "add") {
|
||||
const target = await resolvePatchPath(hunk.path, options);
|
||||
await assertPatchParentPath(hunk.path, options);
|
||||
await ensureDir(target.resolved, fileOps);
|
||||
await fileOps.writeFile(target.resolved, hunk.contents);
|
||||
recordSummary(summary, seen, "added", target.display);
|
||||
@@ -172,6 +173,7 @@ export async function applyPatch(
|
||||
|
||||
if (hunk.movePath) {
|
||||
const moveTarget = await resolvePatchPath(hunk.movePath, options);
|
||||
await assertPatchParentPath(hunk.movePath, options);
|
||||
await ensureDir(moveTarget.resolved, fileOps);
|
||||
await fileOps.writeFile(moveTarget.resolved, applied);
|
||||
await fileOps.remove(target.resolved);
|
||||
@@ -301,6 +303,54 @@ async function ensureDir(filePath: string, ops: PatchFileOps) {
|
||||
await ops.mkdirp(parent);
|
||||
}
|
||||
|
||||
async function assertPatchParentPath(filePath: string, options: ApplyPatchOptions) {
|
||||
if (options.workspaceOnly === false || options.sandbox) {
|
||||
return;
|
||||
}
|
||||
const parent = path.dirname(filePath);
|
||||
if (!parent || parent === ".") {
|
||||
return;
|
||||
}
|
||||
await assertSandboxPath({
|
||||
filePath: parent,
|
||||
cwd: options.cwd,
|
||||
root: options.cwd,
|
||||
});
|
||||
await assertNoExistingParentAliases({
|
||||
parentPath: resolvePathFromInput(parent, options.cwd),
|
||||
rootPath: options.cwd,
|
||||
});
|
||||
}
|
||||
|
||||
async function assertNoExistingParentAliases(params: { parentPath: string; rootPath: string }) {
|
||||
const rootPath = path.resolve(params.rootPath);
|
||||
const parentPath = path.resolve(params.parentPath);
|
||||
const relative = path.relative(rootPath, parentPath);
|
||||
if (!relative || relative === "" || relative.startsWith("..") || path.isAbsolute(relative)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let current = rootPath;
|
||||
for (const segment of relative.split(path.sep)) {
|
||||
if (!segment) {
|
||||
continue;
|
||||
}
|
||||
current = path.join(current, segment);
|
||||
const stat = await fs.lstat(current).catch((error: unknown) => {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
if (!stat) {
|
||||
return;
|
||||
}
|
||||
if (stat.isSymbolicLink()) {
|
||||
throw new Error(`Path alias under sandbox root: ${path.relative(rootPath, current)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function resolvePatchPath(
|
||||
filePath: string,
|
||||
options: ApplyPatchOptions,
|
||||
|
||||
Reference in New Issue
Block a user