Skip to content

Commit 54d17eb

Browse files
committed
fix(mcp): only validate projectPath when it exists; add handler tests
#230's validateProjectPath ran on the raw path incl. a statSync existence check, which rejected the nested/not-yet-created sub-paths that issue #238 expects to resolve UP to the default project. Guard with existsSync so a direct sensitive dir (e.g. /etc, C:\Windows) is still refused while sub-paths walk up. Adds MCP-handler rejection tests (POSIX + Windows-gated).
1 parent 91420b1 commit 54d17eb

2 files changed

Lines changed: 38 additions & 4 deletions

File tree

__tests__/security.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,34 @@ describe('MCP Input Validation', () => {
277277
const result = await handler.execute('codegraph_search', { query: 'example', limit: -5 });
278278
expect(result.isError).toBeFalsy();
279279
});
280+
281+
// #230: getCodeGraph must reject a sensitive system directory passed as
282+
// projectPath before opening it. The error surfaces through execute()'s
283+
// catch as an isError result. /etc is sensitive on POSIX; C:\Windows on
284+
// Windows (path.resolve is platform-specific, so each case is gated).
285+
it.runIf(process.platform !== 'win32')(
286+
'rejects a sensitive POSIX projectPath (/etc) via the MCP handler',
287+
async () => {
288+
const result = await handler.execute('codegraph_search', {
289+
query: 'example',
290+
projectPath: '/etc',
291+
});
292+
expect(result.isError).toBe(true);
293+
expect(result.content[0].text).toMatch(/sensitive system directory/i);
294+
}
295+
);
296+
297+
it.runIf(process.platform === 'win32')(
298+
'rejects a sensitive Windows projectPath (C:\\Windows) via the MCP handler',
299+
async () => {
300+
const result = await handler.execute('codegraph_search', {
301+
query: 'example',
302+
projectPath: 'C:\\Windows',
303+
});
304+
expect(result.isError).toBe(true);
305+
expect(result.content[0].text).toMatch(/sensitive system directory/i);
306+
}
307+
);
280308
});
281309

282310
describe('Atomic Writes', () => {

src/mcp/tools.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -579,10 +579,16 @@ export class ToolHandler {
579579
return this.projectCache.get(projectPath)!;
580580
}
581581

582-
// Validate the path is safe before opening
583-
const pathError = validateProjectPath(projectPath);
584-
if (pathError) {
585-
throw new Error(pathError);
582+
// Reject sensitive system directories before opening. Only validate a
583+
// path that actually exists — a nested or not-yet-created sub-path of a
584+
// real project must still be allowed to resolve UP to its .codegraph/
585+
// root below (issue #238), so we don't run the existence-checking
586+
// validator on paths that are meant to walk up.
587+
if (existsSync(projectPath)) {
588+
const pathError = validateProjectPath(projectPath);
589+
if (pathError) {
590+
throw new Error(pathError);
591+
}
586592
}
587593

588594
// Walk up parent directories to find nearest .codegraph/

0 commit comments

Comments
 (0)