Skip to content

Commit

Permalink
Merge pull request #74 from quinnturner/feat/show-vuln-advisory-in-su…
Browse files Browse the repository at this point in the history
…mmary

 feat: Revise summary to show vulns >= level
  • Loading branch information
quinnturner authored May 27, 2019
2 parents b1fb6e2 + 2308ec6 commit 2525276
Show file tree
Hide file tree
Showing 6 changed files with 150 additions and 62 deletions.
48 changes: 24 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -95,8 +96,7 @@ A config file can manage auditing preferences `audit-ci`. The config file's keys
"moderate": <boolean>, // [Optional] defaults `false`
"high": <boolean>, // [Optional] defaults `false`
"critical": <boolean>, // [Optional] defaults `false`
"report": <boolean>, // [Optional] defaults `false`
"summary": <boolean>, // [Optional] defaults `true`
"report-type": <string>, // [Optional] defaults `important`
"package-manager": <string>, // [Optional] defaults `"auto"`
"advisories": <number[]>, // [Optional] defaults `[]`
"whitelist": <string[]>, // [Optional] defaults `[]`
Expand Down Expand Up @@ -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
Expand Down
40 changes: 32 additions & 8 deletions lib/audit-ci.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ const { argv } = yargs
},
s: {
alias: 'summary',
default: true,
default: false,
describe: 'Show a summary audit report',
type: 'boolean',
},
Expand Down Expand Up @@ -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:
Expand All @@ -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)
Expand Down
46 changes: 37 additions & 9 deletions lib/npm-auditer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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.
Expand All @@ -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)
);
Expand Down
56 changes: 43 additions & 13 deletions lib/yarn-auditer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand All @@ -65,24 +71,48 @@ 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) {
try {
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.') {
Expand Down
11 changes: 7 additions & 4 deletions test/npm-auditer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function config(additions) {
high: false,
critical: false,
},
report: {},
'report-type': 'important',
advisories: [],
whitelist: [],
'show-not-found': false,
Expand All @@ -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 => {
Expand Down Expand Up @@ -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 => {
Expand All @@ -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 => {
Expand Down
Loading

0 comments on commit 2525276

Please sign in to comment.