Skip to content

Commit 9f32616

Browse files
authored
fix: handle drive root paths in image directory security check (#301)
* fix: handle drive root paths in image directory security check When --image-dir is a drive root (e.g., E:\), the path already ends with a separator. Appending another separator created a double-separator prefix that never matched resolved paths, rejecting all valid localPath values. Closes #300 * test: fix path-validation stubs so happy-path tests assert success The stub FigmaService was an empty object, so tests that passed path validation immediately threw on downloadImages, fell into the catch block, and only "passed" via a weak negative assertion. Give the stub a downloadImages that returns [] so the handler completes normally, and assert isError is undefined.
1 parent c68a01a commit 9f32616

2 files changed

Lines changed: 22 additions & 11 deletions

File tree

‎src/mcp/tools/download-figma-images-tool.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,9 @@ async function downloadFigmaImages(
9595
// LLMs frequently produce paths like "/public/images" when they mean "public/images".
9696
const baseDir = imageDir ?? process.cwd();
9797
const resolvedPath = path.resolve(path.join(baseDir, localPath));
98-
if (resolvedPath !== baseDir && !resolvedPath.startsWith(baseDir + path.sep)) {
98+
// Drive roots (e.g. E:\) already end with a separator — avoid doubling it
99+
const baseDirPrefix = baseDir.endsWith(path.sep) ? baseDir : baseDir + path.sep;
100+
if (resolvedPath !== baseDir && !resolvedPath.startsWith(baseDirPrefix)) {
99101
return {
100102
isError: true,
101103
content: [

‎src/tests/path-validation.test.ts‎

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import { describe, expect, it } from "vitest";
33
import { downloadFigmaImagesTool } from "~/mcp/tools/download-figma-images-tool.js";
44
import { downloadFigmaImage } from "~/utils/common.js";
55

6-
const stubFigmaService = {} as Parameters<typeof downloadFigmaImagesTool.handler>[1];
6+
const stubFigmaService = {
7+
downloadImages: () => Promise.resolve([]),
8+
} as unknown as Parameters<typeof downloadFigmaImagesTool.handler>[1];
79

810
const validParams = {
911
fileKey: "abc123",
@@ -38,30 +40,37 @@ describe("download path validation", () => {
3840
});
3941

4042
it("accepts valid relative path within imageDir", async () => {
41-
// Will fail on the Figma API call — we only care that it doesn't
42-
// return the path validation error.
4343
const result = await downloadFigmaImagesTool.handler(
4444
{ ...validParams, localPath: "public/images" },
4545
stubFigmaService,
4646
imageDir,
4747
);
4848

49-
if (result.isError) {
50-
expect(result.content[0].text).not.toContain("resolves outside the allowed image directory");
51-
}
49+
expect(result.isError).toBeUndefined();
50+
});
51+
52+
it("accepts valid path when imageDir is a drive root", async () => {
53+
// Windows drive roots (E:\, D:\) already end with a separator.
54+
// path.resolve on posix treats this as a regular directory, which is fine
55+
// — the logic under test is the prefix check, not actual Windows I/O.
56+
const driveRoot = path.resolve("/");
57+
const result = await downloadFigmaImagesTool.handler(
58+
{ ...validParams, localPath: "project/src/static/images/test" },
59+
stubFigmaService,
60+
driveRoot,
61+
);
62+
63+
expect(result.isError).toBeUndefined();
5264
});
5365

5466
it("accepts path with leading slash as relative", async () => {
55-
// LLMs frequently produce paths like "/public/images" when they mean "public/images"
5667
const result = await downloadFigmaImagesTool.handler(
5768
{ ...validParams, localPath: "/public/images" },
5869
stubFigmaService,
5970
imageDir,
6071
);
6172

62-
if (result.isError) {
63-
expect(result.content[0].text).not.toContain("resolves outside the allowed image directory");
64-
}
73+
expect(result.isError).toBeUndefined();
6574
});
6675
});
6776

0 commit comments

Comments
 (0)