@eurekadevsecops/radar 1.0.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +1 -2
- package/package.json +2 -1
- package/scanners/depscan/run.sh +1 -1
- package/scanners/opengrep/run.sh +1 -1
- package/src/commands/scan.js +51 -10
- package/src/index.js +4 -1
- package/src/plugins/telemetry.js +6 -0
- package/src/telemetry/README.md +52 -0
- package/src/telemetry/index.js +73 -0
- package/src/util/sarif/display_findings.js +3 -3
- package/src/util/sarif/merge.js +20 -2
- package/scanners/depscan/sarif.j2 +0 -90
- package/scanners/opengrep/rules.yaml +0 -69031
package/cli.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// require('dotenv').config()
|
|
4
4
|
const path = require('node:path')
|
|
5
|
-
const
|
|
6
|
-
const cli = require(path.join(__dirname, 'src')).build({ plugins })
|
|
5
|
+
const cli = require(path.join(__dirname, 'src')).build()
|
|
7
6
|
|
|
8
7
|
// Check for updates (not in browsers).
|
|
9
8
|
cli.checkForUpdates()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eurekadevsecops/radar",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Radar is an open-source orchestrator of security scanners.",
|
|
5
5
|
"homepage": "https://www.eurekadevsecops.com/radar",
|
|
6
6
|
"keywords": [
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@persistr/clif": "^1.11.0",
|
|
31
31
|
"@persistr/clif-plugin-settings": "^2.3.1",
|
|
32
32
|
"humanize-duration": "^3.33.0",
|
|
33
|
+
"luxon": "^3.7.1",
|
|
33
34
|
"smol-toml": "^1.4.1",
|
|
34
35
|
"tiny-spinner": "^2.0.5"
|
|
35
36
|
},
|
package/scanners/depscan/run.sh
CHANGED
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
# $3 - Path to the output folder where scan results should be stored
|
|
5
5
|
|
|
6
6
|
set -e
|
|
7
|
-
docker run --rm -v $1:/app -v $2:/input -v $3:/output ghcr.io/
|
|
7
|
+
docker run --rm -v $1:/app -v $2:/input -v $3:/output ghcr.io/eurekadevsecops/radar-depscan 2>&1
|
|
8
8
|
cp $3/depscan/depscan.sarif $3/depscan.sarif
|
package/scanners/opengrep/run.sh
CHANGED
|
@@ -4,4 +4,4 @@
|
|
|
4
4
|
# $3 - Path to the output folder where scan results should be stored
|
|
5
5
|
|
|
6
6
|
set -e
|
|
7
|
-
docker run --rm -
|
|
7
|
+
docker run --rm -v $1:/app -v $2:/input -v $3:/output ghcr.io/eurekadevsecops/radar-opengrep 2>&1
|
package/src/commands/scan.js
CHANGED
|
@@ -46,11 +46,12 @@ module.exports = {
|
|
|
46
46
|
some may belong to multiple categories. You could run all available SAST
|
|
47
47
|
scanners, for example, by passing in SAST as the value for the CATEGORIES
|
|
48
48
|
option. Values are case-insensitive. Multiple values should be comma-separated.
|
|
49
|
-
Defaults to "SAST"
|
|
49
|
+
Defaults to "SAST", unless you use the SCANNERS option in which case the
|
|
50
|
+
default value for CATEGORIES is ignored. To run all scanners across all
|
|
51
|
+
categories, use the value "all" for CATEGORIES.
|
|
50
52
|
|
|
51
|
-
You can specify both SCANNERS and CATEGORIES at the same time.
|
|
52
|
-
|
|
53
|
-
command line, and any matching scanners are then used for the scan.
|
|
53
|
+
You can specify both SCANNERS and CATEGORIES at the same time. This will run
|
|
54
|
+
all scanners provided in both options.
|
|
54
55
|
|
|
55
56
|
By default, findings are displayed as high, moderate, and low. This is the
|
|
56
57
|
'security' severity format. Findings can also be displayed as errors, warnings,
|
|
@@ -81,16 +82,22 @@ module.exports = {
|
|
|
81
82
|
'$ radar scan -e warning,note ' + '(treat warnings and notes as errors)'.grey
|
|
82
83
|
],
|
|
83
84
|
run: async (toolbox, args) => {
|
|
84
|
-
const { log, scanners: availableScanners } = toolbox
|
|
85
|
+
const { log, scanners: availableScanners, telemetry } = toolbox
|
|
85
86
|
|
|
86
87
|
// Set defaults.
|
|
87
88
|
args.TARGET = path.normalize(args.TARGET ?? process.cwd())
|
|
88
89
|
args.FORMAT = args.FORMAT ?? 'security'
|
|
89
90
|
if (args.FORMAT !== 'sarif' && args.FORMAT !== 'security') throw new Error('FORMAT must be one of \'sarif\' or \'security\'')
|
|
90
|
-
args.CATEGORIES
|
|
91
|
+
if (args.CATEGORIES && !['all', 'sca', 'sast', 'dast'].includes(args.CATEGORIES.toLowerCase())) throw new Error(`CATEGORIES must be one of 'all', 'SCA', 'SAST', or 'DAST'`)
|
|
92
|
+
if (!args.SCANNERS && !args.CATEGORIES) args.CATEGORIES = 'sast'
|
|
93
|
+
if (!args.SCANNERS && (args.CATEGORIES === 'all')) args.CATEGORIES = ''
|
|
94
|
+
if (args.SCANNERS) {
|
|
95
|
+
const unknownScanners = args.SCANNERS.split(',').filter(name => !availableScanners.find(scanner => scanner.name === name))
|
|
96
|
+
if (unknownScanners.length > 1) throw new Error(`Unknown scanners: ${unknownScanners.join(', ')}`)
|
|
97
|
+
else if (unknownScanners.length === 1) throw new Error(`Unknown scanner: ${unknownScanners[0]}`)
|
|
98
|
+
}
|
|
91
99
|
|
|
92
100
|
// Set scan parameters.
|
|
93
|
-
// const target = args.TARGET ? path.resolve(args.TARGET) : "$PWD" // target to scan
|
|
94
101
|
const target = path.resolve(args.TARGET) // target to scan
|
|
95
102
|
const assets = path.join(__dirname, '..', '..', 'scanners') // scanner assets
|
|
96
103
|
const outdir = fs.mkdtempSync(path.join(os.tmpdir(), 'radar-')) // output directory
|
|
@@ -114,8 +121,23 @@ module.exports = {
|
|
|
114
121
|
return false
|
|
115
122
|
})
|
|
116
123
|
|
|
124
|
+
// At least one scanner must be selected in order to have a successful scan.
|
|
125
|
+
if (scanners.length === 0) throw new Error('No available scanners selected.')
|
|
126
|
+
|
|
127
|
+
// Send telemetry: scan started.
|
|
128
|
+
let scanID = undefined
|
|
129
|
+
const isTelemetryEnabled = telemetry.enabled()
|
|
130
|
+
if (isTelemetryEnabled) {
|
|
131
|
+
// TODO: Should pass scanID to the server; not read it from the server.
|
|
132
|
+
const response = await telemetry.send(`scans/started`, {}, { scanners: scanners.map((scanner) => scanner.name) })
|
|
133
|
+
const data = await response.json()
|
|
134
|
+
scanID = data.scan_id
|
|
135
|
+
}
|
|
136
|
+
|
|
117
137
|
// Run scanners.
|
|
118
138
|
let isScanCompleted = true
|
|
139
|
+
let runLog = ''
|
|
140
|
+
log(`Running ${scanners.length} of ${availableScanners.length} scanners:`)
|
|
119
141
|
for (const scanner of scanners) {
|
|
120
142
|
let label = scanner.name
|
|
121
143
|
const spinner = new Spinner()
|
|
@@ -137,7 +159,9 @@ module.exports = {
|
|
|
137
159
|
cmd = cmd.replaceAll('${output}', outdir)
|
|
138
160
|
/* eslint-enable no-template-curly-in-string */
|
|
139
161
|
|
|
140
|
-
|
|
162
|
+
const { stdout } = await exec(cmd)
|
|
163
|
+
runLog += stdout
|
|
164
|
+
|
|
141
165
|
if (!args.QUIET) spinner.success(label)
|
|
142
166
|
} catch (error) {
|
|
143
167
|
isScanCompleted = false
|
|
@@ -202,11 +226,27 @@ module.exports = {
|
|
|
202
226
|
fs.writeFileSync(outfile, JSON.stringify(sarif))
|
|
203
227
|
}
|
|
204
228
|
|
|
205
|
-
// TODO: Upload SARIF to user's Eureka account.
|
|
206
|
-
|
|
207
229
|
// Count findings by severity level.
|
|
208
230
|
const summary = await sariftools.summarize(sarif, target)
|
|
209
231
|
|
|
232
|
+
// Send telemetry.
|
|
233
|
+
if (isTelemetryEnabled && scanID) {
|
|
234
|
+
// Scan completed.
|
|
235
|
+
telemetry.send(`scans/:scanID/completed`, { scanID }, {
|
|
236
|
+
findings: {
|
|
237
|
+
total: summary.errors.length + summary.warnings.length + summary.notes.length,
|
|
238
|
+
critical: 0,
|
|
239
|
+
high: summary.errors.length,
|
|
240
|
+
med: summary.warnings.length,
|
|
241
|
+
low: summary.notes.length
|
|
242
|
+
}
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Send sensitive telemetry: scan log and scan findings.
|
|
246
|
+
telemetry.sendSensitive(`scans/:scanID/log`, { scanID }, runLog)
|
|
247
|
+
telemetry.sendSensitive(`scans/:scanID/findings`, { scanID }, { findings: sarif })
|
|
248
|
+
}
|
|
249
|
+
|
|
210
250
|
// Display summarized findings.
|
|
211
251
|
if (!args.QUIET) {
|
|
212
252
|
log()
|
|
@@ -227,6 +267,7 @@ module.exports = {
|
|
|
227
267
|
} else {
|
|
228
268
|
exitCode = 0x10
|
|
229
269
|
if (!args.QUIET) log('Scan NOT completed!')
|
|
270
|
+
if (telemetry.enabled()) telemetry.send(`scans/:scanID/failed`, { scanID })
|
|
230
271
|
}
|
|
231
272
|
|
|
232
273
|
// Clean up.
|
package/src/index.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
const { build } = require('@persistr/clif')
|
|
2
2
|
const pkg = require('../package.json')
|
|
3
3
|
const commands = require('./commands')
|
|
4
|
+
const path = require('node:path')
|
|
4
5
|
|
|
5
6
|
// Plugins.
|
|
6
7
|
const plugins = {
|
|
7
|
-
settings: require('@persistr/clif-plugin-settings')
|
|
8
|
+
settings: require('@persistr/clif-plugin-settings'),
|
|
9
|
+
scanners: require(path.join(__dirname, 'plugins', 'scanners')),
|
|
10
|
+
telemetry: require(path.join(__dirname, 'plugins', 'telemetry'))
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
module.exports = {
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# TELEMETRY
|
|
2
|
+
|
|
3
|
+
## How to use this module to send telemetry
|
|
4
|
+
|
|
5
|
+
### Step 1: Import the module:
|
|
6
|
+
```js
|
|
7
|
+
const telemetry = require('./telemetry')
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### Step 2: Call telemetry.send to send telemetry. Choose one of several telemetry events available.
|
|
11
|
+
|
|
12
|
+
All available telemetry events:
|
|
13
|
+
```js
|
|
14
|
+
telemetry.send(`scans/:scanID/started`, { scanID }, { scanners })
|
|
15
|
+
telemetry.send(`scans/:scanID/completed`, { scanID }, { status, findings, log })
|
|
16
|
+
telemetry.sendSensitive(`scans/:scanID/log`, { scanID }, log)
|
|
17
|
+
telemetry.sendSensitive(`scans/:scanID/findings`, { scanID }, sarif)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
To send a telemetry event indicating that a scan has started:
|
|
21
|
+
```js
|
|
22
|
+
telemetry.send(`scans/:scanID/started`, { scanID }, { scanners })
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
To send a telemetry event indicating that a scan has completed:
|
|
26
|
+
```js
|
|
27
|
+
telemetry.send(`scans/:scanID/completed`, { scanID }, { status, findings, log })
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
To send a telemetry event with the scan console log:
|
|
31
|
+
```js
|
|
32
|
+
telemetry.sendSensitive(`scans/:scanID/log`, { scanID }, log)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To send a telemetry event with the scan vulnerability findings:
|
|
36
|
+
```js
|
|
37
|
+
telemetry.sendSensitive(`scans/:scanID/findings`, { scanID }, sarif)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Step 3: (optional) You can await on telemetry.send to read the fetch response.
|
|
41
|
+
|
|
42
|
+
```js
|
|
43
|
+
const response = await telemetry.send(`scans/:scanID/started`, { scanID }, { scanners })
|
|
44
|
+
console.log(response.statusCode)
|
|
45
|
+
console.log(await response.json())
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Sensitive data in telemtry events
|
|
49
|
+
|
|
50
|
+
NOTE: Telemetry events that contain vulnerability data are sent to the Vulnerability
|
|
51
|
+
Data Backend (VDBE) which can be hosted by customers directly on their own infrastructure.
|
|
52
|
+
These telemetry events must be sent using the `sendSensitive` function.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const package = require('../../package.json')
|
|
2
|
+
const { DateTime } = require("luxon")
|
|
3
|
+
|
|
4
|
+
const EWA_URL = process.env.EWA_URL ?? 'https://app.eurekadevsecops.com'
|
|
5
|
+
const VDBE_URL = process.env.VDBE_URL ?? 'https://vulns.eurekadevsecops.com'
|
|
6
|
+
|
|
7
|
+
const USER_AGENT = `Radar/${package.version} (${package.pkgname}@${package.version}; ${process?.platform}-${process?.arch}; ${process?.release?.name}-${process?.version})`
|
|
8
|
+
|
|
9
|
+
const enabled = () => {
|
|
10
|
+
if (process.env.EUREKA_AGENT_TOKEN) return true
|
|
11
|
+
return false
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const send = async (path, params, body, token) => {
|
|
15
|
+
return fetch(toURL(path, params), {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: {
|
|
18
|
+
'Authorization': `Bearer ${token ?? process.env.EUREKA_AGENT_TOKEN}`,
|
|
19
|
+
'Content-Type': toContentType(path),
|
|
20
|
+
'User-Agent': USER_AGENT,
|
|
21
|
+
'Accept': 'application/json'
|
|
22
|
+
},
|
|
23
|
+
body: toBody(path, body)
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const sendSensitive = async (path, params, body) => {
|
|
28
|
+
return send(path, params, body, await token())
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const token = async () => {
|
|
32
|
+
const response = await fetch(`${EWA_URL}/vdbe/token`, {
|
|
33
|
+
method: 'POST',
|
|
34
|
+
headers: {
|
|
35
|
+
'Authorization': `Bearer ${process.env.EUREKA_AGENT_TOKEN}`,
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'User-Agent': USER_AGENT,
|
|
38
|
+
'Accept': 'application/json'
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
if (!response.ok) throw new Error(`Internal Error: Failed to get VDBE auth token from EWA: ${response.statusText}: ${await response.text()}`)
|
|
42
|
+
const data = await response.json()
|
|
43
|
+
return data.token
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const toURL = (path, params) => {
|
|
47
|
+
if (path === `scans/started`) return `${EWA_URL}/scans/started`
|
|
48
|
+
if (path === `scans/:scanID/completed`) return `${EWA_URL}/scans/${params.scanID}/completed`
|
|
49
|
+
if (path === `scans/:scanID/failed`) return `${EWA_URL}/scans/${params.scanID}/completed`
|
|
50
|
+
if (path === `scans/:scanID/log`) return `${VDBE_URL}/scans/${params.scanID}/log`
|
|
51
|
+
if (path === `scans/:scanID/findings`) return `${VDBE_URL}/scans/${params.scanID}/findings`
|
|
52
|
+
throw new Error(`Internal Error: Unknown telemetry event: ${path}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const toContentType = (path) => {
|
|
56
|
+
if (path === `scans/:scanID/log`) return 'text/plain'
|
|
57
|
+
return 'application/json'
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const toBody = (path, body) => {
|
|
61
|
+
if (path === `scans/:scanID/log`) return body
|
|
62
|
+
if (path === `scans/started`) body = { ...body, timestamp: DateTime.now().toISO(), profile_id: process.env.EUREKA_PROFILE }
|
|
63
|
+
if (path === `scans/:scanID/completed`) body = { ...body, timestamp: DateTime.now().toISO(), status: 'success', log: { sizeBytes: 0, warnings: 0, errors: 0, link: 'none' }, params: { id: '' }}
|
|
64
|
+
if (path === `scans/:scanID/failed`) body = { ...body, timestamp: DateTime.now().toISO(), status: 'failure', findings: { total: 0, critical: 0, high: 0, med: 0, low: 0 }, log: { sizeBytes: 0, warnings: 0, errors: 0, link: 'none' }, params: { id: '' }}
|
|
65
|
+
if (path === `scans/:scanID/findings`) body = { ...body, profileId: process.env.EUREKA_PROFILE }
|
|
66
|
+
return JSON.stringify(body)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
enabled,
|
|
71
|
+
send,
|
|
72
|
+
sendSensitive
|
|
73
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const levels = require('./levels')
|
|
2
2
|
module.exports = async (summary, format, log) => {
|
|
3
|
-
for (const finding of summary.notes) log(`${finding.artifact.name}:${finding.artifact.line}: ` + `${levels[format].single.note}`.bold + `${levels[format].single.suffix}:` + ` ${finding.message}\n`)
|
|
4
|
-
for (const finding of summary.warnings) log(`${finding.artifact.name}:${finding.artifact.line}: ` + `${levels[format].single.warning}`.bold.yellow + `${levels[format].single.suffix}:` + ` ${finding.message}\n`)
|
|
5
|
-
for (const finding of summary.errors) log(`${finding.artifact.name}:${finding.artifact.line}: ` + `${levels[format].single.error}`.bold.red + `${levels[format].single.suffix}:` + ` ${finding.message}\n`)
|
|
3
|
+
for (const finding of summary.notes) log(`${finding.artifact.name}:${finding.artifact.line}: ` + `${levels[format].single.note}`.bold + `${levels[format].single.suffix}:` + ` ${finding.tool}:` + ` ${finding.message}\n`)
|
|
4
|
+
for (const finding of summary.warnings) log(`${finding.artifact.name}:${finding.artifact.line}: ` + `${levels[format].single.warning}`.bold.yellow + `${levels[format].single.suffix}:` + ` ${finding.tool}:` + ` ${finding.message}\n`)
|
|
5
|
+
for (const finding of summary.errors) log(`${finding.artifact.name}:${finding.artifact.line}: ` + `${levels[format].single.error}`.bold.red + `${levels[format].single.suffix}:` + ` ${finding.tool}:` + ` ${finding.message}\n`)
|
|
6
6
|
}
|
package/src/util/sarif/merge.js
CHANGED
|
@@ -12,6 +12,7 @@ module.exports = {
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
const fs = require('node:fs')
|
|
15
|
+
const path = require('node:path')
|
|
15
16
|
module.exports = async (files, outfile) => {
|
|
16
17
|
const sarif = {
|
|
17
18
|
version: '2.1.0',
|
|
@@ -25,14 +26,16 @@ module.exports = async (files, outfile) => {
|
|
|
25
26
|
for (const run of scan.runs) {
|
|
26
27
|
const tool = {
|
|
27
28
|
driver: {
|
|
28
|
-
name:
|
|
29
|
+
name: path.parse(file).name,
|
|
29
30
|
semanticVersion: run.tool.driver.semanticVersion,
|
|
30
31
|
informationUri: run.tool.driver.informationUri,
|
|
31
|
-
properties: run.tool.driver.properties,
|
|
32
|
+
properties: run.tool.driver.properties ?? {},
|
|
32
33
|
rules: []
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
tool.driver.properties.officialName = run.tool.driver.name
|
|
38
|
+
|
|
36
39
|
const rules = new Map()
|
|
37
40
|
for (const result of run.results) {
|
|
38
41
|
rules.set(result.ruleId, true)
|
|
@@ -48,5 +51,20 @@ module.exports = async (files, outfile) => {
|
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
// Clean up the SARIF object, to conform to the SARIF schema.
|
|
55
|
+
for (const run of sarif.runs) {
|
|
56
|
+
for (const result of run.results) {
|
|
57
|
+
const partialFingerprints = result.partialFingerprints
|
|
58
|
+
if (partialFingerprints) {
|
|
59
|
+
if (!partialFingerprints.commitSha) delete partialFingerprints.commitSha
|
|
60
|
+
if (!partialFingerprints.email) delete partialFingerprints.email
|
|
61
|
+
if (!partialFingerprints.author) delete partialFingerprints.author
|
|
62
|
+
if (!partialFingerprints.date) delete partialFingerprints.date
|
|
63
|
+
if (!partialFingerprints.commitMessage) delete partialFingerprints.commitMessage
|
|
64
|
+
if (Object.keys(partialFingerprints).length === 0) delete result.partialFingerprints
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
51
69
|
fs.writeFileSync(outfile, JSON.stringify(sarif))
|
|
52
70
|
}
|
|
@@ -1,90 +0,0 @@
|
|
|
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": "{{ metadata.tools.components[1].name }}",
|
|
9
|
-
"semanticVersion": "{{ metadata.tools.components[1].version }}",
|
|
10
|
-
"informationUri": "https://github.com/owasp-dep-scan/dep-scan",
|
|
11
|
-
"properties": {
|
|
12
|
-
"protocol_version": "v1.0.0",
|
|
13
|
-
"scanner_name": "{{ metadata.tools.components[1].name }}",
|
|
14
|
-
"scanner_version": "{{ metadata.tools.components[1].version }}",
|
|
15
|
-
"db": "https://github.com/AppThreat/vulnerability-db",
|
|
16
|
-
"scan_mode": "source"
|
|
17
|
-
},
|
|
18
|
-
"rules": [ {% for vuln in vulnerabilities %}{% set package = vuln['bom-ref'].split(':')[1] %}
|
|
19
|
-
{
|
|
20
|
-
"id": "{{ vuln['bom-ref'] }}",
|
|
21
|
-
"shortDescription": {
|
|
22
|
-
"text": "Vulnerable pkg: {{ package }}\nCVE: {{ vuln.id }}\nFix: {{ vuln.recommendation }}\n\n{% for prop in vuln.properties %}{{ prop.name }}: {{ prop.value }}\n{% endfor %}"
|
|
23
|
-
},
|
|
24
|
-
"fullDescription": {
|
|
25
|
-
"text": {{ vuln.description | tojson }}
|
|
26
|
-
},
|
|
27
|
-
"help": {
|
|
28
|
-
"text": "{{ vuln.recommendation }}"
|
|
29
|
-
},
|
|
30
|
-
"helpUri": "{% if vuln.source and vuln.source.url %}{{ vuln.source.url }}{% elif vuln.id and 'NPM-' in vuln.id %}https://osv.dev/vulnerability/{{ vuln.id.split('/')[0] }}{% else %}https://unknownhelpuri.com{% endif %}",
|
|
31
|
-
"properties": {
|
|
32
|
-
"tags": [
|
|
33
|
-
{% for prop in vuln.properties %}{% if 'Used' in prop.value -%}
|
|
34
|
-
"{{ 'Used' }}",
|
|
35
|
-
{% endif -%}{% if 'Reachable' in prop.value -%}
|
|
36
|
-
"{{ 'Reachable' }}",
|
|
37
|
-
{% endif -%}{% if 'Confirmed' in prop.value -%}
|
|
38
|
-
"{{ 'Confirmed' }}",
|
|
39
|
-
{% endif -%}{% if 'Exploits' in prop.value -%}
|
|
40
|
-
"{{ 'Exploits' }}",
|
|
41
|
-
{% endif -%}{% if 'PoC' in prop.value -%}
|
|
42
|
-
"{{ 'PoC' }}",
|
|
43
|
-
{% endif -%}{% if 'true' in prop.value and 'prioritized' in prop.name -%}
|
|
44
|
-
"{{ 'Prioritized' }}",
|
|
45
|
-
{% endif -%}{% endfor %}{% if 'MAL-' in vuln.id -%}
|
|
46
|
-
"{{ 'Malware' }}",
|
|
47
|
-
{% endif -%}"{{ vuln['id'] }}"
|
|
48
|
-
]
|
|
49
|
-
}
|
|
50
|
-
}{% if not loop.last %},{% endif %}
|
|
51
|
-
{% endfor %}
|
|
52
|
-
]
|
|
53
|
-
}
|
|
54
|
-
},
|
|
55
|
-
"results": [ {% for vuln in vulnerabilities %}{% set package = vuln['bom-ref'].split(':')[1] %}
|
|
56
|
-
{
|
|
57
|
-
"ruleId": "{{ vuln['bom-ref'] }}",
|
|
58
|
-
"level": {% if vuln.ratings[0].severity in ['critical','high'] -%}
|
|
59
|
-
"{{ 'error' }}",
|
|
60
|
-
{% endif -%}{% if vuln.ratings[0].severity in ['medium'] -%}
|
|
61
|
-
"{{ 'warning' }}",
|
|
62
|
-
{% endif -%}{% if vuln.ratings[0].severity in ['low'] -%}
|
|
63
|
-
"{{ 'note' }}",
|
|
64
|
-
{% endif -%}
|
|
65
|
-
"message": {
|
|
66
|
-
"text": "Vulnerability {{ vuln.id }} in pkg {{ package }}"
|
|
67
|
-
},
|
|
68
|
-
"locations": [
|
|
69
|
-
{
|
|
70
|
-
"physicalLocation": {
|
|
71
|
-
"artifactLocation": {
|
|
72
|
-
"uri": "lockfile",
|
|
73
|
-
"uriBaseId": "%SRCROOT%"
|
|
74
|
-
},
|
|
75
|
-
"region": {
|
|
76
|
-
"startLine": 1
|
|
77
|
-
}
|
|
78
|
-
},
|
|
79
|
-
"message": {
|
|
80
|
-
"text": "Vulnerability {{ vuln.id }} in pkg {{ package }}"
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
]
|
|
84
|
-
}
|
|
85
|
-
{% if not loop.last %},{% endif %}
|
|
86
|
-
{% endfor %}
|
|
87
|
-
]
|
|
88
|
-
}
|
|
89
|
-
]
|
|
90
|
-
}
|