mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 13:44:58 +00:00
fix(test): use NTFS junctions and platform guards for symlink tests on Windows (openclaw#28747) thanks @arosstale
Verified: - pnpm install --frozen-lockfile - pnpm test src/agents/apply-patch.test.ts src/agents/sandbox/fs-bridge.test.ts src/agents/sandbox/validate-sandbox-security.test.ts src/infra/archive.test.ts Co-authored-by: arosstale <117890364+arosstale@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
@@ -148,6 +148,10 @@ describe("applyPatch", () => {
|
||||
});
|
||||
|
||||
it("rejects symlink escape attempts by default", async () => {
|
||||
// File symlinks require SeCreateSymbolicLinkPrivilege on Windows.
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
await withTempDir(async (dir) => {
|
||||
const outside = path.join(path.dirname(dir), "outside-target.txt");
|
||||
const linkPath = path.join(dir, "link.txt");
|
||||
@@ -232,6 +236,10 @@ describe("applyPatch", () => {
|
||||
});
|
||||
|
||||
it("allows symlinks that resolve within cwd by default", async () => {
|
||||
// File symlinks require SeCreateSymbolicLinkPrivilege on Windows.
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
await withTempDir(async (dir) => {
|
||||
const target = path.join(dir, "target.txt");
|
||||
const linkPath = path.join(dir, "link.txt");
|
||||
@@ -259,7 +267,9 @@ describe("applyPatch", () => {
|
||||
await fs.writeFile(outsideFile, "victim\n", "utf8");
|
||||
|
||||
const linkDir = path.join(dir, "linkdir");
|
||||
await fs.symlink(outsideDir, linkDir);
|
||||
// Use 'junction' on Windows — junctions target directories without
|
||||
// requiring SeCreateSymbolicLinkPrivilege.
|
||||
await fs.symlink(outsideDir, linkDir, process.platform === "win32" ? "junction" : undefined);
|
||||
|
||||
const patch = `*** Begin Patch
|
||||
*** Delete File: linkdir/victim.txt
|
||||
@@ -310,7 +320,13 @@ describe("applyPatch", () => {
|
||||
await fs.writeFile(outsideTarget, "keep\n", "utf8");
|
||||
|
||||
const linkDir = path.join(dir, "link");
|
||||
await fs.symlink(outsideDir, linkDir);
|
||||
// Use 'junction' on Windows — junctions target directories without
|
||||
// requiring SeCreateSymbolicLinkPrivilege.
|
||||
await fs.symlink(
|
||||
outsideDir,
|
||||
linkDir,
|
||||
process.platform === "win32" ? "junction" : undefined,
|
||||
);
|
||||
|
||||
const patch = `*** Begin Patch
|
||||
*** Delete File: link
|
||||
|
||||
@@ -308,6 +308,10 @@ describe("sandbox fs bridge shell compatibility", () => {
|
||||
it("rejects pre-existing host symlink escapes before docker exec", async () => {
|
||||
await withTempDir("openclaw-fs-bridge-", async (stateDir) => {
|
||||
const { workspaceDir, outsideFile } = await createHostEscapeFixture(stateDir);
|
||||
// File symlinks require SeCreateSymbolicLinkPrivilege on Windows.
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
await fs.symlink(outsideFile, path.join(workspaceDir, "link.txt"));
|
||||
|
||||
const bridge = createSandboxFsBridge({
|
||||
|
||||
@@ -103,17 +103,22 @@ describe("validateBindMounts", () => {
|
||||
});
|
||||
|
||||
it("blocks symlink escapes into blocked directories", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "openclaw-sbx-"));
|
||||
const link = join(dir, "etc-link");
|
||||
symlinkSync("/etc", link);
|
||||
const run = () => validateBindMounts([`${link}/passwd:/mnt/passwd:ro`]);
|
||||
|
||||
if (process.platform === "win32") {
|
||||
// Windows source paths (e.g. C:\...) are intentionally rejected as non-POSIX.
|
||||
// Symlinks to non-existent targets like /etc require
|
||||
// SeCreateSymbolicLinkPrivilege on Windows. The Windows branch of this
|
||||
// test does not need a real symlink — it only asserts that Windows source
|
||||
// paths are rejected as non-POSIX.
|
||||
const dir = mkdtempSync(join(tmpdir(), "openclaw-sbx-"));
|
||||
const fakePath = join(dir, "etc-link", "passwd");
|
||||
const run = () => validateBindMounts([`${fakePath}:/mnt/passwd:ro`]);
|
||||
expect(run).toThrow(/non-absolute source path/);
|
||||
return;
|
||||
}
|
||||
|
||||
const dir = mkdtempSync(join(tmpdir(), "openclaw-sbx-"));
|
||||
const link = join(dir, "etc-link");
|
||||
symlinkSync("/etc", link);
|
||||
const run = () => validateBindMounts([`${link}/passwd:/mnt/passwd:ro`]);
|
||||
expect(run).toThrow(/blocked path/);
|
||||
});
|
||||
|
||||
|
||||
@@ -120,7 +120,13 @@ describe("archive utils", () => {
|
||||
await withArchiveCase("zip", async ({ workDir, archivePath, extractDir }) => {
|
||||
const outsideDir = path.join(workDir, "outside");
|
||||
await fs.mkdir(outsideDir, { recursive: true });
|
||||
await fs.symlink(outsideDir, path.join(extractDir, "escape"));
|
||||
// Use 'junction' on Windows — junctions target directories without
|
||||
// requiring SeCreateSymbolicLinkPrivilege.
|
||||
await fs.symlink(
|
||||
outsideDir,
|
||||
path.join(extractDir, "escape"),
|
||||
process.platform === "win32" ? "junction" : undefined,
|
||||
);
|
||||
|
||||
const zip = new JSZip();
|
||||
zip.file("escape/pwn.txt", "owned");
|
||||
|
||||
Reference in New Issue
Block a user