From 92a1ba88bd5690e1a3b110a35e423d097a9f01bd Mon Sep 17 00:00:00 2001 From: Tak Hoffman <781889+Takhoffman@users.noreply.github.com> Date: Thu, 19 Mar 2026 00:00:50 -0500 Subject: [PATCH] Fix Windows hook path containment --- src/security/scan-paths.test.ts | 29 +++++++++++++++++++++++++++++ src/security/scan-paths.ts | 7 ++----- 2 files changed, 31 insertions(+), 5 deletions(-) create mode 100644 src/security/scan-paths.test.ts 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 {