Tools: revalidate workspace-only patch targets

This commit is contained in:
Vincent Koc 2026-03-14 20:19:22 -07:00
parent db20141993
commit dec7c38e35
3 changed files with 43 additions and 3 deletions

View File

@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
- Configure/startup: move outbound send-deps resolution into a lightweight helper so `openclaw configure` no longer stalls after the banner while eagerly loading channel plugins. (#46301) thanks @scoootscooob.
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#40146)
- Plugins/install precedence: keep bundled plugins ahead of auto-discovered globals by default, but let an explicitly installed plugin record win its own duplicate-id tie so installed channel plugins load from `~/.openclaw/extensions` after `openclaw plugins install`.
- Tools/apply-patch: revalidate workspace-only delete and directory targets immediately before mutating host paths. Thanks @vincentkoc.
### Fixes

View File

@ -1,7 +1,7 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
import { applyPatch } from "./apply-patch.js";
async function withTempDir<T>(fn: (dir: string) => Promise<T>) {
@ -147,6 +147,25 @@ describe("applyPatch", () => {
});
});
it("revalidates delete targets before removing files", async () => {
await withTempDir(async (dir) => {
const target = path.join(dir, "delete-me.txt");
await fs.writeFile(target, "x\n", "utf8");
const rmSpy = vi.spyOn(fs, "rm");
try {
const patch = `*** Begin Patch
*** Delete File: delete-me.txt
*** End Patch`;
await applyPatch(patch, { cwd: dir });
expect(rmSpy).toHaveBeenCalledWith(target);
} finally {
rmSpy.mockRestore();
}
});
});
it("rejects symlink escape attempts by default", async () => {
// File symlinks require SeCreateSymbolicLinkPrivilege on Windows.
if (process.platform === "win32") {

View File

@ -270,8 +270,28 @@ function resolvePatchFileOps(options: ApplyPatchOptions): PatchFileOps {
encoding: "utf8",
});
},
remove: (filePath) => fs.rm(filePath),
mkdirp: (dir) => fs.mkdir(dir, { recursive: true }).then(() => {}),
remove: async (filePath) => {
if (workspaceOnly) {
await assertSandboxPath({
filePath,
cwd: options.cwd,
root: options.cwd,
allowFinalSymlinkForUnlink: true,
allowFinalHardlinkForUnlink: true,
});
}
await fs.rm(filePath);
},
mkdirp: async (dir) => {
if (workspaceOnly) {
await assertSandboxPath({
filePath: dir,
cwd: options.cwd,
root: options.cwd,
});
}
await fs.mkdir(dir, { recursive: true });
},
};
}