Tools: revalidate workspace-only patch targets (#46803)
* Tools: revalidate workspace-only patch targets * Tests: narrow apply-patch delete-path assertion
This commit is contained in:
parent
5e78c8bc95
commit
8e97b752d0
@ -29,6 +29,7 @@ Docs: https://docs.openclaw.ai
|
||||
- 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)
|
||||
- Browser/remote CDP: honor strict browser SSRF policy during remote CDP reachability and `/json/version` discovery checks, redact sensitive `cdpUrl` tokens from status output, and warn when remote CDP targets private/internal hosts.
|
||||
- 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.
|
||||
- Webhooks/runtime: move auth earlier and tighten pre-auth body limits and timeouts across bundled webhook handlers, including slow-body handling for Mattermost slash commands. Thanks @vincentkoc.
|
||||
- Subagents/follow-ups: require the same controller ownership checks for `/subagents send` as other control actions, so leaf sessions cannot message nested child runs they do not control. Thanks @vincentkoc.
|
||||
- Inbound policy hardening: tighten callback and webhook sender checks across Mattermost and Google Chat, match Nextcloud Talk rooms by stable room token, and treat explicit empty Twitch allowlists as deny-all. (#46787) Thanks @zpbrent, @ijxpwastaken and @vincentkoc.
|
||||
|
||||
@ -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("resolves delete targets before calling fs.rm", 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") {
|
||||
|
||||
@ -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 });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user