Skip to content

Commit

Permalink
audit: convert audit to new version of n-r-f
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Dec 10, 2018
1 parent 514558e commit dec07eb
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 92 deletions.
55 changes: 38 additions & 17 deletions lib/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,37 @@
const Bluebird = require('bluebird')

const audit = require('./install/audit.js')
const figgyPudding = require('figgy-pudding')
const fs = require('graceful-fs')
const Installer = require('./install.js').Installer
const lockVerify = require('lock-verify')
const log = require('npmlog')
const npa = require('npm-package-arg')
const npa = require('libnpm/parse-arg')
const npm = require('./npm.js')
const npmConfig = require('./config/figgy-config.js')
const output = require('./utils/output.js')
const parseJson = require('json-parse-better-errors')

const readFile = Bluebird.promisify(fs.readFile)

const AuditConfig = figgyPudding({
also: {},
'audit-level': {},
deepArgs: 'deep-args',
'deep-args': {},
dev: {},
force: {},
'dry-run': {},
global: {},
json: {},
only: {},
parseable: {},
prod: {},
production: {},
registry: {},
runId: {}
})

module.exports = auditCmd

const usage = require('./utils/usage')
Expand Down Expand Up @@ -110,12 +130,12 @@ function maybeReadFile (name) {
})
}

