From ceeb3ad5f169dea8e18141ede81c6274402f5a41 Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:12:59 -0500 Subject: [PATCH 1/9] docs: Add registry command to README --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5737578c..8d554f7f 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ before_install: | -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 `./`) | +| | --registry | The registry to resolve packages by name and version (default to unspecified) | | | --config | Path to JSON config file | ### (_Optional_) Config file specification @@ -96,7 +97,8 @@ A config file can manage auditing preferences `audit-ci`. The config file's keys "summary": , // [Optional] defaults `true` "package-manager": , // [Optional] defaults `"auto"` "advisories": , // [Optional] defaults `[]` - "whitelist": // [Optional] defaults `[]` + "whitelist": , // [Optional] defaults `[]`, + "registry": // [Optional] defaults `undefined` } ``` @@ -140,7 +142,8 @@ audit-ci "low": true, "package-manager": "auto", "advisories": [100, 101], - "whitelist": ["example1", "example2"] + "whitelist": ["example1", "example2"], + "registry": "https://registry.npmjs.org" } ``` From bbed56dbf9cd8f2b99d371b635d545e7ced2684e Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:13:35 -0500 Subject: [PATCH 2/9] feat(registry): Add registry yargs argument --- lib/audit-ci.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/audit-ci.js b/lib/audit-ci.js index 92ac58a7..898ff34c 100644 --- a/lib/audit-ci.js +++ b/lib/audit-ci.js @@ -70,6 +70,11 @@ const { argv } = yargs describe: 'The directory containing the package.json to audit', type: 'string', }, + registry: { + default: undefined, + describe: 'The registry to resolve packages by name and version', + type: 'string', + }, }) .help('help'); From d342cd7526d9c46d87b28fdeac12e5c5abf59791 Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:15:23 -0500 Subject: [PATCH 3/9] tests: Fixed typo in Model.js test --- test/Model.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Model.js b/test/Model.js index ce5068db..cc914bfd 100644 --- a/test/Model.js +++ b/test/Model.js @@ -213,7 +213,7 @@ describe('Model', () => { }); }); - it('ignores whitelisted advisotry IDs', () => { + it('ignores whitelisted advisory IDs', () => { const model = new Model({ levels: { critical: true, low: true, high: true, moderate: true }, whitelist: [], From 8766e0014b39db961798a2f39c4aae0ff2664e27 Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:18:52 -0500 Subject: [PATCH 4/9] feat(registry): Add support for npm audit --registry --- lib/npm-auditer.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/npm-auditer.js b/lib/npm-auditer.js index ee5af63d..20ef9c22 100644 --- a/lib/npm-auditer.js +++ b/lib/npm-auditer.js @@ -6,7 +6,9 @@ const { runProgram, reportAudit } = require('./common'); const Model = require('./Model'); -function runNpmAudit(directory) { +function runNpmAudit(config) { + const { directory, registry } = config; + const stdoutBuffer = []; function outListener(line) { stdoutBuffer.push(line); @@ -18,6 +20,9 @@ function runNpmAudit(directory) { } const args = ['audit', '--json']; + if (registry) { + args.push('--registry', registry); + } const options = { cwd: directory }; return Promise.resolve() .then(() => runProgram('npm', args, options, outListener, errListener)) @@ -32,7 +37,7 @@ function runNpmAudit(directory) { try { return JSON.parse(stdout); } catch (e) { - console.log(stdout); + console.error(stdout); throw e; } }); @@ -53,17 +58,18 @@ function printReport(parsedOutput, report) { /** * Audit your NPM project! * - * @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config + * @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 * `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. * `levels`: the vulnerability levels to fail on, if `moderate` is set `true`, `high` and `critical` should be as well. * @returns {Promise} Returns the audit report summary on resolve, `Error` on rejection. */ function audit(config, reporter = reportAudit) { return Promise.resolve() - .then(() => runNpmAudit(config.directory)) + .then(() => runNpmAudit(config)) .then(parsedOutput => printReport(parsedOutput, config.report)) .then(parsedOutput => reporter(new Model(config).load(parsedOutput), config, parsedOutput) From f2f64ed472b329d6df89a720115ab1253a1b8610 Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:19:19 -0500 Subject: [PATCH 5/9] feat(registry): Warn the user that yarn audit --registry is unsupported --- lib/yarn-auditer.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/yarn-auditer.js b/lib/yarn-auditer.js index 471e7aba..bc81a8c8 100644 --- a/lib/yarn-auditer.js +++ b/lib/yarn-auditer.js @@ -9,6 +9,12 @@ const { reportAudit, runProgram } = require('./common'); const Model = require('./Model'); const MINIMUM_YARN_VERSION = '1.12.3'; +/** + * Change this to the appropriate version when + * yarn audit --registry is supported: + * @see https://github.com/yarnpkg/yarn/issues/7012 + */ +const MINIMUM_YARN_AUDIT_REGISTRY_VERSION = '99.99.99'; function getYarnVersion() { const version = childProcess @@ -22,20 +28,25 @@ function yarnSupportsAudit(yarnVersion) { return semver.gte(yarnVersion, MINIMUM_YARN_VERSION); } +function yarnAuditSupportsRegistry(yarnVersion) { + return semver.gte(yarnVersion, MINIMUM_YARN_AUDIT_REGISTRY_VERSION); +} + /** * Audit your Yarn project! * - * @param {{directory: string, report: { full?: boolean, summary?: boolean }, whitelist: string[], advisories: string[], levels: { low: boolean, moderate: boolean, high: boolean, critical: boolean }}} config + * @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 * `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. * `levels`: the vulnerability levels to fail on, if `moderate` is set `true`, `high` and `critical` should be as well. * @returns {Promise} Returns the audit report summary on resolve, `Error` on rejection. */ function audit(config, reporter = reportAudit) { return Promise.resolve().then(() => { - const { report, whitelist } = config; + const { registry, report, whitelist } = config; let missingLockFile = false; const model = new Model(config); @@ -90,6 +101,17 @@ function audit(config, reporter = reportAudit) { } const options = { cwd: config.directory }; const args = ['audit', '--json']; + if (registry) { + const auditRegistrySupported = yarnAuditSupportsRegistry(yarnVersion); + if (auditRegistrySupported) { + args.push('--registry', registry); + } else { + console.warn( + '\x1b[33m%s\x1b[0m', + 'Yarn audit does not support the registry flag yet.' + ); + } + } return runProgram('yarn', args, options, outListener, errListener).then( () => { if (missingLockFile) { From 68958501726d3f102b2bd6de54c4919bcc774897 Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:19:50 -0500 Subject: [PATCH 6/9] feat(registry): Add unit tests for audit --registry --- test/npm-auditer.js | 57 +++++++++++++++++++++++++++++++++++--------- test/yarn-auditer.js | 40 ++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/test/npm-auditer.js b/test/npm-auditer.js index ce06e54f..355550a1 100644 --- a/test/npm-auditer.js +++ b/test/npm-auditer.js @@ -1,3 +1,4 @@ +/* eslint-disable no-unused-expressions */ /* * Copyright IBM All Rights Reserved. * @@ -8,20 +9,36 @@ const path = require('path'); const { audit } = require('../lib/npm-auditer'); function config(additions) { - return Object.assign({}, { whitelist: [], advisories: [] }, additions); + const defaultConfig = { + levels: { + low: false, + moderate: false, + high: false, + critical: false, + }, + report: {}, + advisories: [], + whitelist: [], + directory: './', + registry: undefined, + }; + return Object.assign({}, defaultConfig, additions); } function testDir(s) { return path.resolve(__dirname, s); } -describe('npm-auditer', () => { +// To modify what slow times are, need to use +// function() {} instead of () => {} +// eslint-disable-next-line func-names +describe('npm-auditer', function() { + this.slow(6000); it('reports critical severity', () => { return audit( config({ directory: testDir('npm-critical'), levels: { critical: true }, - report: {}, }), summary => summary ).then(summary => { @@ -38,7 +55,6 @@ describe('npm-auditer', () => { config({ directory: testDir('npm-critical'), levels: { critical: false }, - report: {}, }), summary => summary ).then(summary => { @@ -55,7 +71,6 @@ describe('npm-auditer', () => { config({ directory: testDir('npm-high'), levels: { high: true }, - report: {}, }), summary => summary ).then(summary => { @@ -72,7 +87,6 @@ describe('npm-auditer', () => { config({ directory: testDir('npm-moderate'), levels: { moderate: true }, - report: {}, }), summary => summary ).then(summary => { @@ -89,7 +103,6 @@ describe('npm-auditer', () => { config({ directory: testDir('npm-moderate'), levels: { moderate: false }, - report: {}, }), summary => summary ).then(summary => { @@ -107,7 +120,6 @@ describe('npm-auditer', () => { directory: testDir('npm-moderate'), levels: { moderate: true }, advisories: [658], - report: {}, }), summary => summary ).then(summary => { @@ -125,7 +137,6 @@ describe('npm-auditer', () => { directory: testDir('npm-moderate'), levels: { moderate: true }, advisories: [659], - report: {}, }), summary => summary ).then(summary => { @@ -142,7 +153,6 @@ describe('npm-auditer', () => { config({ directory: testDir('npm-low'), levels: { low: true }, - report: {}, }), summary => summary ).then(summary => { @@ -159,7 +169,6 @@ describe('npm-auditer', () => { config({ directory: testDir('npm-none'), levels: { low: true }, - report: {}, }), summary => summary ).then(summary => { @@ -171,4 +180,30 @@ describe('npm-auditer', () => { }); }); }); + // eslint-disable-next-line func-names + it('fails with error code ETIMEDOUT on an invalid site', function(cb) { + this.slow(25000); + audit( + config({ + directory: testDir('npm-low'), + levels: { low: true }, + registry: 'https://registry.npmjs.co', + }) + ).catch(err => { + expect(err.message).to.include('code ETIMEDOUT'); + cb(); + }); + }); + it('fails errors with code ENOAUDIT on a valid site with no audit', cb => { + audit( + config({ + directory: testDir('npm-low'), + levels: { low: true }, + registry: 'https://example.com', + }) + ).catch(err => { + expect(err.message).to.include('code ENOAUDIT'); + cb(); + }); + }); }); diff --git a/test/yarn-auditer.js b/test/yarn-auditer.js index 82b5940e..076d8720 100644 --- a/test/yarn-auditer.js +++ b/test/yarn-auditer.js @@ -8,20 +8,36 @@ const path = require('path'); const { audit } = require('../lib/yarn-auditer'); function config(additions) { - return Object.assign({}, { whitelist: [], advisories: [] }, additions); + const defaultConfig = { + levels: { + low: false, + moderate: false, + high: false, + critical: false, + }, + report: {}, + advisories: [], + whitelist: [], + directory: './', + registry: undefined, + }; + return Object.assign({}, defaultConfig, additions); } function testDir(s) { return path.resolve(__dirname, s); } -describe('yarn-auditer', () => { +// To modify what slow times are, need to use +// function() {} instead of () => {} +// eslint-disable-next-line func-names +describe('yarn-auditer', function() { + this.slow(3000); it('reports critical severity', () => { return audit( config({ directory: testDir('yarn-critical'), levels: { critical: true }, - report: {}, }), summary => summary ).then(summary => { @@ -38,7 +54,6 @@ describe('yarn-auditer', () => { config({ directory: testDir('yarn-critical'), levels: { critical: false }, - report: {}, }), summary => summary ).then(summary => { @@ -55,7 +70,6 @@ describe('yarn-auditer', () => { config({ directory: testDir('yarn-high'), levels: { high: true }, - report: {}, }), summary => summary ).then(summary => { @@ -72,7 +86,6 @@ describe('yarn-auditer', () => { config({ directory: testDir('yarn-moderate'), levels: { moderate: true }, - report: {}, }), summary => summary ).then(summary => { @@ -89,7 +102,6 @@ describe('yarn-auditer', () => { config({ directory: testDir('yarn-moderate'), levels: { moderate: false }, - report: {}, }), summary => summary ).then(summary => { @@ -107,7 +119,6 @@ describe('yarn-auditer', () => { directory: testDir('yarn-moderate'), levels: { moderate: true }, advisories: [658], - report: {}, }), summary => summary ).then(summary => { @@ -125,7 +136,6 @@ describe('yarn-auditer', () => { directory: testDir('yarn-moderate'), levels: { moderate: true }, advisories: [659], - report: {}, }), summary => summary ).then(summary => { @@ -142,7 +152,6 @@ describe('yarn-auditer', () => { config({ directory: testDir('yarn-low'), levels: { low: true }, - report: {}, }), summary => summary ).then(summary => { @@ -159,7 +168,6 @@ describe('yarn-auditer', () => { config({ directory: testDir('yarn-none'), levels: { low: true }, - report: {}, }), summary => summary ).then(summary => { @@ -171,4 +179,14 @@ describe('yarn-auditer', () => { }); }); }); + it("doesn't use the registry flag since it's not supported in Yarn yet", () => { + return audit( + config({ + directory: testDir('yarn-low'), + levels: { low: true }, + registry: 'https://example.com', + }), + summary => summary + ); + }); }); From 84a1b1fe3170033867ff2ef334ab98698e301188 Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:39:04 -0500 Subject: [PATCH 7/9] test: Remove unnecessary eslint-disable no-unused-expressions --- test/npm-auditer.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/npm-auditer.js b/test/npm-auditer.js index 355550a1..bc07c968 100644 --- a/test/npm-auditer.js +++ b/test/npm-auditer.js @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-expressions */ /* * Copyright IBM All Rights Reserved. * From b6b3b1cd7c9a7a52938d3a3c6db53edb44df6e14 Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Tue, 5 Mar 2019 20:58:19 -0500 Subject: [PATCH 8/9] test: Modify timeouts to fix test --- package.json | 2 +- test/npm-auditer.js | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 04a1ecc4..93dd02d3 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ ], "scripts": { "lint": "eslint -c ./.eslintrc.json lib/*", - "test": "mocha --exit --timeout 60000 --recursive --reporter spec test/*.js" + "test": "mocha --exit --timeout 40000 --recursive --reporter spec test/*.js" }, "dependencies": { "byline": "^5.0.0", diff --git a/test/npm-auditer.js b/test/npm-auditer.js index bc07c968..a9081179 100644 --- a/test/npm-auditer.js +++ b/test/npm-auditer.js @@ -180,8 +180,9 @@ describe('npm-auditer', function() { }); }); // eslint-disable-next-line func-names - it('fails with error code ETIMEDOUT on an invalid site', function(cb) { - this.slow(25000); + it('fails with error code ETIMEDOUT on an invalid site', function(done) { + this.slow(50000); + this.timeout(120000); audit( config({ directory: testDir('npm-low'), @@ -190,10 +191,10 @@ describe('npm-auditer', function() { }) ).catch(err => { expect(err.message).to.include('code ETIMEDOUT'); - cb(); + done(); }); }); - it('fails errors with code ENOAUDIT on a valid site with no audit', cb => { + it('fails errors with code ENOAUDIT on a valid site with no audit', done => { audit( config({ directory: testDir('npm-low'), @@ -202,7 +203,7 @@ describe('npm-auditer', function() { }) ).catch(err => { expect(err.message).to.include('code ENOAUDIT'); - cb(); + done(); }); }); }); From e30fa53078e38aff2527d85a3e3c7d80a431f70d Mon Sep 17 00:00:00 2001 From: Quinn Turner Date: Wed, 6 Mar 2019 09:23:01 -0500 Subject: [PATCH 9/9] test(npm-auditer-registry): Change ETIMEDOUT test to ENOTFOUND The test was intended to test for a registry with a typo. The previous test's domain existed, which was unintended. To gracefully handle this, the audit should error with `code ENOTFOUND`. --- test/npm-auditer.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/npm-auditer.js b/test/npm-auditer.js index a9081179..f09f4491 100644 --- a/test/npm-auditer.js +++ b/test/npm-auditer.js @@ -179,18 +179,15 @@ describe('npm-auditer', function() { }); }); }); - // eslint-disable-next-line func-names - it('fails with error code ETIMEDOUT on an invalid site', function(done) { - this.slow(50000); - this.timeout(120000); + it('fails with error code ENOTFOUND on a non-existent site', done => { audit( config({ directory: testDir('npm-low'), levels: { low: true }, - registry: 'https://registry.npmjs.co', + registry: 'https://registry.nonexistentdomain0000000000.com', }) ).catch(err => { - expect(err.message).to.include('code ETIMEDOUT'); + expect(err.message).to.include('code ENOTFOUND'); done(); }); });