diff --git a/src/filesystem/__tests__/lib.test.ts b/src/filesystem/__tests__/lib.test.ts index cc13ef0353..bc7fb86e84 100644 --- a/src/filesystem/__tests__/lib.test.ts +++ b/src/filesystem/__tests__/lib.test.ts @@ -204,6 +204,30 @@ describe('Lib Functions', () => { await expect(validatePath(newFilePath)) .rejects.toThrow('Parent directory does not exist'); }); + + it('resolves relative paths against allowed directories instead of process.cwd()', async () => { + const relativePath = 'test-file.txt'; + const originalCwd = process.cwd; + + // Mock process.cwd to return a directory outside allowed directories + const disallowedCwd = process.platform === 'win32' ? 'C:\\Windows\\System32' : '/root'; + (process as any).cwd = jest.fn(() => disallowedCwd); + + try { + const result = await validatePath(relativePath); + + // Result should be resolved against first allowed directory, not process.cwd() + const expectedPath = process.platform === 'win32' + ? path.resolve('C:\\Users\\test', relativePath) + : path.resolve('/home/user', relativePath); + + expect(result).toBe(expectedPath); + expect(result).not.toContain(disallowedCwd); + } finally { + // Restore original process.cwd + process.cwd = originalCwd; + } + }); }); }); diff --git a/src/filesystem/lib.ts b/src/filesystem/lib.ts index 240ca0d476..17e4654cd5 100644 --- a/src/filesystem/lib.ts +++ b/src/filesystem/lib.ts @@ -72,12 +72,35 @@ export function createUnifiedDiff(originalContent: string, newContent: string, f ); } +// Helper function to resolve relative paths against allowed directories +function resolveRelativePathAgainstAllowedDirectories(relativePath: string): string { + if (allowedDirectories.length === 0) { + // Fallback to process.cwd() if no allowed directories are set + return path.resolve(process.cwd(), relativePath); + } + + // Try to resolve relative path against each allowed directory + for (const allowedDir of allowedDirectories) { + const candidate = path.resolve(allowedDir, relativePath); + const normalizedCandidate = normalizePath(candidate); + + // Check if the resulting path lies within any allowed directory + if (isPathWithinAllowedDirectories(normalizedCandidate, allowedDirectories)) { + return candidate; + } + } + + // If no valid resolution found, use the first allowed directory as base + // This provides a consistent fallback behavior + return path.resolve(allowedDirectories[0], relativePath); +} + // Security & Validation Functions export async function validatePath(requestedPath: string): Promise { const expandedPath = expandHome(requestedPath); const absolute = path.isAbsolute(expandedPath) ? path.resolve(expandedPath) - : path.resolve(process.cwd(), expandedPath); + : resolveRelativePathAgainstAllowedDirectories(expandedPath); const normalizedRequested = normalizePath(absolute);