| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 | 'use strict'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').Installerconst lockVerify = require('lock-verify')const log = require('npmlog')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 = auditCmdconst usage = require('./utils/usage')auditCmd.usage = usage(  'audit',  '\nnpm audit [--json] [--production]' +  '\nnpm audit fix ' +  '[--force|--package-lock-only|--dry-run|--production|--only=(dev|prod)]')auditCmd.completion = function (opts, cb) {  const argv = opts.conf.argv.remain  switch (argv[2]) {    case 'audit':      return cb(null, [])    default:      return cb(new Error(argv[2] + ' not recognized'))  }}class Auditor extends Installer {  constructor (where, dryrun, args, opts) {    super(where, dryrun, args, opts)    this.deepArgs = (opts && opts.deepArgs) || []    this.runId = opts.runId || ''    this.audit = false  }  loadAllDepsIntoIdealTree (cb) {    Bluebird.fromNode(cb => super.loadAllDepsIntoIdealTree(cb)).then(() => {      if (this.deepArgs && this.deepArgs.length) {        this.deepArgs.forEach(arg => {          arg.reduce((acc, child, ii) => {            if (!acc) {              // We might not always be able to find `target` through the given              // path. If we can't we'll just ignore it.              return            }            const spec = npa(child)            const target = (              acc.requires.find(n => n.package.name === spec.name) ||              acc.requires.find(                n => audit.scrub(n.package.name, this.runId) === spec.name              )            )            if (target && ii === arg.length - 1) {              target.loaded = false              // This kills `hasModernMeta()` and forces a re-fetch              target.package = {                name: spec.name,                version: spec.fetchSpec,                _requested: target.package._requested              }              delete target.fakeChild              let parent = target.parent              while (parent) {                parent.loaded = false                parent = parent.parent              }              target.requiredBy.forEach(par => {                par.loaded = false                delete par.fakeChild              })            }            return target          }, this.idealTree)        })        return Bluebird.fromNode(cb => super.loadAllDepsIntoIdealTree(cb))      }    }).nodeify(cb)  }  // no top level lifecycles on audit  runPreinstallTopLevelLifecycles (cb) { cb() }  runPostinstallTopLevelLifecycles (cb) { cb() }}function maybeReadFile (name) {  const file = `${npm.prefix}/${name}`  return readFile(file)    .then((data) => {      try {        return parseJson(data)      } catch (ex) {        ex.code = 'EJSONPARSE'        throw ex      }    })    .catch({code: 'ENOENT'}, () => null)    .catch((ex) => {      ex.file = file      throw ex    })}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)  })  if (resolves.length) {    return Object.assign({}, action, {resolves})  }}function auditCmd (args, cb) {  const opts = AuditConfig(npmConfig())  if (opts.global) {    const err = new Error('`npm audit` does not support testing globals')    err.code = 'EAUDITGLOBAL'    throw err  }  if (args.length && args[0] !== 'fix') {    return cb(new Error('Invalid audit subcommand: `' + args[0] + '`\n\nUsage:\n' + auditCmd.usage))  }  return Bluebird.all([    maybeReadFile('npm-shrinkwrap.json'),    maybeReadFile('package-lock.json'),    maybeReadFile('package.json')  ]).spread((shrinkwrap, lockfile, pkgJson) => {    const sw = shrinkwrap || lockfile    if (!pkgJson) {      const err = new Error('No package.json found: Cannot audit a project without a package.json')      err.code = 'EAUDITNOPJSON'      throw err    }    if (!sw) {      const err = new Error('Neither npm-shrinkwrap.json nor package-lock.json found: Cannot audit a project without a lockfile')      err.code = 'EAUDITNOLOCK'      throw err    } else if (shrinkwrap && lockfile) {      log.warn('audit', 'Both npm-shrinkwrap.json and package-lock.json exist, using npm-shrinkwrap.json.')    }    const requires = Object.assign(      {},      (pkgJson && pkgJson.dependencies) || {},      (!opts.production && pkgJson && pkgJson.devDependencies) || {}    )    return lockVerify(npm.prefix).then((result) => {      if (result.status) return audit.generate(sw, requires)      const lockFile = shrinkwrap ? 'npm-shrinkwrap.json' : 'package-lock.json'      const err = new Error(`Errors were found in your ${lockFile}, run  npm install  to fix them.\n    ` +        result.errors.join('\n    '))      err.code = 'ELOCKVERIFY'      throw err    })  }).then((auditReport) => {    return audit.submitForFullReport(auditReport)  }).catch((err) => {    if (err.statusCode >= 400) {      let msg      if (err.statusCode === 401) {        msg = `Either your login credentials are invalid or your registry (${opts.registry}) does not support audit.`      } else if (err.statusCode === 404) {        msg = `Your configured registry (${opts.registry}) does not support audit requests.`      } else {        msg = `Your configured registry (${opts.registry}) may not support audit requests, or the audit endpoint may be temporarily unavailable.`      }      if (err.body.length) {        msg += '\nThe server said: ' + err.body      }      const ne = new Error(msg)      ne.code = 'ENOAUDIT'      ne.wrapped = err      throw ne    }    throw err  }).then((auditResult) => {    if (args[0] === 'fix') {      const actions = (auditResult.actions || []).reduce((acc, action) => {        action = filterEnv(action, opts)        if (!action) { return acc }        if (action.isMajor) {          acc.major.add(`${action.module}@${action.target}`)          action.resolves.forEach(({id, path}) => acc.majorFixes.add(`${id}::${path}`))        } else if (action.action === 'install') {          acc.install.add(`${action.module}@${action.target}`)          action.resolves.forEach(({id, path}) => acc.installFixes.add(`${id}::${path}`))        } else if (action.action === 'update') {          const name = action.module          const version = action.target          action.resolves.forEach(vuln => {            acc.updateFixes.add(`${vuln.id}::${vuln.path}`)            const modPath = vuln.path.split('>')            const newPath = modPath.slice(              0, modPath.indexOf(name)            ).concat(`${name}@${version}`)            if (newPath.length === 1) {              acc.install.add(newPath[0])            } else {              acc.update.add(newPath.join('>'))            }          })        } else if (action.action === 'review') {          action.resolves.forEach(({id, path}) => acc.review.add(`${id}::${path}`))        }        return acc      }, {        install: new Set(),        installFixes: new Set(),        update: new Set(),        updateFixes: new Set(),        major: new Set(),        majorFixes: new Set(),        review: new Set()      })      return Bluebird.try(() => {        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        const total = Object.keys(metavuln).reduce((acc, key) => acc + metavuln[key], 0)        if (installCount) {          log.verbose(            'audit',            'installing',            [...actions.install, ...(installMajor ? actions.major : []), ...actions.update]          )        }        return Bluebird.fromNode(cb => {          new Auditor(            npm.prefix,            !!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 (!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`)            }            if (actions.major.size) {              output(`  ${actions.major.size} package update${actions.major.size === 1 ? '' : 's'} for ${actions.majorFixes.size} vulnerabilit${actions.majorFixes.size === 1 ? 'y' : 'ies'} involved breaking changes`)              if (installMajor) {                output('  (installed due to `--force` option)')              } else {                output('  (use `npm audit fix --force` to install breaking changes;' +                       ' or refer to `npm audit` for steps to fix these manually)')              }            }          }        })      })    } else {      const levels = ['low', 'moderate', 'high', 'critical']      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 (opts.parseable) {        return audit.printParseableReport(auditResult)      } else {        return audit.printFullReport(auditResult)      }    }  }).asCallback(cb)}
 |