Skip to content

Commit 5fb6146

Browse files
committed
address Code Scanning TOCTOU race and PR review feedback
- Eliminate filesystem race condition in search-ql-code.ts (read-then-check instead of stat-then-read) - Add symlink cycle detection using lstatSync and visited-path tracking - Fix tool description field names in profile-codeql-query-from-logs.ts ({startLine,endLine} → detailLines: {start,end}) - Fix monitoring-state.json fixtures to use standard sessions format - Rename find_qll_files → find_ql_files to match actual .ql extension
1 parent 7775f49 commit 5fb6146

File tree

13 files changed

+84
-78
lines changed

13 files changed

+84
-78
lines changed

client/integration-tests/primitives/tools/codeql_resolve_files/find_qll_files/README.md renamed to client/integration-tests/primitives/tools/codeql_resolve_files/find_ql_files/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
# Integration Test: codeql_resolve_files/find_qll_files
1+
# Integration Test: codeql_resolve_files/find_ql_files
22

33
## Purpose
44

5-
Tests the `codeql_resolve_files` tool to ensure it can find QL library files by extension within a CodeQL pack directory.
5+
Tests the `codeql_resolve_files` tool to ensure it can find QL query files by extension within a CodeQL pack directory.
66

77
## Test Scenario
88

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"sessions": [
3+
{
4+
"id": "integration_test_session",
5+
"calls": [
6+
{
7+
"tool": "codeql_resolve_files",
8+
"timestamp": "2025-09-25T16:06:00.000Z",
9+
"status": "success"
10+
}
11+
]
12+
}
13+
]
14+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"sessions": [],
3+
"parameters": {
4+
"dir": "server/ql/javascript/examples/src",
5+
"include-extension": [".ql"]
6+
}
7+
}

client/integration-tests/primitives/tools/codeql_resolve_files/find_qll_files/after/monitoring-state.json

Lines changed: 0 additions & 9 deletions
This file was deleted.

client/integration-tests/primitives/tools/codeql_resolve_files/find_qll_files/before/monitoring-state.json

Lines changed: 0 additions & 9 deletions
This file was deleted.

client/integration-tests/primitives/tools/profile_codeql_query_from_logs/multi_query_raw_log/after/query-evaluation-detail.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# CodeQL Evaluator Profile — Predicate Detail
2-
# Generated: 2026-03-10T13:53:19.904Z
2+
# Generated: 2026-03-10T14:57:38.405Z
33
# Log format: raw
44
# CodeQL version: 2.23.1
55
# Use read_file with line ranges from the JSON response to access individual predicates.

