Skip to content

Commit 1735e3c

Browse files
committed
fix(refs): prevent out-of-root file reads from index
1 parent 2aa0831 commit 1735e3c

File tree

2 files changed

+40
-7
lines changed

2 files changed

+40
-7
lines changed

src/core/symbol-references.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,16 +68,25 @@ function buildPreviewFromFileLines(lines: string[], line: number): string {
6868
}
6969

7070
function resolveAbsoluteChunkPath(rootPath: string, chunk: IndexedChunk): string | null {
71+
const resolvedRoot = path.resolve(rootPath);
72+
const isWithinRoot = (candidate: string): boolean => {
73+
const resolvedCandidate = path.resolve(candidate);
74+
const relative = path.relative(resolvedRoot, resolvedCandidate);
75+
return Boolean(relative) && !relative.startsWith('..') && !path.isAbsolute(relative);
76+
};
77+
7178
if (typeof chunk.filePath === 'string' && chunk.filePath.trim()) {
7279
const raw = chunk.filePath.trim();
7380
if (path.isAbsolute(raw)) {
74-
return raw;
81+
return isWithinRoot(raw) ? raw : null;
7582
}
76-
return path.resolve(rootPath, raw);
83+
const resolved = path.resolve(resolvedRoot, raw);
84+
return isWithinRoot(resolved) ? resolved : null;
7785
}
7886

7987
if (typeof chunk.relativePath === 'string' && chunk.relativePath.trim()) {
80-
return path.resolve(rootPath, chunk.relativePath.trim());
88+
const resolved = path.resolve(resolvedRoot, chunk.relativePath.trim());
89+
return isWithinRoot(resolved) ? resolved : null;
8190
}
8291

8392
return null;

tests/get-symbol-references.test.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@ import {
1010
KEYWORD_INDEX_FILENAME
1111
} from '../src/constants/codebase-context.js';
1212

13+
type ToolCallRequest = {
14+
jsonrpc: '2.0';
15+
id: number;
16+
method: 'tools/call';
17+
params: { name: string; arguments: Record<string, unknown> };
18+
};
19+
20+
type ToolCallResponse = {
21+
content: Array<{ type: 'text'; text: string }>;
22+
isError?: boolean;
23+
};
24+
25+
function getToolCallHandler(server: unknown): (request: ToolCallRequest) => Promise<ToolCallResponse> {
26+
const handlers = (server as { _requestHandlers?: unknown })._requestHandlers;
27+
if (!(handlers instanceof Map)) {
28+
throw new Error('Expected server._requestHandlers to be a Map');
29+
}
30+
const handler = handlers.get('tools/call');
31+
if (typeof handler !== 'function') {
32+
throw new Error('Expected tools/call handler to be registered');
33+
}
34+
return handler as (request: ToolCallRequest) => Promise<ToolCallResponse>;
35+
}
36+
1337
describe('get_symbol_references MCP tool', () => {
1438
let tempRoot: string | null = null;
1539
let originalArgv: string[] | null = null;
@@ -106,7 +130,7 @@ describe('get_symbol_references MCP tool', () => {
106130
);
107131

108132
const { server } = await import('../src/index.js');
109-
const handler = (server as any)._requestHandlers.get('tools/call');
133+
const handler = getToolCallHandler(server);
110134

111135
const response = await handler({
112136
jsonrpc: '2.0',
@@ -202,7 +226,7 @@ describe('get_symbol_references MCP tool', () => {
202226
);
203227

204228
const { server } = await import('../src/index.js');
205-
const handler = (server as any)._requestHandlers.get('tools/call');
229+
const handler = getToolCallHandler(server);
206230

207231
const response = await handler({
208232
jsonrpc: '2.0',
@@ -283,7 +307,7 @@ describe('get_symbol_references MCP tool', () => {
283307
);
284308

285309
const { server } = await import('../src/index.js');
286-
const handler = (server as any)._requestHandlers.get('tools/call');
310+
const handler = getToolCallHandler(server);
287311

288312
const response = await handler({
289313
jsonrpc: '2.0',
@@ -362,7 +386,7 @@ describe('get_symbol_references MCP tool', () => {
362386
);
363387

364388
const { server } = await import('../src/index.js');
365-
const handler = (server as any)._requestHandlers.get('tools/call');
389+
const handler = getToolCallHandler(server);
366390

367391
const response = await handler({
368392
jsonrpc: '2.0',

0 commit comments

Comments
 (0)