mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 09:01:22 +00:00
fix(agents): block workspaceOnly apply_patch delete symlink escape
This commit is contained in:
@@ -50,9 +50,16 @@ export function resolveSandboxPath(params: { filePath: string; cwd: string; root
|
||||
return { resolved, relative };
|
||||
}
|
||||
|
||||
export async function assertSandboxPath(params: { filePath: string; cwd: string; root: string }) {
|
||||
export async function assertSandboxPath(params: {
|
||||
filePath: string;
|
||||
cwd: string;
|
||||
root: string;
|
||||
allowFinalSymlink?: boolean;
|
||||
}) {
|
||||
const resolved = resolveSandboxPath(params);
|
||||
await assertNoSymlinkEscape(resolved.relative, path.resolve(params.root));
|
||||
await assertNoSymlinkEscape(resolved.relative, path.resolve(params.root), {
|
||||
allowFinalSymlink: params.allowFinalSymlink,
|
||||
});
|
||||
return resolved;
|
||||
}
|
||||
|
||||
@@ -90,18 +97,29 @@ export async function resolveSandboxedMediaSource(params: {
|
||||
return resolved.resolved;
|
||||
}
|
||||
|
||||
async function assertNoSymlinkEscape(relative: string, root: string) {
|
||||
async function assertNoSymlinkEscape(
|
||||
relative: string,
|
||||
root: string,
|
||||
options?: { allowFinalSymlink?: boolean },
|
||||
) {
|
||||
if (!relative) {
|
||||
return;
|
||||
}
|
||||
const rootReal = await tryRealpath(root);
|
||||
const parts = relative.split(path.sep).filter(Boolean);
|
||||
let current = root;
|
||||
for (const part of parts) {
|
||||
for (let idx = 0; idx < parts.length; idx += 1) {
|
||||
const part = parts[idx];
|
||||
const isLast = idx === parts.length - 1;
|
||||
current = path.join(current, part);
|
||||
try {
|
||||
const stat = await fs.lstat(current);
|
||||
if (stat.isSymbolicLink()) {
|
||||
// Unlinking a symlink itself is safe even if it points outside the root. What we
|
||||
// must prevent is traversing through a symlink to reach targets outside root.
|
||||
if (options?.allowFinalSymlink && isLast) {
|
||||
return;
|
||||
}
|
||||
const target = await tryRealpath(current);
|
||||
if (!isPathInside(rootReal, target)) {
|
||||
throw new Error(
|
||||
|
||||
Reference in New Issue
Block a user