From 664266d7f0e2958d58442b9df80dd93d6c27ca91 Mon Sep 17 00:00:00 2001 From: Stephane Rufer Date: Wed, 8 Jul 2020 10:03:15 -0700 Subject: [PATCH] Add mute option to `yarn audit` The mute option allows a user to mute advisories that are not fixable, or not relevant to the project Fixes #6669 --- src/cli/commands/audit.js | 52 ++++++++++++++++++++++- src/reporters/base-reporter.js | 3 ++ src/reporters/console/console-reporter.js | 9 ++++ src/reporters/json-reporter.js | 4 ++ src/reporters/lang/en.js | 1 + 5 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/cli/commands/audit.js b/src/cli/commands/audit.js index 7f2d92ebf7..76ab833188 100644 --- a/src/cli/commands/audit.js +++ b/src/cli/commands/audit.js @@ -19,6 +19,7 @@ const gzip = promisify(zlib.gzip); export type AuditOptions = { groups: Array, level?: string, + muteIssues?: string[], }; export type AuditNode = { @@ -134,6 +135,11 @@ export function setFlags(commander: Object) { info|low|moderate|high|critical. Default: info`, 'info', ); + commander.option( + '--mute [ ...]', + `Mute any of the specified advisory ids`, + muteIssues => muteIssues && muteIssues.split(','), + ); } export function hasWrapper(commander: Object, args: Array): boolean { @@ -145,6 +151,7 @@ export async function run(config: Config, reporter: Reporter, flags: Object, arg const audit = new Audit(config, reporter, { groups: flags.groups || OWNED_DEPENDENCY_TYPES, level: flags.level || DEFAULT_LOG_LEVEL, + muteIssues: flags.mute, }); const lockfile = await Lockfile.fromDirectory(config.lockfileFolder, reporter); const install = new Install({}, config, reporter, lockfile); @@ -260,8 +267,45 @@ export default class Audit { if (!responseJson.metadata) { throw new Error(`Unexpected audit response (Missing Metadata): ${JSON.stringify(responseJson, null, 2)}`); } - this.reporter.verbose(`Audit Response: ${JSON.stringify(responseJson, null, 2)}`); - return responseJson; + const filteredResponse = responseJson; + if (this.options.muteIssues && this.options.muteIssues.length) { + const newAdvisories = {}; + const newActions = []; + const newMuted = []; + const newVulnerabilities = Object.assign({}, responseJson.metadata.vulnerabilities); + for (const [key, value] of Object.entries(responseJson.advisories)) { + if (this.options.muteIssues && this.options.muteIssues.includes(key)) { + newMuted.push(value); + responseJson.actions.forEach(action => { + const newResolves = action.resolves.filter(resolve => { + if (key == resolve.id.toString() && value && value.severity) { + newVulnerabilities[value.severity] -= 1; + return false; + } + return true; + }); + if (newResolves.length) { + newActions.push({ + action: action.action, + module: action.module, + resolves: newResolves, + }); + } + }); + } else { + newAdvisories[key] = value; + } + } + Object.assign(filteredResponse, responseJson, { + muted: newMuted, + advisories: newAdvisories, + actions: newActions, + metadata: Object.assign(responseJson.metadata, {vulnerabilities: newVulnerabilities}), + }); + } + this.reporter.verbose(`Audit Response: ${JSON.stringify(filteredResponse, null, 2)}`); + + return filteredResponse; } _insertWorkspacePackagesIntoManifest(manifest: Object, resolver: PackageResolver) { @@ -309,6 +353,9 @@ export default class Audit { const reportAdvisory = (resolution: AuditResolution) => { const advisory = this.auditData.advisories[resolution.id.toString()]; + if (!advisory) { + return; + } if (this.severityLevels.indexOf(advisory.severity) >= startLoggingAt) { this.reporter.auditAdvisory(resolution, advisory); @@ -349,5 +396,6 @@ export default class Audit { } this.summary(); + this.reporter.auditMute(this.auditData.muted); } } diff --git a/src/reporters/base-reporter.js b/src/reporters/base-reporter.js index 44653602b0..2b3346ea52 100644 --- a/src/reporters/base-reporter.js +++ b/src/reporters/base-reporter.js @@ -240,6 +240,9 @@ export default class BaseReporter { // summary for security audit report auditSummary(auditMetadata: AuditMetadata) {} + // mutted advisories for security audit report + auditMute(mutedAdvisories: AuditAdvisory[]) {} + // render an activity spinner and return a function that will trigger an update activity(): ReporterSpinner { return { diff --git a/src/reporters/console/console-reporter.js b/src/reporters/console/console-reporter.js index 0e1aa38afd..34c331f73f 100644 --- a/src/reporters/console/console-reporter.js +++ b/src/reporters/console/console-reporter.js @@ -516,6 +516,15 @@ export default class ConsoleReporter extends BaseReporter { } } + auditMute(mutedAdvisories: AuditAdvisory[]) { + const message = this.lang( + 'auditMute', + this.rawText(chalk.yellow(mutedAdvisories.length.toString())), + this.rawText(mutedAdvisories.map(advisory => advisory.id).join(', ')), + ); + this._log(message); + } + auditAction(recommendation: AuditActionRecommendation) { const label = recommendation.action.resolves.length === 1 ? 'vulnerability' : 'vulnerabilities'; this._log( diff --git a/src/reporters/json-reporter.js b/src/reporters/json-reporter.js index ecb7285910..ba93993de6 100644 --- a/src/reporters/json-reporter.js +++ b/src/reporters/json-reporter.js @@ -173,4 +173,8 @@ export default class JSONReporter extends BaseReporter { auditSummary(auditMetadata: AuditMetadata) { this._dump('auditSummary', auditMetadata); } + + auditMute(mutedAdvisories: AuditAdvisory[]) { + this._dump(mutedAdvisories); + } } diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js index e376e969b3..e15a061d6a 100644 --- a/src/reporters/lang/en.js +++ b/src/reporters/lang/en.js @@ -424,6 +424,7 @@ const messages = { auditRunning: 'Auditing packages', auditSummary: '$0 vulnerabilities found - Packages audited: $1', auditSummarySeverity: 'Severity:', + auditMute: '$0 vulnerabilities muted - Advisory ids: ($1)', auditCritical: '$0 Critical', auditHigh: '$0 High', auditModerate: '$0 Moderate',