audit.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. const npmAuditReport = require('npm-audit-report')
  2. const ArboristWorkspaceCmd = require('../arborist-cmd.js')
  3. const auditError = require('../utils/audit-error.js')
  4. const { log, output } = require('proc-log')
  5. const reifyFinish = require('../utils/reify-finish.js')
  6. const VerifySignatures = require('../utils/verify-signatures.js')
  7. class Audit extends ArboristWorkspaceCmd {
  8. static description = 'Run a security audit'
  9. static name = 'audit'
  10. static params = [
  11. 'audit-level',
  12. 'dry-run',
  13. 'force',
  14. 'json',
  15. 'package-lock-only',
  16. 'package-lock',
  17. 'omit',
  18. 'include',
  19. 'foreground-scripts',
  20. 'ignore-scripts',
  21. 'include-attestations',
  22. ...super.params,
  23. ]
  24. static usage = ['[fix|signatures]']
  25. static async completion (opts) {
  26. const argv = opts.conf.argv.remain
  27. if (argv.length === 2) {
  28. return ['fix', 'signatures']
  29. }
  30. switch (argv[2]) {
  31. case 'fix':
  32. case 'signatures':
  33. return []
  34. default:
  35. throw Object.assign(new Error(`${argv[2]} not recognized`), {
  36. code: 'EUSAGE',
  37. })
  38. }
  39. }
  40. async exec (args) {
  41. if (args[0] === 'signatures') {
  42. await this.auditSignatures()
  43. } else {
  44. await this.auditAdvisories(args)
  45. }
  46. }
  47. async auditAdvisories (args) {
  48. const fix = args[0] === 'fix'
  49. if (this.npm.config.get('package-lock') === false && fix) {
  50. throw this.usageError('fix cannot be used without a package-lock')
  51. }
  52. const reporter = this.npm.config.get('json') ? 'json' : 'detail'
  53. const Arborist = require('@npmcli/arborist')
  54. const opts = {
  55. ...this.npm.flatOptions,
  56. audit: true,
  57. path: this.npm.prefix,
  58. reporter,
  59. workspaces: this.workspaceNames,
  60. }
  61. const arb = new Arborist(opts)
  62. await arb.audit({ fix })
  63. if (fix) {
  64. await reifyFinish(this.npm, arb)
  65. } else {
  66. // will throw if there's an error, because this is an audit command
  67. auditError(this.npm, arb.auditReport)
  68. const result = npmAuditReport(arb.auditReport, {
  69. ...opts,
  70. chalk: this.npm.chalk,
  71. })
  72. process.exitCode = process.exitCode || result.exitCode
  73. output.standard(result.report)
  74. }
  75. }
  76. async auditSignatures () {
  77. if (this.npm.global) {
  78. throw Object.assign(
  79. new Error('`npm audit signatures` does not support global packages'), {
  80. code: 'EAUDITGLOBAL',
  81. }
  82. )
  83. }
  84. log.verbose('audit', 'loading installed dependencies')
  85. const Arborist = require('@npmcli/arborist')
  86. const opts = {
  87. ...this.npm.flatOptions,
  88. path: this.npm.prefix,
  89. workspaces: this.workspaceNames,
  90. }
  91. const arb = new Arborist(opts)
  92. const tree = await arb.loadActual()
  93. let filterSet = new Set()
  94. if (opts.workspaces && opts.workspaces.length) {
  95. filterSet =
  96. arb.workspaceDependencySet(
  97. tree,
  98. opts.workspaces,
  99. this.npm.flatOptions.includeWorkspaceRoot
  100. )
  101. } else if (!this.npm.flatOptions.workspacesEnabled) {
  102. filterSet =
  103. arb.excludeWorkspacesDependencySet(tree)
  104. }
  105. const verify = new VerifySignatures(tree, filterSet, this.npm, { ...opts })
  106. await verify.run()
  107. }
  108. }
  109. module.exports = Audit