mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-07 23:11:25 +00:00
fix: harden workspace boundary path resolution
This commit is contained in:
@@ -195,6 +195,26 @@ describe("resolveSandboxedMediaSource", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects sandbox symlink escapes when the outside leaf does not exist yet", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
await withSandboxRoot(async (sandboxDir) => {
|
||||
const outsideDir = await fs.mkdtemp(
|
||||
path.join(process.cwd(), "sandbox-media-outside-missing-"),
|
||||
);
|
||||
const linkDir = path.join(sandboxDir, "escape-link");
|
||||
await fs.symlink(outsideDir, linkDir);
|
||||
try {
|
||||
const missingOutsidePath = path.join(linkDir, "new-file.txt");
|
||||
await expectSandboxRejection(missingOutsidePath, sandboxDir, /symlink|sandbox/i);
|
||||
} finally {
|
||||
await fs.rm(linkDir, { force: true });
|
||||
await fs.rm(outsideDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects hardlinked OpenClaw tmp paths to outside files", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
|
||||
@@ -71,7 +71,7 @@ export async function assertSandboxPath(params: {
|
||||
};
|
||||
await assertNoPathAliasEscape({
|
||||
absolutePath: resolved.resolved,
|
||||
rootPath: path.resolve(params.root),
|
||||
rootPath: params.root,
|
||||
boundaryLabel: "sandbox root",
|
||||
policy,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { existsSync, realpathSync } from "node:fs";
|
||||
import { posix } from "node:path";
|
||||
import { resolvePathViaExistingAncestorSync } from "../../infra/boundary-path.js";
|
||||
|
||||
/**
|
||||
* Normalize a POSIX host path: resolve `.`, `..`, collapse `//`, strip trailing `/`.
|
||||
@@ -17,31 +17,5 @@ export function resolveSandboxHostPathViaExistingAncestor(sourcePath: string): s
|
||||
if (!sourcePath.startsWith("/")) {
|
||||
return sourcePath;
|
||||
}
|
||||
|
||||
const normalized = normalizeSandboxHostPath(sourcePath);
|
||||
let current = normalized;
|
||||
const missingSegments: string[] = [];
|
||||
|
||||
while (current !== "/" && !existsSync(current)) {
|
||||
missingSegments.unshift(posix.basename(current));
|
||||
const parent = posix.dirname(current);
|
||||
if (parent === current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
if (!existsSync(current)) {
|
||||
return normalized;
|
||||
}
|
||||
|
||||
try {
|
||||
const resolvedAncestor = normalizeSandboxHostPath(realpathSync.native(current));
|
||||
if (missingSegments.length === 0) {
|
||||
return resolvedAncestor;
|
||||
}
|
||||
return normalizeSandboxHostPath(posix.join(resolvedAncestor, ...missingSegments));
|
||||
} catch {
|
||||
return normalized;
|
||||
}
|
||||
return normalizeSandboxHostPath(resolvePathViaExistingAncestorSync(sourcePath));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user