From 29c9f8c58ea94dd67d55d5e80e06f67096e56c02 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Sun, 24 Aug 2025 02:44:31 +0000 Subject: [PATCH] fix: resolve relative paths against allowed directories instead of process.cwd() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue where relative paths were incorrectly resolved against process.cwd(), causing "Access denied - path outside allowed directories" errors when the MCP server's working directory was outside the configured allowed directories. The fix implements intelligent path resolution that: 1. First tries to resolve relative paths against each allowed directory 2. Validates the resulting path is within allowed directories 3. Falls back to the first allowed directory if no valid resolution is found 4. Maintains backward compatibility by falling back to process.cwd() when no allowed directories are configured Resolves #2526 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Ola Hungerford --- src/filesystem/__tests__/lib.test.ts | 24 ++++++++++++++++++++++++ src/filesystem/lib.ts | 25 ++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) 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);