client/integration-tests/primitives/tools/profile_codeql_query_from_logs/single_query_raw_log/after/query-evaluation-detail.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# CodeQL Evaluator Profile — Predicate Detail
2-
# Generated: 2026-03-10T13:53:19.907Z
2+
# Generated: 2026-03-10T14:57:38.415Z
33
# Log format: raw
44
# CodeQL version: 2.23.1
55
# Use read_file with line ranges from the JSON response to access individual predicates.
Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,14 @@
11
{
2-
"toolName": "search_ql_code",
3-
"parameters": {
4-
"pattern": "isSource",
5-
"paths": ["server/ql/javascript/examples/src"],
6-
"contextLines": 1
7-
},
8-
"success": true,
9-
"description": "Successfully searched QL files for predicate name pattern",
10-
"searchResult": {
11-
"totalMatches": 1,
12-
"returnedMatches": 1,
13-
"truncated": false,
14-
"filesSearched": 1,
15-
"results": [
16-
{
17-
"filePath": "server/ql/javascript/examples/src/ExampleQuery1/ExampleQuery1.ql",
18-
"lineNumber": 1,
19-
"lineContent": "contains isSource match"
20-
}
21-
]
22-
}
2+
"sessions": [
3+
{
4+
"id": "integration_test_session",
5+
"calls": [
6+
{
7+
"tool": "search_ql_code",
8+
"timestamp": "2025-09-25T16:06:00.000Z",
9+
"status": "success"
10+
}
11+
]
12+
}
13+
]
2314
}
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
{
2-
"toolName": "search_ql_code",
2+
"sessions": [],
33
"parameters": {
44
"pattern": "isSource",
55
"paths": ["server/ql/javascript/examples/src"],
66
"contextLines": 1
7-
},
8-
"expectedSuccess": true,
9-
"description": "Test search_ql_code for predicate name search in JavaScript examples"
7+
}
108
}

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

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -56702,7 +56702,7 @@ var StreamableHTTPServerTransport = class {
5670256702
var import_express = __toESM(require_express2(), 1);
5670356703
var import_cors = __toESM(require_lib3(), 1);
5670456704
var import_dotenv = __toESM(require_main(), 1);
56705-
import { realpathSync } from "fs";
56705+
import { realpathSync as realpathSync2 } from "fs";
5670656706
import { resolve as resolve13 } from "path";
5670756707
import { pathToFileURL as pathToFileURL5 } from "url";
5670856708

@@ -61835,7 +61835,7 @@ function buildInlineResponse(profile, topN, detailLineIndex, files) {
6183561835
function registerProfileCodeQLQueryFromLogsTool(server) {
6183661836
server.tool(
6183761837
"profile_codeql_query_from_logs",
61838-
"Parse CodeQL evaluator logs into a structured performance profile. Returns compact JSON with per-query summaries and top-N slowest predicates (name, duration, result size, eval order, dependency count). Full RA operations, pipeline-stage tuple progressions, and dependency lists are written to a line-indexed detail file \u2014 each predicate includes {startLine, endLine} for targeted read_file access to its full analysis. Works with logs from codeql query run, codeql database analyze, or vscode-codeql.",
61838+
"Parse CodeQL evaluator logs into a structured performance profile. Returns compact JSON with per-query summaries and top-N slowest predicates (name, duration, result size, eval order, dependency count). Full RA operations, pipeline-stage tuple progressions, and dependency lists are written to a line-indexed detail file \u2014 each predicate includes detailLines: {start, end} for targeted read_file access to its full analysis. Works with logs from codeql query run, codeql database analyze, or vscode-codeql.",
6183961839
{
6184061840
evaluatorLog: external_exports.string().describe(
6184161841
"Path to evaluator-log.jsonl or evaluator-log.summary.jsonl"
@@ -62805,27 +62805,37 @@ var codeqlResolveTestsTool = {
6280562805
};
6280662806

6280762807
// src/tools/codeql/search-ql-code.ts
62808-
import { readdirSync as readdirSync8, readFileSync as readFileSync11, statSync as statSync8 } from "fs";
62808+
import { lstatSync, readdirSync as readdirSync8, readFileSync as readFileSync11, realpathSync } from "fs";
6280962809
import { extname as extname2, join as join15, resolve as resolve9 } from "path";
6281062810
init_logger();
6281162811
var MAX_FILE_SIZE_BYTES = 5 * 1024 * 1024;
6281262812
var MAX_FILES_TRAVERSED = 1e4;
6281362813
function collectFiles(paths, extensions, fileCount) {
6281462814
const files = [];
62815+
const visitedDirs = /* @__PURE__ */ new Set();
6281562816
function walk(p) {
6281662817
if (fileCount.value >= MAX_FILES_TRAVERSED) return;
6281762818
let stat;
6281862819
try {
62819-
stat = statSync8(p);
62820+
stat = lstatSync(p);
6282062821
} catch {
6282162822
return;
6282262823
}
62824+
if (stat.isSymbolicLink()) return;
6282362825
if (stat.isFile()) {
6282462826
if (extensions.length === 0 || extensions.includes(extname2(p))) {
6282562827
files.push(p);
6282662828
}
6282762829
fileCount.value++;
6282862830
} else if (stat.isDirectory()) {
62831+
let realPath;
62832+
try {
62833+
realPath = realpathSync(p);
62834+
} catch {
62835+
return;
62836+
}
62837+
if (visitedDirs.has(realPath)) return;
62838+
visitedDirs.add(realPath);
6282962839
let entries;
6283062840
try {
6283162841
entries = readdirSync8(p);
@@ -62845,21 +62855,15 @@ function collectFiles(paths, extensions, fileCount) {
6284562855
return files;
6284662856
}
6284762857
function searchFile(filePath, regex, contextLines) {
62848-
let stat;
62849-
try {
62850-
stat = statSync8(filePath);
62851-
} catch {
62852-
return [];
62853-
}
62854-
if (stat.size > MAX_FILE_SIZE_BYTES) {
62855-
return [];
62856-
}
6285762858
let content;
6285862859
try {
6285962860
content = readFileSync11(filePath, "utf-8");
6286062861
} catch {
6286162862
return [];
6286262863
}
62864+
if (Buffer.byteLength(content, "utf-8") > MAX_FILE_SIZE_BYTES) {
62865+
return [];
62866+
}
6286362867
const lines = content.replace(/\r\n/g, "\n").split("\n");
6286462868
const matches = [];
6286562869
for (let i = 0; i < lines.length; i++) {
@@ -66070,7 +66074,7 @@ async function main() {
6607066074
process.exit(1);
6607166075
}
6607266076
}
66073-
var scriptPath = process.argv[1] ? realpathSync(resolve13(process.argv[1])) : void 0;
66077+
var scriptPath = process.argv[1] ? realpathSync2(resolve13(process.argv[1])) : void 0;
6607466078
if (scriptPath && import.meta.url === pathToFileURL5(scriptPath).href) {
6607566079
main();
6607666080
}

0 commit comments

Comments
 (0)