Skip to content

Commit c4db8b6

Browse files
committed
Create markdown summary file for sharing MRVA results
1 parent 47ec074 commit c4db8b6

3 files changed

Lines changed: 126 additions & 2 deletions

File tree

extensions/ql-vscode/src/remote-queries/remote-queries-markdown-generation.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@ export type MarkdownFile = string[];
1010
*/
1111
export function generateMarkdown(query: RemoteQuery, analysesResults: AnalysisResults[]): MarkdownFile[] {
1212
const files: MarkdownFile[] = [];
13+
// Generate summary file with links to individual files
14+
const summaryLines: MarkdownFile = generateMarkdownSummary(query);
1315
for (const analysisResult of analysesResults) {
1416
if (analysisResult.interpretedResults.length === 0) {
1517
// TODO: We'll add support for non-interpreted results later.
1618
continue;
1719
}
20+
21+
// Append nwo and results count to the summary table
22+
summaryLines.push(
23+
`| ${analysisResult.nwo} | [${analysisResult.interpretedResults.length} result(s)](${createGistRelativeLink(analysisResult.nwo)}) |`
24+
);
25+
26+
// Generate individual markdown file for each repository
1827
const lines = [
1928
`### ${analysisResult.nwo}`,
2029
''
@@ -25,8 +34,37 @@ export function generateMarkdown(query: RemoteQuery, analysesResults: AnalysisRe
2534
}
2635
files.push(lines);
2736
}
37+
files.push(summaryLines);
2838
return files;
39+
}
40+
41+
export function generateMarkdownSummary(query: RemoteQuery): MarkdownFile {
42+
const lines: MarkdownFile = [];
43+
// Title
44+
lines.push(`## Results for "${query.queryName}"`);
45+
lines.push('');
2946

47+
// Expandable section containing query text
48+
const queryCodeBlock = [
49+
'```ql',
50+
...query.queryText.split('\n'),
51+
'```',
52+
];
53+
lines.push(
54+
...buildExpandableMarkdownSection('Query', queryCodeBlock)
55+
);
56+
57+
// Summary table
58+
lines.push(
59+
'### Summary',
60+
''
61+
);
62+
lines.push(
63+
'| Repository | Results |',
64+
'| --- | --- |',
65+
);
66+
// nwo and result count will be appended to this table
67+
return lines;
3068
}
3169

3270
function generateMarkdownForInterpretedResult(interpretedResult: AnalysisAlert, language: string): MarkdownFile {
@@ -97,3 +135,39 @@ export function createMarkdownRemoteFileRef(
97135
const markdownLink = `[${linkText || fileLink.filePath}](${createRemoteFileRef(fileLink, startLine, endLine)})`;
98136
return markdownLink;
99137
}
138+
139+
/**
140+
* Builds an expandable markdown section of the form:
141+
* <details>
142+
* <summary>title</summary>
143+
*
144+
* contents
145+
*
146+
* </details>
147+
*/
148+
function buildExpandableMarkdownSection(title: string, contents: MarkdownFile): MarkdownFile {
149+
const expandableLines: MarkdownFile = [];
150+
expandableLines.push(
151+
'<details>',
152+
`<summary>${title}</summary>`,
153+
'',
154+
);
155+
expandableLines.push(...contents);
156+
expandableLines.push(
157+
'',
158+
'</details>',
159+
''
160+
);
161+
return expandableLines;
162+
}
163+
164+
/**
165+
* Creates anchor link to a file in the gist. This is of the form:
166+
* '#file-<name>-<file-extension>'
167+
*
168+
* TODO: Make sure these names align with the actual file names once we upload them to a gist.
169+
*/
170+
function createGistRelativeLink(nwo: string): string {
171+
const [owner, repo] = nwo.split('/');
172+
return `#file-${owner}-${repo}-md`;
173+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## Results for "Shell command built from environment values"
2+
3+
<details>
4+
<summary>Query</summary>
5+
6+
```ql
7+
/**
8+
* @name Shell command built from environment values
9+
* @description Building a shell command string with values from the enclosing
10+
* environment may cause subtle bugs or vulnerabilities.
11+
* @kind path-problem
12+
* @problem.severity warning
13+
* @security-severity 6.3
14+
* @precision high
15+
* @id js/shell-command-injection-from-environment
16+
* @tags correctness
17+
* security
18+
* external/cwe/cwe-078
19+
* external/cwe/cwe-088
20+
*/
21+
22+
import javascript
23+
import DataFlow::PathGraph
24+
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentQuery
25+
26+
from
27+
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
28+
Source sourceNode
29+
where
30+
sourceNode = source.getNode() and
31+
cfg.hasFlowPath(source, sink) and
32+
if cfg.isSinkWithHighlight(sink.getNode(), _)
33+
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
34+
else highlight = sink.getNode()
35+
select highlight, source, sink, "This shell command depends on an uncontrolled $@.", sourceNode,
36+
sourceNode.getSourceType()
37+
38+
```
39+
40+
</details>
41+
42+
### Summary
43+
44+
| Repository | Results |
45+
| --- | --- |
46+
| github/codeql | [4 result(s)](#file-github-codeql-md) |
47+
| meteor/meteor | [1 result(s)](#file-meteor-meteor-md) |

extensions/ql-vscode/test/pure-tests/remote-queries/markdown-generation/interpreted-results/markdown-generation.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,21 @@ describe('markdown generation', async function() {
1414
);
1515
const markdownFiles = generateMarkdown(problemQuery, analysesResults);
1616

17-
// Check that query has results for two repositories
18-
expect(markdownFiles.length).to.equal(2);
17+
// Check that query has results for two repositories, plus a summary file
18+
expect(markdownFiles.length).to.equal(3);
1919

2020
const markdownFile1 = markdownFiles[0]; // results for github/codeql repo
2121
const markdownFile2 = markdownFiles[1]; // results for meteor/meteor repo
22+
const markdownFile3 = markdownFiles[2]; // summary file
2223

2324
const expectedTestOutput1 = await readTestOutputFile('data/results-repo1.md');
2425
const expectedTestOutput2 = await readTestOutputFile('data/results-repo2.md');
26+
const expectedSummaryFile = await readTestOutputFile('data/summary.md');
2527

2628
// Check that markdown output is correct, after making line endings consistent
2729
expect(markdownFile1.join('\n')).to.equal(expectedTestOutput1);
2830
expect(markdownFile2.join('\n')).to.equal(expectedTestOutput2);
31+
expect(markdownFile3.join('\n')).to.equal(expectedSummaryFile);
2932
});
3033
});
3134

0 commit comments

Comments
 (0)