Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions __tests__/security.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
14 changes: 13 additions & 1 deletion src/mcp/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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);

Expand Down