Skip to content

Commit 7fedace

Browse files
committed
SARIF analysis tools and cache model improvements
Add sarif_extract_rule, sarif_rule_to_markdown, and sarif_compare_alerts tools with shared library (sarif-utils.ts, 34 unit tests). Extend cache model with rule_id/run_id columns and ruleId filter on all cache tools. Decompose database_analyze SARIF into per-rule cache entries. Add compare_overlapping_alerts prompt for classifying alert overlap as redundant, complementary, or false. Add extensions to SarifRunSchema. Include 3 client integration tests with shared SARIF fixture and test runner support for SARIF tools. Update e2e assertion (14 → 17 opt-in tools). Update server-tools.md and server-prompts.md documentation.
1 parent d42fe5b commit 7fedace

52 files changed

Lines changed: 6919 additions & 49 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Integration Test: sarif_compare_alerts - sink_overlap
2+
3+
## Purpose
4+
5+
Validates that the `sarif_compare_alerts` tool correctly compares code locations
6+
between two SARIF alerts from different rules to detect sink-level overlap.
7+
8+
## Inputs
9+
10+
- `test-input.sarif`: A multi-rule SARIF file where `js/sql-injection` result 0
11+
and `js/missing-rate-limiting` result 0 both reference `src/routes/users.js`
12+
but at different line ranges — so they should NOT have sink overlap.
13+
14+
## Expected Behavior
15+
16+
The tool returns a comparison result with:
17+
18+
- `overlaps`: false (the two alerts are at different lines in the same file)
19+
- `overlapMode`: "sink"
20+
- Alert details for both A and B (ruleId, location, message)
21+
- Empty `sharedLocations` array
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"toolName": "sarif_compare_alerts",
3+
"success": true,
4+
"description": "Successfully compared alert locations for sink overlap"
5+
}
Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,267 @@
1+
{
2+
"version": "2.1.0",
3+
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
4+
"runs": [
5+
{
6+
"tool": {
7+
"driver": {
8+
"name": "CodeQL",
9+
"version": "2.20.4",
10+
"rules": [
11+
{
12+
"id": "js/sql-injection",
13+
"name": "js/sql-injection",
14+
"shortDescription": {
15+
"text": "Database query built from user-controlled sources"
16+
},
17+
"fullDescription": {
18+
"text": "Building a database query from user-controlled sources is vulnerable to insertion of malicious database code by the user."
19+
},
20+
"help": {
21+
"text": "Avoid string concatenation in SQL queries.",
22+
"markdown": "# SQL Injection\n\nBuilding a database query from user-controlled sources is vulnerable to SQL injection.\n\n## Recommendation\n\nUse parameterized queries or prepared statements."
23+
},
24+
"properties": {
25+
"tags": [
26+
"security",
27+
"external/cwe/cwe-089"
28+
],
29+
"kind": "path-problem",
30+
"precision": "high",
31+
"security-severity": "8.8"
32+
}
33+
},
34+
{
35+
"id": "js/missing-rate-limiting",
36+
"name": "js/missing-rate-limiting",
37+
"shortDescription": {
38+
"text": "Missing rate limiting"
39+
},
40+
"help": {
41+
"text": "Add rate limiting to route handlers.",
42+
"markdown": "# Missing Rate Limiting\n\nWithout rate limiting, API endpoints are vulnerable to denial-of-service attacks."
43+
},
44+
"properties": {
45+
"tags": [
46+
"security",
47+
"external/cwe/cwe-770"
48+
],
49+
"kind": "problem",
50+
"precision": "medium",
51+
"security-severity": "7.5"
52+
}
53+
}
54+
]
55+
},
56+
"extensions": [
57+
{
58+
"name": "codeql/javascript-queries",
59+
"version": "1.0.0"
60+
}
61+
]
62+
},
63+
"results": [
64+
{
65+
"ruleId": "js/sql-injection",
66+
"ruleIndex": 0,
67+
"message": {
68+
"text": "This query string depends on a [user-provided value](1)."
69+
},
70+
"locations": [
71+
{
72+
"physicalLocation": {
73+
"artifactLocation": {
74+
"uri": "src/routes/users.js",
75+
"uriBaseId": "%SRCROOT%"
76+
},
77+
"region": {
78+
"startLine": 25,
79+
"startColumn": 9,
80+
"endLine": 25,
81+
"endColumn": 42
82+
}
83+
}
84+
}
85+
],
86+
"relatedLocations": [
87+
{
88+
"physicalLocation": {
89+
"artifactLocation": {
90+
"uri": "src/routes/users.js"
91+
},
92+
"region": {
93+
"startLine": 20,
94+
"startColumn": 15,
95+
"endColumn": 30
96+
}
97+
},
98+
"message": {
99+
"text": "user-provided value"
100+
}
101+
}
102+
],
103+
"codeFlows": [
104+
{
105+
"threadFlows": [
106+
{
107+
"locations": [
108+
{
109+
"location": {
110+
"physicalLocation": {
111+
"artifactLocation": {
112+
"uri": "src/routes/users.js"
113+
},
114+
"region": {
115+
"startLine": 20,
116+
"startColumn": 15,
117+
"endColumn": 30
118+
}
119+
},
120+
"message": {
121+
"text": "req.query.id"
122+
}
123+
}
124+
},
125+
{
126+
"location": {
127+
"physicalLocation": {
128+
"artifactLocation": {
129+
"uri": "src/routes/users.js"
130+
},
131+
"region": {
132+
"startLine": 22,
133+
"startColumn": 7,
134+
"endColumn": 20
135+
}
136+
},
137+
"message": {
138+
"text": "userId"
139+
}
140+
}
141+
},
142+
{
143+
"location": {
144+
"physicalLocation": {
145+
"artifactLocation": {
146+
"uri": "src/routes/users.js"
147+
},
148+
"region": {
149+
"startLine": 25,
150+
"startColumn": 9,
151+
"endColumn": 42
152+
}
153+
},
154+
"message": {
155+
"text": "db.query(...)"
156+
}
157+
}
158+
}
159+
]
160+
}
161+
]
162+
}
163+
],
164+
"partialFingerprints": {
165+
"primaryLocationLineHash": "abc123def456"
166+
}
167+
},
168+
{
169+
"ruleId": "js/sql-injection",
170+
"ruleIndex": 0,
171+
"message": {
172+
"text": "This query string depends on a [user-provided value](1)."
173+
},
174+
"locations": [
175+
{
176+
"physicalLocation": {
177+
"artifactLocation": {
178+
"uri": "src/routes/products.js",
179+
"uriBaseId": "%SRCROOT%"
180+
},
181+
"region": {
182+
"startLine": 18,
183+
"startColumn": 5,
184+
"endLine": 18,
185+
"endColumn": 48
186+
}
187+
}
188+
}
189+
],
190+
"codeFlows": [
191+
{
192+
"threadFlows": [
193+
{
194+
"locations": [
195+
{
196+
"location": {
197+
"physicalLocation": {
198+
"artifactLocation": {
199+
"uri": "src/routes/products.js"
200+
},
201+
"region": {
202+
"startLine": 12,
203+
"startColumn": 20,
204+
"endColumn": 35
205+
}
206+
},
207+
"message": {
208+
"text": "req.body.name"
209+
}
210+
}
211+
},
212+
{
213+
"location": {
214+
"physicalLocation": {
215+
"artifactLocation": {
216+
"uri": "src/routes/products.js"
217+
},
218+
"region": {
219+
"startLine": 18,
220+
"startColumn": 5,
221+
"endColumn": 48
222+
}
223+
},
224+
"message": {
225+
"text": "db.execute(...)"
226+
}
227+
}
228+
}
229+
]
230+
}
231+
]
232+
}
233+
],
234+
"partialFingerprints": {
235+
"primaryLocationLineHash": "ghi789jkl012"
236+
}
237+
},
238+
{
239+
"ruleId": "js/missing-rate-limiting",
240+
"ruleIndex": 1,
241+
"message": {
242+
"text": "This route handler performs an authorization check, but is not rate-limited."
243+
},
244+
"locations": [
245+
{
246+
"physicalLocation": {
247+
"artifactLocation": {
248+
"uri": "src/routes/users.js",
249+
"uriBaseId": "%SRCROOT%"
250+
},
251+
"region": {
252+
"startLine": 15,
253+
"startColumn": 1,
254+
"endLine": 15,
255+
"endColumn": 45
256+
}
257+
}
258+
}
259+
],
260+
"partialFingerprints": {
261+
"primaryLocationLineHash": "mno345pqr678"
262+
}
263+
}
264+
]
265+
}
266+
]
267+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"toolName": "sarif_compare_alerts",
3+
"expectedSuccess": true,
4+
"description": "Test sarif_compare_alerts detects sink overlap between two alerts in the same file"
5+
}

0 commit comments

Comments
 (0)