From ff6636ed5b885e1731bdc549844d433c5418424b Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 13 Mar 2026 23:37:31 +0000 Subject: [PATCH] fix: tighten path guard coverage --- src/infra/path-guards.test.ts | 32 +++++++++++++++++++++++++++++++- src/infra/path-guards.ts | 9 ++++----- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/infra/path-guards.test.ts b/src/infra/path-guards.test.ts index 28bf3d7c3b8..8882e2ecec8 100644 --- a/src/infra/path-guards.test.ts +++ b/src/infra/path-guards.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it } from "vitest"; import { hasNodeErrorCode, isNodeError, @@ -8,6 +8,21 @@ import { normalizeWindowsPathForComparison, } from "./path-guards.js"; +const originalPlatformDescriptor = Object.getOwnPropertyDescriptor(process, "platform"); + +function setPlatform(platform: NodeJS.Platform): void { + Object.defineProperty(process, "platform", { + value: platform, + configurable: true, + }); +} + +afterEach(() => { + if (originalPlatformDescriptor) { + Object.defineProperty(process, "platform", originalPlatformDescriptor); + } +}); + describe("normalizeWindowsPathForComparison", () => { it("normalizes extended-length and UNC windows paths", () => { expect(normalizeWindowsPathForComparison("\\\\?\\C:\\Users\\Peter/Repo")).toBe( @@ -47,4 +62,19 @@ describe("isPathInside", () => { expect(isPathInside("/workspace/root", "/workspace/root/nested/file.txt")).toBe(true); expect(isPathInside("/workspace/root", "/workspace/root/../escape.txt")).toBe(false); }); + + it("uses win32 path semantics for windows containment checks", () => { + setPlatform("win32"); + + expect(isPathInside(String.raw`C:\workspace\root`, String.raw`C:\workspace\root`)).toBe(true); + expect( + isPathInside(String.raw`C:\workspace\root`, String.raw`C:\workspace\root\Nested\File.txt`), + ).toBe(true); + expect( + isPathInside(String.raw`C:\workspace\root`, String.raw`C:\workspace\root\..\escape.txt`), + ).toBe(false); + expect( + isPathInside(String.raw`C:\workspace\root`, String.raw`D:\workspace\root\file.txt`), + ).toBe(false); + }); }); diff --git a/src/infra/path-guards.ts b/src/infra/path-guards.ts index a2f88a1532c..379801152e8 100644 --- a/src/infra/path-guards.ts +++ b/src/infra/path-guards.ts @@ -33,16 +33,15 @@ export function isSymlinkOpenError(value: unknown): boolean { } export function isPathInside(root: string, target: string): boolean { - const resolvedRoot = path.resolve(root); - const resolvedTarget = path.resolve(target); - if (process.platform === "win32") { - const rootForCompare = normalizeWindowsPathForComparison(resolvedRoot); - const targetForCompare = normalizeWindowsPathForComparison(resolvedTarget); + const rootForCompare = normalizeWindowsPathForComparison(path.win32.resolve(root)); + const targetForCompare = normalizeWindowsPathForComparison(path.win32.resolve(target)); const relative = path.win32.relative(rootForCompare, targetForCompare); return relative === "" || (!relative.startsWith("..") && !path.win32.isAbsolute(relative)); } + const resolvedRoot = path.resolve(root); + const resolvedTarget = path.resolve(target); const relative = path.relative(resolvedRoot, resolvedTarget); return relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative)); }