fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots

PR #28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to #31716

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
justinhuangcode
2026-03-02 16:18:02 +00:00
committed by Peter Steinberger
parent aab87ec880
commit 14baadda2c
4 changed files with 73 additions and 2 deletions

View File

@@ -461,6 +461,43 @@ describe("image tool implicit imageModel config", () => {
});
});
it("respects fsPolicy.workspaceOnly for non-sandbox image paths", async () => {
await withTempWorkspacePng(async ({ workspaceDir, imagePath }) => {
const fetch = stubMinimaxOkFetch();
const agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-image-"));
try {
const cfg = createMinimaxImageConfig();
const tool = requireImageTool(
createImageTool({
config: cfg,
agentDir,
workspaceDir,
fsPolicy: { workspaceOnly: true },
}),
);
// File inside workspace is allowed.
await expectImageToolExecOk(tool, imagePath);
expect(fetch).toHaveBeenCalledTimes(1);
// File outside workspace is rejected even without sandbox.
const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-outside-"));
const outsideImage = path.join(outsideDir, "secret.png");
await fs.writeFile(outsideImage, Buffer.from(ONE_PIXEL_PNG_B64, "base64"));
try {
await expect(
tool.execute("t2", { prompt: "Describe.", image: outsideImage }),
).rejects.toThrow(/not under an allowed directory/i);
} finally {
await fs.rm(outsideDir, { recursive: true, force: true });
}
} finally {
await fs.rm(agentDir, { recursive: true, force: true });
}
});
});
it("allows workspace images via createOpenClawCodingTools default workspace root", async () => {
await withTempWorkspacePng(async ({ imagePath }) => {
const fetch = stubMinimaxOkFetch();