Create markdown summary file for sharing MRVA results
This commit is contained in:
@@ -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`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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) |
|
||||||
@@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user