Create markdown summary file for sharing MRVA results

This commit is contained in:
shati-patel
2022-04-11 17:13:51 +01:00
committed by Shati Patel
parent 47ec074cfb
commit c4db8b6d4b
3 changed files with 126 additions and 2 deletions

View File

@@ -10,11 +10,20 @@ export type MarkdownFile = string[];
*/ */
export function generateMarkdown(query: RemoteQuery, analysesResults: AnalysisResults[]): MarkdownFile[] { export function generateMarkdown(query: RemoteQuery, analysesResults: AnalysisResults[]): MarkdownFile[] {
const files: MarkdownFile[] = []; const files: MarkdownFile[] = [];
// Generate summary file with links to individual files
const summaryLines: MarkdownFile = generateMarkdownSummary(query);
for (const analysisResult of analysesResults) { for (const analysisResult of analysesResults) {
if (analysisResult.interpretedResults.length === 0) { if (analysisResult.interpretedResults.length === 0) {
// TODO: We'll add support for non-interpreted results later. // TODO: We'll add support for non-interpreted results later.
continue; continue;
} }
// Append nwo and results count to the summary table
summaryLines.push(
`| ${analysisResult.nwo} | [${analysisResult.interpretedResults.length} result(s)](${createGistRelativeLink(analysisResult.nwo)}) |`
);
// Generate individual markdown file for each repository
const lines = [ const lines = [
`### ${analysisResult.nwo}`, `### ${analysisResult.nwo}`,
'' ''
@@ -25,8 +34,37 @@ export function generateMarkdown(query: RemoteQuery, analysesResults: AnalysisRe
} }
files.push(lines); files.push(lines);
} }
files.push(summaryLines);
return files; return files;
}
export function generateMarkdownSummary(query: RemoteQuery): MarkdownFile {
const lines: MarkdownFile = [];
// Title
lines.push(`## Results for "${query.queryName}"`);
lines.push('');
// Expandable section containing query text
const queryCodeBlock = [
'```ql',
...query.queryText.split('\n'),
'```',
];
lines.push(
...buildExpandableMarkdownSection('Query', queryCodeBlock)
);
// Summary table
lines.push(
'### Summary',
''
);
lines.push(
'| Repository | Results |',
'| --- | --- |',
);
// nwo and result count will be appended to this table
return lines;
} }
function generateMarkdownForInterpretedResult(interpretedResult: AnalysisAlert, language: string): MarkdownFile { function generateMarkdownForInterpretedResult(interpretedResult: AnalysisAlert, language: string): MarkdownFile {
@@ -97,3 +135,39 @@ export function createMarkdownRemoteFileRef(
const markdownLink = `[${linkText || fileLink.filePath}](${createRemoteFileRef(fileLink, startLine, endLine)})`; const markdownLink = `[${linkText || fileLink.filePath}](${createRemoteFileRef(fileLink, startLine, endLine)})`;
return markdownLink; return markdownLink;
} }
/**
* Builds an expandable markdown section of the form:
* <details>
* <summary>title</summary>
*
* contents
*
* </details>
*/
function buildExpandableMarkdownSection(title: string, contents: MarkdownFile): MarkdownFile {
const expandableLines: MarkdownFile = [];
expandableLines.push(
'<details>',
`<summary>${title}</summary>`,
'',
);
expandableLines.push(...contents);
expandableLines.push(
'',
'</details>',
''
);
return expandableLines;
}
/**
* Creates anchor link to a file in the gist. This is of the form:
* '#file-<name>-<file-extension>'
*
* TODO: Make sure these names align with the actual file names once we upload them to a gist.
*/
function createGistRelativeLink(nwo: string): string {
const [owner, repo] = nwo.split('/');
return `#file-${owner}-${repo}-md`;
}

View File

@@ -0,0 +1,47 @@
## Results for "Shell command built from environment values"
<details>
<summary>Query</summary>
```ql
/**
* @name Shell command built from environment values
* @description Building a shell command string with values from the enclosing
* environment may cause subtle bugs or vulnerabilities.
* @kind path-problem
* @problem.severity warning
* @security-severity 6.3
* @precision high
* @id js/shell-command-injection-from-environment
* @tags correctness
* security
* external/cwe/cwe-078
* external/cwe/cwe-088
*/
import javascript
import DataFlow::PathGraph
import semmle.javascript.security.dataflow.ShellCommandInjectionFromEnvironmentQuery
from
Configuration cfg, DataFlow::PathNode source, DataFlow::PathNode sink, DataFlow::Node highlight,
Source sourceNode
where
sourceNode = source.getNode() and
cfg.hasFlowPath(source, sink) and
if cfg.isSinkWithHighlight(sink.getNode(), _)
then cfg.isSinkWithHighlight(sink.getNode(), highlight)
else highlight = sink.getNode()
select highlight, source, sink, "This shell command depends on an uncontrolled $@.", sourceNode,
sourceNode.getSourceType()
```
</details>
### Summary
| Repository | Results |
| --- | --- |
| github/codeql | [4 result(s)](#file-github-codeql-md) |
| meteor/meteor | [1 result(s)](#file-meteor-meteor-md) |

View File

@@ -14,18 +14,21 @@ describe('markdown generation', async function() {
); );
const markdownFiles = generateMarkdown(problemQuery, analysesResults); const markdownFiles = generateMarkdown(problemQuery, analysesResults);
// Check that query has results for two repositories // Check that query has results for two repositories, plus a summary file
expect(markdownFiles.length).to.equal(2); expect(markdownFiles.length).to.equal(3);
const markdownFile1 = markdownFiles[0]; // results for github/codeql repo const markdownFile1 = markdownFiles[0]; // results for github/codeql repo
const markdownFile2 = markdownFiles[1]; // results for meteor/meteor repo const markdownFile2 = markdownFiles[1]; // results for meteor/meteor repo
const markdownFile3 = markdownFiles[2]; // summary file
const expectedTestOutput1 = await readTestOutputFile('data/results-repo1.md'); const expectedTestOutput1 = await readTestOutputFile('data/results-repo1.md');
const expectedTestOutput2 = await readTestOutputFile('data/results-repo2.md'); const expectedTestOutput2 = await readTestOutputFile('data/results-repo2.md');
const expectedSummaryFile = await readTestOutputFile('data/summary.md');
// Check that markdown output is correct, after making line endings consistent // Check that markdown output is correct, after making line endings consistent
expect(markdownFile1.join('\n')).to.equal(expectedTestOutput1); expect(markdownFile1.join('\n')).to.equal(expectedTestOutput1);
expect(markdownFile2.join('\n')).to.equal(expectedTestOutput2); expect(markdownFile2.join('\n')).to.equal(expectedTestOutput2);
expect(markdownFile3.join('\n')).to.equal(expectedSummaryFile);
}); });
}); });