function filterEnv (action) {
const includeDev = npm.config.get('dev') ||
(!/^prod(uction)?$/.test(npm.config.get('only')) && !npm.config.get('production')) ||
/^dev(elopment)?$/.test(npm.config.get('only')) ||
/^dev(elopment)?$/.test(npm.config.get('also'))
const includeProd = !/^dev(elopment)?$/.test(npm.config.get('only'))
function filterEnv (action, opts) {
const includeDev = opts.dev ||
(!/^prod(uction)?$/.test(opts.only) && !opts.production) ||
/^dev(elopment)?$/.test(opts.only) ||
/^dev(elopment)?$/.test(opts.also)
const includeProd = !/^dev(elopment)?$/.test(opts.only)
const resolves = action.resolves.filter(({dev}) => {
return (dev && includeDev) || (!dev && includeProd)
})
Expand All @@ -125,7 +145,8 @@ function filterEnv (action) {
}

function auditCmd (args, cb) {
if (npm.config.get('global')) {
const opts = AuditConfig(npmConfig())
if (opts.global) {
const err = new Error('`npm audit` does not support testing globals')
err.code = 'EAUDITGLOBAL'
throw err
Expand Down Expand Up @@ -169,7 +190,7 @@ function auditCmd (args, cb) {
return audit.submitForFullReport(auditReport)
}).catch((err) => {
if (err.statusCode === 404 || err.statusCode >= 500) {
const ne = new Error(`Your configured registry (${npm.config.get('registry')}) does not support audit requests.`)
const ne = new Error(`Your configured registry (${opts.registry}) does not support audit requests.`)
ne.code = 'ENOAUDIT'
ne.wrapped = err
throw ne
Expand All @@ -178,7 +199,7 @@ function auditCmd (args, cb) {
}).then((auditResult) => {
if (args[0] === 'fix') {
const actions = (auditResult.actions || []).reduce((acc, action) => {
action = filterEnv(action)
action = filterEnv(action, opts)
if (!action) { return acc }
if (action.isMajor) {
acc.major.add(`${action.module}@${action.target}`)
Expand Down Expand Up @@ -215,7 +236,7 @@ function auditCmd (args, cb) {
review: new Set()
})
return Bluebird.try(() => {
const installMajor = npm.config.get('force')
const installMajor = opts.force
const installCount = actions.install.size + (installMajor ? actions.major.size : 0) + actions.update.size
const vulnFixCount = new Set([...actions.installFixes, ...actions.updateFixes, ...(installMajor ? actions.majorFixes : [])]).size
const metavuln = auditResult.metadata.vulnerabilities
Expand All @@ -230,16 +251,16 @@ function auditCmd (args, cb) {
return Bluebird.fromNode(cb => {
new Auditor(
npm.prefix,
!!npm.config.get('dry-run'),
!!opts['dry-run'],
[...actions.install, ...(installMajor ? actions.major : [])],
{
opts.concat({
runId: auditResult.runId,
deepArgs: [...actions.update].map(u => u.split('>'))
}
}).toJSON()
).run(cb)
}).then(() => {
const numScanned = auditResult.metadata.totalDependencies
if (!npm.config.get('json') && !npm.config.get('parseable')) {
if (!opts.json && !opts.parseable) {
output(`fixed ${vulnFixCount} of ${total} vulnerabilit${total === 1 ? 'y' : 'ies'} in ${numScanned} scanned package${numScanned === 1 ? '' : 's'}`)
if (actions.review.size) {
output(` ${actions.review.size} vulnerabilit${actions.review.size === 1 ? 'y' : 'ies'} required manual review and could not be updated`)
Expand All @@ -258,12 +279,12 @@ function auditCmd (args, cb) {
})
} else {
const levels = ['low', 'moderate', 'high', 'critical']
const minLevel = levels.indexOf(npm.config.get('audit-level'))
const minLevel = levels.indexOf(opts['audit-level'])
const vulns = levels.reduce((count, level, i) => {
return i < minLevel ? count : count + (auditResult.metadata.vulnerabilities[level] || 0)
}, 0)
if (vulns > 0) process.exitCode = 1
if (npm.config.get('parseable')) {
if (opts.parseable) {
return audit.printParseableReport(auditResult)
} else {
return audit.printFullReport(auditResult)
Expand Down
11 changes: 8 additions & 3 deletions lib/config/figgy-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ const path = require('path')
const npmSession = crypto.randomBytes(8).toString('hex')
log.verbose('npm-session', npmSession)

const NpmConfig = figgyPudding({})
const SCOPE_REGISTRY_REGEX = /@.*:registry$/gi
const NpmConfig = figgyPudding({}, {
other (key) {
return key.match(SCOPE_REGISTRY_REGEX)
}
})

let baseConfig

Expand All @@ -25,8 +30,8 @@ function mkConfig (...providers) {
dirPacker: pack.packGitDep,
hashAlgorithm: 'sha1',
includeDeprecated: false,
npmSession,
projectScope: npm.projectScope,
'npm-session': npmSession,
'project-scope': npm.projectScope,
refer: npm.registry.refer,
dmode: npm.modes.exec,
fmode: npm.modes.file,
Expand Down
141 changes: 69 additions & 72 deletions lib/install/audit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,118 +7,115 @@ exports.printInstallReport = printInstallReport
exports.printParseableReport = printParseableReport
exports.printFullReport = printFullReport

const Bluebird = require('bluebird')
const auditReport = require('npm-audit-report')
const npmConfig = require('../config/figgy-config.js')
const figgyPudding = require('figgy-pudding')
const treeToShrinkwrap = require('../shrinkwrap.js').treeToShrinkwrap
const packageId = require('../utils/package-id.js')
const output = require('../utils/output.js')
const npm = require('../npm.js')
const qw = require('qw')
const registryFetch = require('npm-registry-fetch')
const zlib = require('zlib')
const gzip = Bluebird.promisify(zlib.gzip)
const log = require('npmlog')
const regFetch = require('npm-registry-fetch')
const perf = require('../utils/perf.js')
const url = require('url')
const npa = require('npm-package-arg')
const uuid = require('uuid')
const ssri = require('ssri')
const cloneDeep = require('lodash.clonedeep')
const pacoteOpts = require('../config/pacote.js')

// used when scrubbing module names/specifiers
const runId = uuid.v4()

const InstallAuditConfig = figgyPudding({
color: {},
json: {},
unicode: {}
}, {
other (key) {
return /:registry$/.test(key)
}
})

function submitForInstallReport (auditData) {
const cfg = npm.config // avoid the no-dynamic-lookups test
const scopedRegistries = cfg.keys.filter(_ => /:registry$/.test(_)).map(_ => cfg.get(_))
perf.emit('time', 'audit compress')
// TODO: registryFetch will be adding native support for `Content-Encoding: gzip` at which point
// we'll pass in something like `gzip: true` and not need to JSON stringify, gzip or headers.
return gzip(JSON.stringify(auditData)).then(body => {
perf.emit('timeEnd', 'audit compress')
log.info('audit', 'Submitting payload of ' + body.length + 'bytes')
scopedRegistries.forEach(reg => {
// we don't care about the response so destroy the stream if we can, or leave it flowing
// so it can eventually finish and clean up after itself
fetchAudit(url.resolve(reg, '/-/npm/v1/security/audits/quick'))
.then(_ => {
_.body.on('error', () => {})
if (_.body.destroy) {
_.body.destroy()
} else {
_.body.resume()
}
}, _ => {})
})
perf.emit('time', 'audit submit')
return fetchAudit('/-/npm/v1/security/audits/quick', body).then(response => {
perf.emit('timeEnd', 'audit submit')
perf.emit('time', 'audit body')
return response.json()
}).then(result => {
perf.emit('timeEnd', 'audit body')
return result
})
const opts = InstallAuditConfig(npmConfig())
const scopedRegistries = [...opts.keys()].filter(
k => /:registry$/.test(k)
).map(k => opts[k])
scopedRegistries.forEach(registry => {
// we don't care about the response so destroy the stream if we can, or leave it flowing
// so it can eventually finish and clean up after itself
regFetch('/-/npm/v1/security/audits/quick', opts.concat({
method: 'POST',
registry,
gzip: true,
body: auditData
})).then(_ => {
_.body.on('error', () => {})
if (_.body.destroy) {
_.body.destroy()
} else {
_.body.resume()
}
}, _ => {})
})
}

function submitForFullReport (auditData) {
perf.emit('time', 'audit compress')
// TODO: registryFetch will be adding native support for `Content-Encoding: gzip` at which point
// we'll pass in something like `gzip: true` and not need to JSON stringify, gzip or headers.
return gzip(JSON.stringify(auditData)).then(body => {
perf.emit('timeEnd', 'audit compress')
log.info('audit', 'Submitting payload of ' + body.length + ' bytes')
perf.emit('time', 'audit submit')
return fetchAudit('/-/npm/v1/security/audits', body).then(response => {
perf.emit('timeEnd', 'audit submit')
perf.emit('time', 'audit body')
return response.json()
}).then(result => {
perf.emit('timeEnd', 'audit body')
result.runId = runId
return result
})
perf.emit('time', 'audit submit')
return regFetch('/-/npm/v1/security/audits/quick', opts.concat({
method: 'POST',
gzip: true,
body: auditData
})).then(response => {
perf.emit('timeEnd', 'audit submit')
perf.emit('time', 'audit body')
return response.json()
}).then(result => {
perf.emit('timeEnd', 'audit body')
return result
})
}

function fetchAudit (href, body) {
const opts = pacoteOpts()
return registryFetch(href, {
function submitForFullReport (auditData) {
perf.emit('time', 'audit submit')
const opts = InstallAuditConfig(npmConfig())
return regFetch('/-/npm/v1/security/audits', opts.concat({
method: 'POST',
headers: { 'content-encoding': 'gzip', 'content-type': 'application/json' },
config: npm.config,
npmSession: opts.npmSession,
projectScope: npm.projectScope,
log: log,
body: body
gzip: true,
body: auditData
})).then(response => {
perf.emit('timeEnd', 'audit submit')
perf.emit('time', 'audit body')
return response.json()
}).then(result => {
perf.emit('timeEnd', 'audit body')
result.runId = runId
return result
})
}

function printInstallReport (auditResult) {
const opts = InstallAuditConfig(npmConfig())
return auditReport(auditResult, {
reporter: 'install',
withColor: npm.color,
withUnicode: npm.config.get('unicode')
withColor: opts.color,
withUnicode: opts.unicode
}).then(result => output(result.report))
}

function printFullReport (auditResult) {
const opts = InstallAuditConfig(npmConfig())
return auditReport(auditResult, {
log: output,
reporter: npm.config.get('json') ? 'json' : 'detail',
withColor: npm.color,
withUnicode: npm.config.get('unicode')
reporter: opts.json ? 'json' : 'detail',
withColor: opts.color,
withUnicode: opts.unicode
}).then(result => output(result.report))
}

function printParseableReport (auditResult) {
const opts = InstallAuditConfig(npmConfig())
return auditReport(auditResult, {
log: output,
reporter: 'parseable',
withColor: npm.color,
withUnicode: npm.config.get('unicode')
withColor: opts.color,
withUnicode: opts.unicode
}).then(result => output(result.report))
}

Expand Down

0 comments on commit dec07eb

Please sign in to comment.