Kaynağa Gözat

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
Aditya Rawat 1 ay önce
ebeveyn
işleme
02ea482b37
2 değiştirilmiş dosya ile 41 ekleme ve 1 silme
  1. 28 0
      __tests__/security.test.ts
  2. 13 1
      src/mcp/tools.ts

+ 28 - 0
__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', () => {

+ 13 - 1
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);