-
Notifications
You must be signed in to change notification settings - Fork 534
/
open-source-sarif-output.ts
106 lines (99 loc) · 3.27 KB
/
open-source-sarif-output.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import * as sarif from 'sarif';
import * as upperFirst from 'lodash.upperfirst';
import * as groupBy from 'lodash.groupby';
import * as map from 'lodash.map';
import { TestResult, AnnotatedIssue } from '../snyk-test/legacy';
import { getResults } from './get-sarif-result';
const LOCK_FILES_TO_MANIFEST_MAP = {
'Gemfile.lock': 'Gemfile',
'package-lock.json': 'package.json',
'yarn.lock': 'package.json',
'Gopkg.lock': 'Gopkg.toml',
'go.sum': 'go.mod',
'composer.lock': 'composer.json',
'Podfile.lock': 'Podfile',
'poetry.lock': 'pyproject.toml',
};
export function createSarifOutputForOpenSource(
testResults: TestResult[],
): sarif.Log {
return {
$schema:
'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
version: '2.1.0',
runs: testResults.map(replaceLockfileWithManifest).map((testResult) => ({
tool: {
driver: {
name: 'Snyk Open Source',
properties: {
artifactsScanned: testResult.dependencyCount,
},
rules: getRules(testResult),
},
},
results: getResults(testResult),
})),
};
}
function replaceLockfileWithManifest(testResult: TestResult): TestResult {
let targetFile = testResult.displayTargetFile || '';
for (const [key, replacer] of Object.entries(LOCK_FILES_TO_MANIFEST_MAP)) {
targetFile = targetFile.replace(new RegExp(key, 'g'), replacer);
}
return {
...testResult,
vulnerabilities: testResult.vulnerabilities || [],
displayTargetFile: targetFile,
};
}
export function getRules(testResult: TestResult): sarif.ReportingDescriptor[] {
const groupedVulnerabilities = groupBy(testResult.vulnerabilities, 'id');
return map(
groupedVulnerabilities,
([vuln, ...moreVulns]: AnnotatedIssue[]): sarif.ReportingDescriptor => {
const cves = vuln.identifiers?.CVE?.join();
return {
id: vuln.id,
shortDescription: {
text: `${upperFirst(vuln.severity)} severity - ${
vuln.title
} vulnerability in ${vuln.packageName}`,
},
fullDescription: {
text: cves
? `(${cves}) ${vuln.name}@${vuln.version}`
: `${vuln.name}@${vuln.version}`,
},
help: {
text: '',
markdown: `* Package Manager: ${testResult.packageManager}
* ${vuln.type === 'license' ? 'Module' : 'Vulnerable module'}: ${vuln.name}
* Introduced through: ${getIntroducedThrough(vuln)}
#### Detailed paths
${[vuln, ...moreVulns]
.map((item) => `* _Introduced through_: ${item.from.join(' › ')}`)
.join('\n')}
${vuln.description}`.replace(/##\s/g, '# '),
},
properties: {
tags: [
'security',
...(vuln.identifiers?.CWE || []),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
testResult.packageManager!,
],
cvssv3_baseScore: vuln.cvssScore, // AWS
'security-severity': String(vuln.cvssScore), // GitHub
},
};
},
);
}
function getIntroducedThrough(vuln: AnnotatedIssue) {
const [firstFrom, secondFrom] = vuln.from || [];
return vuln.from.length > 2
? `${firstFrom}, ${secondFrom} and others`
: vuln.from.length === 2
? `${firstFrom} and ${secondFrom}`
: firstFrom;
}