Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds a new list reporter #10

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -31,7 +31,7 @@ Report(response, options, (result) => {
## options

reporter:
specify which output format you want to use (install, detail, json)
specify which output format you want to use (install, list, detail, json)

withColor:
true || false indicates if some report elements should use colors or not
Expand Down
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -2,6 +2,7 @@

const reporters = {
install: require('./reporters/install'),
parseable: require('./reporters/parseable'),
detail: require('./reporters/detail'),
json: require('./reporters/json'),
quiet: require('./reporters/quiet')
Expand Down
123 changes: 123 additions & 0 deletions reporters/parseable.js
@@ -0,0 +1,123 @@
'use strict'

const Table = require('cli-table2')
const Utils = require('../lib/utils')

const report = function (data, options) {
const defaults = {
severityThreshold: 'info'
}

const blankChars = {
'top': ' ',
'top-mid': ' ',
'top-left': ' ',
'top-right': ' ',
'bottom': ' ',
'bottom-mid': ' ',
'bottom-left': ' ',
'bottom-right': ' ',
'left': ' ',
'left-mid': ' ',
'mid': ' ',
'mid-mid': ' ',
'right': ' ',
'right-mid': ' ',
'middle': ' '
}

const config = Object.assign({}, defaults, options)

let exit = 0

const actions = function (data, config) {

let accumulator = {
high:'',
moderate: '',
low: ''
}

if (Object.keys(data.advisories).length !== 0) {

data.actions.forEach((action) => {
let l = {}
// Start with install/update actions
if (action.action === 'update' || action.action === 'install') {
const recommendation = getRecommendation(action, config)
l.recommendation = Utils.color(recommendation.cmd, 'bold', config.withColor)
l.breaking = recommendation.isBreaking ? 'Y' : 'N'

// TODO: Verify: The advisory seems to repeat and be the same for all the 'resolves'. Is it true?
const advisory = data.advisories[action.resolves[0].id]
l.sevLevel = Utils.severityLabel(advisory.severity, config.withColor)
l.severity = advisory.title
l.package = Utils.color(advisory.module_name, 'green', config.withColor)
l.moreInfo = `https://nodesecurity.io/advisories/${advisory.id}`
l.path = `${action.resolves[0].path.split('>').join(Utils.color(' > ', 'grey', config.withColor))}`

accumulator[advisory.severity] += [action.action, l.package, l.sevLevel, l.recommendation, l.severity, l.moreInfo, l.path, l.breaking]
.join('\t') + '\n'
}

if (action.action === 'review') {

action.resolves.forEach((resolution) => {
const advisory = data.advisories[resolution.id]

l.sevLevel = Utils.severityLabel(advisory.severity, config.withColor)
l.severity = advisory.title
l.package = Utils.color(advisory.module_name, 'green', config.withColor)
l.moreInfo = `https://nodesecurity.io/advisories/${advisory.id}`
l.patchedIn = Utils.color(advisory.patched_versions.replace(' ', '') === '<0.0.0' ? 'No patch available' : advisory.patched_versions, 'bold', config.withColor)
l.path = `${resolution.path.split('>').join(Utils.color(' > ', 'grey', config.withColor))}`

accumulator[advisory.severity] += [action.action, l.package, l.sevLevel, l.patchedIn, l.severity, l.moreInfo, l.path]
.join('\t') + '\n'
}) // forEach resolves
} // is review

}) // forEach actions

}
return accumulator['high'] + accumulator['moderate'] + accumulator['low']
}

const exitCode = function (metadata) {
let total = 0
const keys = Object.keys(metadata.vulnerabilities)
for (let key of keys) {
const value = metadata.vulnerabilities[key]
total = total + value
}

if (total > 0) {
exit = 1
}
}

exitCode(data.metadata)

return {
report: actions(data, config),
exitCode: exit
}
}

const getRecommendation = function (action, config) {
if (action.action === 'install') {
const isDev = action.resolves[0].dev

return {
cmd: `npm install ${isDev ? '--dev ' : ''}${action.module}@${action.target}`,
isBreaking: action.isMajor
}
} else {
return {
cmd: `npm update ${action.module} --depth ${action.depth}`,
isBreaking: false
}
}
}

module.exports = report
72 changes: 72 additions & 0 deletions test/parseable-report-test.js
@@ -0,0 +1,72 @@
'use strict'

const tap = require('tap')
const Report = require('../')
const Keyfob = require('keyfob')

const fixtures = Keyfob.load({ path: 'test/fixtures', fn: require })

tap.test('it generates a detail report with no vulns', function (t) {
return Report(fixtures['no-vulns'], {reporter: 'parseable'}).then((report) => {
t.match(report.exitCode, 0)
t.equal(report.report.length,0)
})
})

tap.test('it generates a detail report with one vuln (update action)', function (t) {
return Report(fixtures['one-vuln'], {reporter: 'parseable'}).then((report) => {
t.match(report.exitCode, 1)
t.match(report.report, /^update/)
t.match(report.report, /npm update tough-cookie --depth 6/)
})
})

tap.test('it generates a detail report with one vuln (install action)', function (t) {
return Report(fixtures['one-vuln-install'], {reporter: 'parseable'}).then((report) => {
t.match(report.exitCode, 1)
t.match(report.report, /^install/)
t.match(report.report, /npm install knex@3.0.0/)
})
})

tap.test('it generates a detail report with one vuln (install dev dep)', function (t) {
return Report(fixtures['one-vuln-dev'], {reporter: 'parseable'}).then((report) => {
t.match(report.exitCode, 1)
t.match(report.report, /npm install --dev knex@3.0.0/)
})
})

tap.test('it generates a detail report with one vuln (review dev dep)', function (t) {
return Report(fixtures['one-vuln-dev-review'], {reporter: 'parseable'}).then((report) => {
t.match(report.exitCode, 1)
t.match(report.report, /review/)
t.match(report.report, /knex/)
})
})

tap.test('it generates a detail report with one vuln, no color', function (t) {
return Report(fixtures['one-vuln'], {reporter: 'parseable', withColor: false}).then((report) => {
t.match(report.exitCode, 1)
t.match(report.report, /\tnpm update tough-cookie --depth 6\t/)
})
})

tap.test('it generates a detail report with some vulns', function (t) {
return Report(fixtures['some-vulns'], {reporter: 'parseable'}).then((report) => {
t.match(report.exitCode, 1)
t.match(report.report, /Low/)
t.match(report.report, /High/)
t.match(report.report, /Denial of Service/)
t.match(report.report, /Cryptographically Weak PRNG/)
})
})

tap.test('it generates a detail report with some vulns, no color', function (t) {
return Report(fixtures['some-vulns'], {reporter: 'parseable', withColor: false}).then((report) => {
t.match(report.exitCode, 1)
t.match(report.report, /\tLow/)
t.match(report.report, /\tHigh/)
t.match(report.report, /\tDenial of Service/)
t.match(report.report, /\tCryptographically Weak PRNG/)
})
})