diff --git a/README.md b/README.md index 3b2ca478..1abdfd23 100644 --- a/README.md +++ b/README.md @@ -58,31 +58,32 @@ steps: ### Installing as a global dependency in your CI -An alternative to installing as a devDependency is to install globally within the CI environment at run-time. +An alternative to installing as a devDependency is to use npx to install within the CI environment at run-time. ```yml before_install: - - if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then npm i -g audit-ci && audit-ci -m; fi + - if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then npx audit-ci -m; fi ``` ## Options -| Args | Alias | Description | -| ---- | ----------------- | ------------------------------------------------------------------------------------------ | -| -l | --low | Prevents integration with low or higher vulnerabilities (default `false`) | -| -m | --moderate | Prevents integration with moderate or higher vulnerabilities (default `false`) | -| -h | --high | Prevents integration with high or critical vulnerabilities (default `false`) | -| -c | --critical | Prevents integration only with critical vulnerabilities (default `false`) | -| -p | --package-manager | Choose a package manager [_choices_: `auto`, `npm`, `yarn`] (default `auto`) | -| -r | --report | Shows the full audit report (default `false`) | -| -s | --summary | Shows the summary audit report (default `true`) | -| -a | --advisories | Vulnerable advisory ids to whitelist from preventing integration (default `none`) | -| -w | --whitelist | Vulnerable modules to whitelist from preventing integration (default `none`) | -| -d | --directory | The directory containing the package.json to audit (default `./`) | -| | --show-not-found | Show whitelisted advisories that are not found (default `true`) | -| | --registry | The registry to resolve packages by name and version (default to unspecified) | -| | --retry-count | The number of attempts audit-ci calls an unavailable registry before failing (default `5`) | -| | --config | Path to JSON config file | +| Args | Alias | Description | +| ---- | ----------------- | ----------------------------------------------------------------------------------------------------- | +| -l | --low | Prevents integration with low or higher vulnerabilities (default `false`) | +| -m | --moderate | Prevents integration with moderate or higher vulnerabilities (default `false`) | +| -h | --high | Prevents integration with high or critical vulnerabilities (default `false`) | +| -c | --critical | Prevents integration only with critical vulnerabilities (default `false`) | +| -p | --report-type | Format for the audit report results [_choices_: `important`, `summary`, `full`] (default `important`) | +| -p | --package-manager | Choose a package manager [_choices_: `auto`, `npm`, `yarn`] (default `auto`) | +| -a | --advisories | Vulnerable advisory ids to whitelist from preventing integration (default `none`) | +| -w | --whitelist | Vulnerable modules to whitelist from preventing integration (default `none`) | +| -d | --directory | The directory containing the package.json to audit (default `./`) | +| | --show-not-found | Show whitelisted advisories that are not found (default `true`) | +| | --registry | The registry to resolve packages by name and version (default to unspecified) | +| | --retry-count | The number of attempts audit-ci calls an unavailable registry before failing (default `5`) | +| | --config | Path to JSON config file | +| -r | --report | [_DEPRECATED_] (Use `--report-type full`) Shows the full audit report (default `false`) | +| -s | --summary | [_DEPRECATED_] (Use `--report-type summary`) Shows the summary audit report (default `false`) | ### (_Optional_) Config file specification @@ -95,8 +96,7 @@ A config file can manage auditing preferences `audit-ci`. The config file's keys "moderate": , // [Optional] defaults `false` "high": , // [Optional] defaults `false` "critical": , // [Optional] defaults `false` - "report": , // [Optional] defaults `false` - "summary": , // [Optional] defaults `true` + "report-type": , // [Optional] defaults `important` "package-manager": , // [Optional] defaults `"auto"` "advisories": , // [Optional] defaults `[]` "whitelist": , // [Optional] defaults `[]` @@ -125,16 +125,16 @@ audit-ci -m audit-ci -l -a 690 -w lodash base64url ``` -### Prevents build with critical vulnerabilities using aliases without showing the report +### Prevents build with critical vulnerabilities showing the full report ```sh -audit-ci --critical --report false +audit-ci --critical --report-type full ``` -### Continues build regardless of vulnerabilities, but show the report +### Continues build regardless of vulnerabilities, but show the summary report ```sh -audit-ci +audit-ci --report-type summary ``` ### Example config file and different directory usage diff --git a/lib/audit-ci.js b/lib/audit-ci.js index 11edf95f..ccda5ecb 100644 --- a/lib/audit-ci.js +++ b/lib/audit-ci.js @@ -48,7 +48,7 @@ const { argv } = yargs }, s: { alias: 'summary', - default: true, + default: false, describe: 'Show a summary audit report', type: 'boolean', }, @@ -80,6 +80,12 @@ const { argv } = yargs describe: 'The registry to resolve packages by name and version', type: 'string', }, + 'report-type': { + default: 'important', + describe: 'Format for the audit report results', + type: 'string', + choices: ['important', 'summary', 'full'], + }, 'retry-count': { default: 5, describe: @@ -105,18 +111,36 @@ function mapVulnerabilityLevelInput(config) { return { low: false, moderate: false, high: false, critical: false }; } -function mapReportLevelInput(config) { - if (config.r) { - return { full: true }; +function mapReportTypeInput(config) { + const { 'report-type': reportType, report, summary } = config; + if (report) { + console.warn( + '\x1b[33m%s\x1b[0m', + "[DEPRECATED] The 'r' and 'report' options have been deprecated and may be removed in the future. Use `--report-type important` [default] to show only relevant information or `--report-type full` to show the full audit report." + ); + return 'full'; } - if (config.s) { - return { summary: true }; + if (summary) { + console.warn( + '\x1b[33m%s\x1b[0m', + "[DEPRECATED] The 's' and 'summary' options have been deprecated and may be removed in the future. Use `--report-type important` [default] to show only relevant information or `--report-type summary` to show only the audit metadata." + ); + return 'summary'; + } + switch (reportType) { + case 'full': + case 'important': + case 'summary': + return reportType; + default: + throw new Error( + `Invalid report type: ${reportType}. Should be \`['important', 'full', 'summary']\`.` + ); } - return {}; } argv.levels = mapVulnerabilityLevelInput(argv); -argv.report = mapReportLevelInput(argv); +argv['report-type'] = mapReportTypeInput(argv); /** * @param {'auto' | 'npm' | 'yarn'} pmArg the package manager (including the `auto` option) diff --git a/lib/npm-auditer.js b/lib/npm-auditer.js index bebf5431..749a60c0 100644 --- a/lib/npm-auditer.js +++ b/lib/npm-auditer.js @@ -43,14 +43,40 @@ function runNpmAudit(config) { }); } -function printReport(parsedOutput, report) { - if (report.full) { - console.log('\x1b[36m%s\x1b[0m', 'NPM audit report JSON:'); - console.log(JSON.stringify(parsedOutput, null, 2)); +function printReport(parsedOutput, levels, reportType) { + function printReportObj(text, obj) { + console.log('\x1b[36m%s\x1b[0m', text); + console.log(JSON.stringify(obj, null, 2)); } - if (report.summary) { - console.log('\x1b[36m%s\x1b[0m', 'NPM audit report summary:'); - console.log(JSON.stringify(parsedOutput.metadata, null, 2)); + switch (reportType) { + case 'full': + printReportObj('Yarn audit report JSON:', parsedOutput); + break; + case 'important': { + const relevantAdvisories = Object.keys(parsedOutput.advisories).reduce( + (acc, advisory) => + levels[parsedOutput.advisories[advisory].severity] + ? Object.assign( + { [advisory]: parsedOutput.advisories[advisory] }, + acc + ) + : acc, + {} + ); + const keyFindings = { + advisories: relevantAdvisories, + metadata: parsedOutput.metadata, + }; + printReportObj('NPM audit report results:', keyFindings); + break; + } + case 'summary': + printReportObj('NPM audit report summary:', parsedOutput.metadata); + break; + default: + throw new Error( + `Invalid report type: ${reportType}. Should be \`['important', 'full', 'summary']\`.` + ); } return parsedOutput; } @@ -60,7 +86,7 @@ function printReport(parsedOutput, report) { * * @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], registry: string, levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config * `directory`: the directory containing the package.json to audit. - * `report`: report level: `full` for full report, `summary` for summary + * `report-type`: [`important`, `summary`, `full`] how the audit report is displayed. * `whitelist`: a list of packages that should not break the build if their vulnerability is found. * `advisories`: a list of advisory ids that should not break the build if found. * `registry`: the registry to resolve packages by name and version. @@ -71,7 +97,9 @@ function printReport(parsedOutput, report) { function audit(config, reporter = reportAudit) { return Promise.resolve() .then(() => runNpmAudit(config)) - .then(parsedOutput => printReport(parsedOutput, config.report)) + .then(parsedOutput => + printReport(parsedOutput, config.levels, config['report-type']) + ) .then(parsedOutput => reporter(new Model(config).load(parsedOutput), config, parsedOutput) ); diff --git a/lib/yarn-auditer.js b/lib/yarn-auditer.js index ce9a7d29..6e8e500e 100644 --- a/lib/yarn-auditer.js +++ b/lib/yarn-auditer.js @@ -37,7 +37,7 @@ function yarnAuditSupportsRegistry(yarnVersion) { * * @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], registry: string, levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config * `directory`: the directory containing the package.json to audit. - * `report`: report level: `full` for full report, `summary` for summary + * `report-type`: [`important`, `summary`, `full`] how the audit report is displayed. * `whitelist`: a list of packages that should not break the build if their vulnerability is found. * `advisories`: a list of advisory ids that should not break the build if found. * `registry`: the registry to resolve packages by name and version. @@ -48,7 +48,13 @@ function yarnAuditSupportsRegistry(yarnVersion) { */ function audit(config, reporter = reportAudit) { return Promise.resolve().then(() => { - const { registry, report, whitelist, _yarn } = config; + const { + levels, + registry, + 'report-type': reportType, + whitelist, + _yarn, + } = config; const yarnExec = _yarn || 'yarn'; let missingLockFile = false; const model = new Model(config); @@ -65,12 +71,20 @@ function audit(config, reporter = reportAudit) { console.log(`Modules to whitelist: ${whitelist.join(', ')}.`); } - if (report.full) { - console.log('\x1b[36m%s\x1b[0m', 'Yarn audit report JSON:'); - } - - if (report.summary) { - console.log('\x1b[36m%s\x1b[0m', 'Yarn audit report summary:'); + switch (reportType) { + case 'full': + console.log('\x1b[36m%s\x1b[0m', 'Yarn audit report JSON:'); + break; + case 'important': + console.log('\x1b[36m%s\x1b[0m', 'Yarn audit report results:'); + break; + case 'summary': + console.log('\x1b[36m%s\x1b[0m', 'Yarn audit report summary:'); + break; + default: + throw new Error( + `Invalid report type: ${reportType}. Should be \`['important', 'full', 'summary']\`.` + ); } function outListener(line) { @@ -78,11 +92,27 @@ function audit(config, reporter = reportAudit) { const auditLine = JSON.parse(line); const { type, data } = auditLine; - if (report.full) { - console.log(JSON.stringify(auditLine, null, 2)); - } - if (report.summary && type === 'auditSummary') { - console.log(JSON.stringify(data, null, 2)); + switch (reportType) { + case 'full': + console.log(JSON.stringify(auditLine, null, 2)); + break; + case 'important': + if ( + (type === 'auditAdvisory' && levels[data.advisory.severity]) || + type === 'auditSummary' + ) { + console.log(JSON.stringify(data, null, 2)); + } + break; + case 'summary': + if (type === 'auditSummary') { + console.log(JSON.stringify(data, null, 2)); + } + break; + default: + throw new Error( + `Invalid report type: ${reportType}. Should be \`['important', 'full', 'summary']\`.` + ); } if (type === 'info' && data === 'No lockfile found.') { diff --git a/test/npm-auditer.js b/test/npm-auditer.js index 46bf36a9..202f587a 100644 --- a/test/npm-auditer.js +++ b/test/npm-auditer.js @@ -16,7 +16,7 @@ function config(additions) { high: false, critical: false, }, - report: {}, + 'report-type': 'important', advisories: [], whitelist: [], 'show-not-found': false, @@ -36,11 +36,12 @@ function testDir(s) { // eslint-disable-next-line func-names describe('npm-auditer', function() { this.slow(6000); - it('reports critical severity', () => { + it('prints full report with critical severity', () => { return audit( config({ directory: testDir('npm-critical'), levels: { critical: true }, + 'report-type': 'full', }), summary => summary ).then(summary => { @@ -70,11 +71,12 @@ describe('npm-auditer', function() { }); }); }); - it('reports high severity', () => { + it('reports summary with high severity', () => { return audit( config({ directory: testDir('npm-high'), levels: { high: true }, + 'report-type': 'summary', }), summary => summary ).then(summary => { @@ -87,11 +89,12 @@ describe('npm-auditer', function() { }); }); }); - it('reports moderate severity', () => { + it('reports important info with moderate severity', () => { return audit( config({ directory: testDir('npm-moderate'), levels: { moderate: true }, + 'report-type': 'important', }), summary => summary ).then(summary => { diff --git a/test/yarn-auditer.js b/test/yarn-auditer.js index fef97845..7eaa17f2 100644 --- a/test/yarn-auditer.js +++ b/test/yarn-auditer.js @@ -15,7 +15,7 @@ function config(additions) { high: false, critical: false, }, - report: {}, + 'report-type': 'important', advisories: [], whitelist: [], 'show-not-found': false, @@ -54,11 +54,12 @@ describe('yarn-auditer', function() { expect(err.toString()).to.contain(errorMessage); }); }); - it('reports critical severity', () => { + it('prints full report with critical severity', () => { return audit( config({ directory: testDir('yarn-critical'), levels: { critical: true }, + 'report-type': 'full', }), summary => summary ).then(summary => { @@ -88,11 +89,12 @@ describe('yarn-auditer', function() { }); }); }); - it('reports high severity', () => { + it('reports summary with high severity', () => { return audit( config({ directory: testDir('yarn-high'), levels: { high: true }, + 'report-type': 'summary', }), summary => summary ).then(summary => { @@ -105,11 +107,12 @@ describe('yarn-auditer', function() { }); }); }); - it('reports moderate severity', () => { + it('reports important info with moderate severity', () => { return audit( config({ directory: testDir('yarn-moderate'), levels: { moderate: true }, + 'report-type': 'important', }), summary => summary ).then(summary => {