diff --git a/src/filesystem/__tests__/path-validation.test.ts b/src/filesystem/__tests__/path-validation.test.ts index 81ad247ee2..8a5dc33f54 100644 --- a/src/filesystem/__tests__/path-validation.test.ts +++ b/src/filesystem/__tests__/path-validation.test.ts @@ -439,6 +439,28 @@ describe('Path Validation', () => { expect(isPathWithinAllowedDirectories('\\\\other\\share\\project', allowed)).toBe(false); } }); + + it('allows UNC path within allowed UNC directory', () => { + if (path.sep === '\\') { + const allowed = ['\\\\server\\share\\project']; + expect(isPathWithinAllowedDirectories('\\\\server\\share\\project\\subdir\\file.txt', allowed)).toBe(true); + } + }); + + it('blocks UNC path outside allowed UNC directory', () => { + if (path.sep === '\\') { + const allowed = ['\\\\server\\share\\project']; + expect(isPathWithinAllowedDirectories('\\\\server\\share\\other', allowed)).toBe(false); + expect(isPathWithinAllowedDirectories('\\\\other\\share\\project', allowed)).toBe(false); + } + }); + + it('allows exact UNC path match', () => { + if (path.sep === '\\') { + const allowed = ['\\\\server\\share\\project']; + expect(isPathWithinAllowedDirectories('\\\\server\\share\\project', allowed)).toBe(true); + } + }); }); describe('Symlink Tests', () => { diff --git a/src/filesystem/path-validation.ts b/src/filesystem/path-validation.ts index 972e9c49d0..d74ca1b4cb 100644 --- a/src/filesystem/path-validation.ts +++ b/src/filesystem/path-validation.ts @@ -1,8 +1,35 @@ import path from 'path'; +/** + * Checks if a path is a UNC path (e.g. \\server\share). + */ +function isUNCPath(p: string): boolean { + return p.startsWith('\\\\'); +} + +/** + * Normalizes a path that may be a UNC path on Windows. + * + * On Windows, path.normalize can strip one leading backslash from UNC paths + * (e.g. \\server\share becomes \server\share), and then path.resolve + * interprets it as a drive-relative path (e.g. C:\server\share). This + * function preserves the UNC prefix through normalization. + */ +function normalizePossiblyUNCPath(p: string): string { + if (isUNCPath(p)) { + let normalized = path.normalize(p); + // path.normalize may strip a leading backslash from UNC paths + if (!normalized.startsWith('\\\\')) { + normalized = '\\' + normalized; + } + return normalized; + } + return path.resolve(path.normalize(p)); +} + /** * Checks if an absolute path is within any of the allowed directories. - * + * * @param absolutePath - The absolute path to check (will be normalized) * @param allowedDirectories - Array of absolute allowed directory paths (will be normalized) * @returns true if the path is within an allowed directory, false otherwise @@ -27,7 +54,7 @@ export function isPathWithinAllowedDirectories(absolutePath: string, allowedDire // Normalize the input path let normalizedPath: string; try { - normalizedPath = path.resolve(path.normalize(absolutePath)); + normalizedPath = normalizePossiblyUNCPath(absolutePath); } catch { return false; } @@ -51,7 +78,7 @@ export function isPathWithinAllowedDirectories(absolutePath: string, allowedDire // Normalize the allowed directory let normalizedDir: string; try { - normalizedDir = path.resolve(path.normalize(dir)); + normalizedDir = normalizePossiblyUNCPath(dir); } catch { return false; }