Skip to content

Commit 618cf48

Browse files
authored
Merge pull request #2609 from modelcontextprotocol/claude/issue-2526-20250824-0240
fix: resolve relative paths against allowed directories instead of process.cwd()
2 parents 173d991 + 87a996b commit 618cf48

File tree

3 files changed

+54
-7
lines changed

3 files changed

+54
-7
lines changed

package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/filesystem/__tests__/lib.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,30 @@ describe('Lib Functions', () => {
204204
await expect(validatePath(newFilePath))
205205
.rejects.toThrow('Parent directory does not exist');
206206
});
207+
208+
it('resolves relative paths against allowed directories instead of process.cwd()', async () => {
209+
const relativePath = 'test-file.txt';
210+
const originalCwd = process.cwd;
211+
212+
// Mock process.cwd to return a directory outside allowed directories
213+
const disallowedCwd = process.platform === 'win32' ? 'C:\\Windows\\System32' : '/root';
214+
(process as any).cwd = vi.fn(() => disallowedCwd);
215+
216+
try {
217+
const result = await validatePath(relativePath);
218+
219+
// Result should be resolved against first allowed directory, not process.cwd()
220+
const expectedPath = process.platform === 'win32'
221+
? path.resolve('C:\\Users\\test', relativePath)
222+
: path.resolve('/home/user', relativePath);
223+
224+
expect(result).toBe(expectedPath);
225+
expect(result).not.toContain(disallowedCwd);
226+
} finally {
227+
// Restore original process.cwd
228+
process.cwd = originalCwd;
229+
}
230+
});
207231
});
208232
});
209233

src/filesystem/lib.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,35 @@ export function createUnifiedDiff(originalContent: string, newContent: string, f
7272
);
7373
}
7474

75+
// Helper function to resolve relative paths against allowed directories
76+
function resolveRelativePathAgainstAllowedDirectories(relativePath: string): string {
77+
if (allowedDirectories.length === 0) {
78+
// Fallback to process.cwd() if no allowed directories are set
79+
return path.resolve(process.cwd(), relativePath);
80+
}
81+
82+
// Try to resolve relative path against each allowed directory
83+
for (const allowedDir of allowedDirectories) {
84+
const candidate = path.resolve(allowedDir, relativePath);
85+
const normalizedCandidate = normalizePath(candidate);
86+
87+
// Check if the resulting path lies within any allowed directory
88+
if (isPathWithinAllowedDirectories(normalizedCandidate, allowedDirectories)) {
89+
return candidate;
90+
}
91+
}
92+
93+
// If no valid resolution found, use the first allowed directory as base
94+
// This provides a consistent fallback behavior
95+
return path.resolve(allowedDirectories[0], relativePath);
96+
}
97+
7598
// Security & Validation Functions
7699
export async function validatePath(requestedPath: string): Promise<string> {
77100
const expandedPath = expandHome(requestedPath);
78101
const absolute = path.isAbsolute(expandedPath)
79102
? path.resolve(expandedPath)
80-
: path.resolve(process.cwd(), expandedPath);
103+
: resolveRelativePathAgainstAllowedDirectories(expandedPath);
81104

82105
const normalizedRequested = normalizePath(absolute);
83106

0 commit comments

Comments
 (0)