mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 21:30:32 +00:00
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:
committed by
Peter Steinberger
parent
aab87ec880
commit
14baadda2c
@@ -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();
|
||||
|
||||
@@ -309,8 +309,11 @@ export function createImageTool(options?: {
|
||||
: "Analyze one or more images with the configured image model (agents.defaults.imageModel). Use image for a single path/URL, or images for multiple (up to 20). Provide a prompt describing what to analyze.";
|
||||
|
||||
const localRoots = (() => {
|
||||
const roots = getDefaultLocalRoots();
|
||||
const workspaceDir = normalizeWorkspaceDir(options?.workspaceDir);
|
||||
if (options?.fsPolicy?.workspaceOnly) {
|
||||
return workspaceDir ? [workspaceDir] : [];
|
||||
}
|
||||
const roots = getDefaultLocalRoots();
|
||||
if (!workspaceDir) {
|
||||
return roots;
|
||||
}
|
||||
|
||||
@@ -326,6 +326,34 @@ describe("createPdfTool", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("respects fsPolicy.workspaceOnly for non-sandbox pdf paths", async () => {
|
||||
await withTempAgentDir(async (agentDir) => {
|
||||
vi.stubEnv("ANTHROPIC_API_KEY", "anthropic-test");
|
||||
const workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-pdf-ws-"));
|
||||
const outsideDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-pdf-out-"));
|
||||
try {
|
||||
const cfg = withDefaultModel(ANTHROPIC_PDF_MODEL);
|
||||
const tool = createPdfTool({
|
||||
config: cfg,
|
||||
agentDir,
|
||||
workspaceDir,
|
||||
fsPolicy: { workspaceOnly: true },
|
||||
});
|
||||
expect(tool).not.toBeNull();
|
||||
|
||||
const outsidePdf = path.join(outsideDir, "secret.pdf");
|
||||
await fs.writeFile(outsidePdf, "%PDF-1.4 fake");
|
||||
|
||||
await expect(tool!.execute("t1", { prompt: "test", pdf: outsidePdf })).rejects.toThrow(
|
||||
/not under an allowed directory/i,
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
await fs.rm(outsideDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects unsupported scheme references", async () => {
|
||||
await withTempAgentDir(async (agentDir) => {
|
||||
vi.stubEnv("ANTHROPIC_API_KEY", "anthropic-test");
|
||||
|
||||
@@ -339,8 +339,11 @@ export function createPdfTool(options?: {
|
||||
: DEFAULT_MAX_PAGES;
|
||||
|
||||
const localRoots = (() => {
|
||||
const roots = getDefaultLocalRoots();
|
||||
const workspaceDir = normalizeWorkspaceDir(options?.workspaceDir);
|
||||
if (options?.fsPolicy?.workspaceOnly) {
|
||||
return workspaceDir ? [workspaceDir] : [];
|
||||
}
|
||||
const roots = getDefaultLocalRoots();
|
||||
if (!workspaceDir) {
|
||||
return roots;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user