From 02ea482b3734c6eff1c0293d360fe75ea3086000 Mon Sep 17 00:00:00 2001 From: Aditya Rawat Date: Sat, 23 May 2026 00:45:02 +0530 Subject: [PATCH] fix: validate projectPath in MCP handler to block sensitive directories (#230) Validate projectPath in getCodeGraph so MCP clients can't open a codegraph in a sensitive system directory. Guarded with existsSync so nested/not-yet-created sub-paths still resolve up to the default project (preserves issue #238). Adds MCP-handler rejection tests (POSIX + Windows-gated); validated on a real Windows 11 VM. Closes #230 --- __tests__/security.test.ts | 28 ++++++++++++++++++++++++++++ src/mcp/tools.ts | 14 +++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/__tests__/security.test.ts b/__tests__/security.test.ts index abb70fe..75ac843 100644 --- a/__tests__/security.test.ts +++ b/__tests__/security.test.ts @@ -307,6 +307,34 @@ describe('MCP Input Validation', () => { const result = await handler.execute('codegraph_search', { query: 'example', limit: -5 }); expect(result.isError).toBeFalsy(); }); + + // #230: getCodeGraph must reject a sensitive system directory passed as + // projectPath before opening it. The error surfaces through execute()'s + // catch as an isError result. /etc is sensitive on POSIX; C:\Windows on + // Windows (path.resolve is platform-specific, so each case is gated). + it.runIf(process.platform !== 'win32')( + 'rejects a sensitive POSIX projectPath (/etc) via the MCP handler', + async () => { + const result = await handler.execute('codegraph_search', { + query: 'example', + projectPath: '/etc', + }); + expect(result.isError).toBe(true); + expect(result.content[0].text).toMatch(/sensitive system directory/i); + } + ); + + it.runIf(process.platform === 'win32')( + 'rejects a sensitive Windows projectPath (C:\\Windows) via the MCP handler', + async () => { + const result = await handler.execute('codegraph_search', { + query: 'example', + projectPath: 'C:\\Windows', + }); + expect(result.isError).toBe(true); + expect(result.content[0].text).toMatch(/sensitive system directory/i); + } + ); }); describe('Atomic Writes', () => { diff --git a/src/mcp/tools.ts b/src/mcp/tools.ts index dfd4154..deb8dfd 100644 --- a/src/mcp/tools.ts +++ b/src/mcp/tools.ts @@ -15,7 +15,7 @@ import { readFileSync, writeSync, } from 'fs'; -import { clamp, validatePathWithinRoot } from '../utils'; +import { clamp, validatePathWithinRoot, validateProjectPath } from '../utils'; import { tmpdir } from 'os'; import { join } from 'path'; @@ -579,6 +579,18 @@ export class ToolHandler { return this.projectCache.get(projectPath)!; } + // Reject sensitive system directories before opening. Only validate a + // path that actually exists — a nested or not-yet-created sub-path of a + // real project must still be allowed to resolve UP to its .codegraph/ + // root below (issue #238), so we don't run the existence-checking + // validator on paths that are meant to walk up. + if (existsSync(projectPath)) { + const pathError = validateProjectPath(projectPath); + if (pathError) { + throw new Error(pathError); + } + } + // Walk up parent directories to find nearest .codegraph/ const resolvedRoot = findNearestCodeGraphRoot(projectPath);