Skip to content

Commit fad9638

Browse files
committed
Use fstatSync(fd) to avoid OOM w/ searchFile
1 parent d6656ca commit fad9638

File tree

4 files changed

+89
-25
lines changed

4 files changed

+89
-25
lines changed

.github/instructions/server_src_ts.instructions.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,9 @@ This file contains instructions for working with TypeScript source code files in
2424
- PREFER the import of functionality from `@modelcontextprotocol/sdk` over direct implementation, unless absolutely necessary.
2525
- PREFER to implement each MCP server primitive in its own file named after the primitive, e.g., `server/src/<lib-example>/<primitive-example>.ts`.
2626
- PREFER many simple MCP server primitives that each do one thing well over fewer complex MCP server primitives that do many things.
27-
- PREFER copying and/or adapting existing `*.prompt.md` files matching one of the following patterns:
28-
- `ql/.github/prompts/*.prompt.md`
29-
- `ql/languages/*/tools/dev/*.prompt.md`
30-
- `ql/resources/{codeql,qlt}/*.prompt.md`
3127

3228
## CONTRAINTS
3329

3430
- NEVER leave any trailing whitespace on any line.
35-
- NEVER guess at what a `codeql` or `qlt` CLI subcommand does; ALWAYS verify against the official `codeql <subcommand> -h -vv` or `qlt <subcommand> -h` documentation, respectively.
31+
- NEVER guess at what a `codeql` CLI subcommand does; ALWAYS verify against the official `codeql <subcommand> -h -vv` documentation.
3632
- **NEVER use stat/lstat followed by a separate read/open on the same path** — this is a TOCTOU (Time-of-Check-Time-of-Use) race condition (CWE-367). Instead, attempt the operation directly (e.g., `readFileSync`) within a try/catch block. If you need to know the file size before reading, read first and then check the buffer size — do NOT stat then read. For directory traversal, `lstatSync` is acceptable since it is the operation itself (checking entry type), not a precursor to a separate operation.

server/dist/codeql-development-mcp-server.js

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -62913,7 +62913,7 @@ var codeqlResolveTestsTool = {
6291362913
};
6291462914

6291562915
// src/tools/codeql/search-ql-code.ts
62916-
import { createReadStream as createReadStream3, lstatSync, readdirSync as readdirSync8, readFileSync as readFileSync9, realpathSync } from "fs";
62916+
import { closeSync, createReadStream as createReadStream3, fstatSync, lstatSync, openSync, readdirSync as readdirSync8, readFileSync as readFileSync9, realpathSync } from "fs";
6291762917
import { basename as basename6, extname as extname2, join as join15, resolve as resolve9 } from "path";
6291862918
import { createInterface as createInterface3 } from "readline";
6291962919
init_logger();
@@ -62968,14 +62968,38 @@ function collectFiles(paths, extensions, fileCount) {
6296862968
return files;
6296962969
}
6297062970
async function searchFile(filePath, regex, contextLines, maxCollect) {
62971+
let fd;
62972+
try {
62973+
fd = openSync(filePath, "r");
62974+
} catch {
62975+
return { matches: [], totalCount: 0 };
62976+
}
62977+
let size;
62978+
try {
62979+
size = fstatSync(fd).size;
62980+
} catch {
62981+
try {
62982+
closeSync(fd);
62983+
} catch {
62984+
}
62985+
return { matches: [], totalCount: 0 };
62986+
}
62987+
if (size > MAX_FILE_SIZE_BYTES) {
62988+
return searchFileStreaming(filePath, regex, contextLines, maxCollect, fd);
62989+
}
6297162990
let content;
6297262991
try {
62973-
content = readFileSync9(filePath, "utf-8");
62992+
content = readFileSync9(fd, "utf-8");
6297462993
} catch {
62994+
try {
62995+
closeSync(fd);
62996+
} catch {
62997+
}
6297562998
return { matches: [], totalCount: 0 };
6297662999
}
62977-
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_SIZE_BYTES) {
62978-
return searchFileStreaming(filePath, regex, contextLines, maxCollect);
63000+
try {
63001+
closeSync(fd);
63002+
} catch {
6297963003
}
6298063004
const lines = content.replace(/\r\n/g, "\n").split("\n");
6298163005
const matches = [];
@@ -63001,19 +63025,31 @@ async function searchFile(filePath, regex, contextLines, maxCollect) {
6300163025
}
6300263026
return { matches, totalCount };
6300363027
}
63004-
async function searchFileStreaming(filePath, regex, contextLines, maxCollect) {
63028+
async function searchFileStreaming(filePath, regex, contextLines, maxCollect, fd) {
6300563029
const matches = [];
6300663030
const recentLines = [];
6300763031
const pending = [];
6300863032
let lineNumber = 0;
6300963033
let totalCount = 0;
6301063034
let rl;
6301163035
try {
63036+
const streamOpts = { encoding: "utf-8" };
63037+
if (fd !== void 0) {
63038+
streamOpts.fd = fd;
63039+
streamOpts.autoClose = true;
63040+
streamOpts.start = 0;
63041+
}
6301263042
rl = createInterface3({
63013-
input: createReadStream3(filePath, { encoding: "utf-8" }),
63043+
input: createReadStream3(filePath, streamOpts),
6301463044
crlfDelay: Infinity
6301563045
});
6301663046
} catch {
63047+
if (fd !== void 0) {
63048+
try {
63049+
closeSync(fd);
63050+
} catch {
63051+
}
63052+
}
6301763053
return { matches: [], totalCount: 0 };
6301863054
}
6301963055
for await (const line of rl) {

0 commit comments

Comments
 (0)