diff --git a/src/security/scan-paths.test.ts b/src/security/scan-paths.test.ts new file mode 100644 index 00000000000..a77d5d68c93 --- /dev/null +++ b/src/security/scan-paths.test.ts @@ -0,0 +1,29 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; + +const originalPlatform = process.platform; + +function setPlatform(value: NodeJS.Platform): void { + Object.defineProperty(process, "platform", { + configurable: true, + value, + }); +} + +afterEach(() => { + setPlatform(originalPlatform); + vi.restoreAllMocks(); +}); + +describe("security scan path guards", () => { + it("uses Windows-aware containment checks for differently normalized paths", async () => { + setPlatform("win32"); + const { isPathInside } = await import("./scan-paths.js"); + + expect( + isPathInside(String.raw`C:\Workspace\Root`, String.raw`c:\workspace\root\hooks\hook`), + ).toBe(true); + expect( + isPathInside(String.raw`\\?\C:\Workspace\Root`, String.raw`C:\workspace\root\hooks\hook`), + ).toBe(true); + }); +}); diff --git a/src/security/scan-paths.ts b/src/security/scan-paths.ts index e41e7ade261..29161cd86a1 100644 --- a/src/security/scan-paths.ts +++ b/src/security/scan-paths.ts @@ -1,11 +1,8 @@ import fs from "node:fs"; -import path from "node:path"; +import { isPathInside as isBoundaryPathInside } from "../infra/path-guards.js"; export function isPathInside(basePath: string, candidatePath: string): boolean { - const base = path.resolve(basePath); - const candidate = path.resolve(candidatePath); - const rel = path.relative(base, candidate); - return rel === "" || (!rel.startsWith(`..${path.sep}`) && rel !== ".." && !path.isAbsolute(rel)); + return isBoundaryPathInside(basePath, candidatePath); } function safeRealpathSync(filePath: string): string | null {