From 0301d2253afc5484e1c8f4807cec38f5b2e8701e Mon Sep 17 00:00:00 2001 From: Deven Phillips Date: Wed, 23 Oct 2019 07:34:53 -0400 Subject: [PATCH 1/4] Use spawn and stream stdout/stderr This prevents a situation where an unusually large npm audit response can get cut off and generate errors --- bin/index.js | 53 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/bin/index.js b/bin/index.js index d35f915..a0b47da 100755 --- a/bin/index.js +++ b/bin/index.js @@ -3,20 +3,20 @@ /** * Copyright [2018] [Joseph B. Phillips] * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and * limitations under the License. */ -const { exec } = require('child_process'); +const { exec, spawn } = require('child_process'); const { parse_audit_results } = require('../lib/parser'); const { parse_args, validThresholds, check_npm_version } = require('../lib/parse_args'); @@ -33,15 +33,38 @@ if (threshold === -1) { } // Build the npm audit command -command = 'npm audit --json' -if( registry !== null ) { - command += ' --registry=' + registry +var command = 'npm'; +var command_args = ['audit', '--json']; +if ( registry !== null ) { + command_args.push(' --registry=' + registry); } +var stdout = ''; +var stderr = ''; + +const audit_proc = spawn(command, command_args, { stdio: ['ignore', 'pipe', 'pipe'], detached: false }); + +audit_proc.stdout.on('data', (data) => { + var holder = stdout; + stdout = holder.concat(data); +}); + +audit_proc.stderr.on('data', (data) => { + var holder = stderr; + stderr = holder.concat(data); +}); + +audit_proc.on('close', (exit_code) => { + const { exitCode, cliOutput } = parse_audit_results(stderr, stdout, threshold, ignoreDev, json_output, whitelist); + console.log(cliOutput); + process.exit(exitCode); +}); + // // Execute and capture the output for processing -exec(command, {maxBuffer: 500 * 1024}, (err, stdout, stderr) => { - const { exitCode, cli_output } = parse_audit_results(err, stdout, threshold, ignoreDev, json_output, whitelist); - console.log(cli_output); - process.exit(exitCode); -}); \ No newline at end of file +// exec(command, {maxBuffer: 5000 * 1024}, (err, stdout, stderr) => { +// const { exitCode, cli_output } = parse_audit_results(err, stdout, threshold, ignoreDev, json_output, whitelist); +// console.log(cli_output); +// process.exit(exitCode); +// }); + From 221c5abf3a7fdc19032540bdec8eeb5176782cd7 Mon Sep 17 00:00:00 2001 From: Deven Phillips Date: Wed, 23 Oct 2019 07:36:00 -0400 Subject: [PATCH 2/4] Update variable names to camel case --- lib/parser.js | 32 ++++++++++++++-------------- lib/parser.test.js | 52 ++++++++++++++++++++++++++-------------------- 2 files changed, 45 insertions(+), 39 deletions(-) diff --git a/lib/parser.js b/lib/parser.js index ce4cc14..ba3164a 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -24,49 +24,50 @@ const { validThresholds } = require('./parse_args'); * @param {string} stdout The output from the command provided to `exec` * @param {number} threshold The severity threshold to filter on * @param {boolean} ignoreDev Boolean which determines if dev dependencies should be considered + * @returns {object} A tuple of the exitCode and cliOutput */ function parse_audit_results(err, stdout, threshold, ignoreDev, json_output = false, whitelist = []) { let exitCode = 0; - let cli_output = ""; + let cliOutput = ""; const data = JSON.parse(stdout); if (err === null) { if (json_output) { data['advisories'] = {}; data['actions'] = []; data['muted'] = []; - cli_output = JSON.stringify(data, null, 2) + "\n"; + cliOutput = JSON.stringify(data, null, 2) + "\n"; } else { - cli_output += 'No vulnerabilities found.\n'; + cliOutput += 'No vulnerabilities found.\n'; } } else { let advisories = Object.entries(data.advisories); - let flaggedDepenencies = filter_advisories(advisories, ignoreDev, threshold, whitelist); + let flaggedDependencies = filter_advisories(advisories, ignoreDev, threshold, whitelist); // If `-j` or `--json` passed, return the json data with the appropriate filters applied if (json_output) { var retVal = JSON.parse(JSON.stringify(data)); retVal.advisories = {}; - retVal.advisories = flaggedDepenencies; - cli_output = JSON.stringify(retVal, null, 2) + '\n'; - } else if (flaggedDepenencies.length > 0) { + retVal.advisories = flaggedDependencies; + cliOutput = JSON.stringify(retVal, null, 2) + '\n'; + } else if (flaggedDependencies.length > 0) { // If any vulnerabilities exceed the threshold and are not filtered, print the details and fail the build. - cli_output += ignoreDev ? ( + cliOutput += ignoreDev ? ( "The following production vulnerabilities " ) : ( "The following vulnerabilities " - ) + ); - cli_output += "are " + validThresholds[threshold] + " severity or higher:\n" + cliOutput += "are " + validThresholds[threshold] + " severity or higher:\n" exitCode = 1; const flagTable = new Table({ head: ["module", "severity", "overview"] - }) + }); - flaggedDepenencies.forEach((advisory) => { // Print out dependencies which exceed the threshold + flaggedDependencies.forEach((advisory) => { // Print out dependencies which exceed the threshold let libraryName = advisory[1].module_name; let libraryVersion = advisory[1].findings[0].version; let advisoryOverview = 'https://www.npmjs.com/advisories/' + advisory[0]; @@ -78,11 +79,10 @@ function parse_audit_results(err, stdout, threshold, ignoreDev, json_output = fa ]) }); - cli_output += flagTable.toString() + "\n" + cliOutput += flagTable.toString() + "\n" } } - - return { exitCode, cli_output }; + return { exitCode, cliOutput }; } /** @@ -91,7 +91,7 @@ function parse_audit_results(err, stdout, threshold, ignoreDev, json_output = fa * @param {boolean} ignoreDev Should dev dependencies be ignored? * @param {number} threshold The severity threshold above which a vulnerability will not be ignored * @param {string[]} whitelist A (possibly empty) list of modules/versions which should be ignored - * @returns An array (posssibly empty) of advisory objects + * @returns An array (possibly empty) of advisory objects */ function filter_advisories(advisories, ignoreDev, threshold, whitelist = []) { const filteredByThreshold = advisories.filter((advisory, idx) => { diff --git a/lib/parser.test.js b/lib/parser.test.js index 69a7323..7fe779c 100644 --- a/lib/parser.test.js +++ b/lib/parser.test.js @@ -27,8 +27,8 @@ const CRIT_THRESHOLD = 3; */ test('Validate when err is NULL', () => { const test_data = readFileSync('test_data/zero_vulnerabilities.json', 'utf8'); - let { exitCode, cli_output } = parse_audit_results(null, test_data, LOW_THRESHOLD, false); - expect(cli_output).toBe('No vulnerabilities found.\n'); + let { exitCode, cliOutput } = parse_audit_results(null, test_data, LOW_THRESHOLD, false); + expect(cliOutput).toBe('No vulnerabilities found.\n'); expect(exitCode).toBe(0); }); @@ -37,7 +37,7 @@ test('Validate when err is NULL', () => { */ test('Validate when err is NULL and JSON output is desired', () => { const test_data = readFileSync('test_data/zero_vulnerabilities.json', 'utf8'); - let { exitCode, cli_output } = parse_audit_results(null, test_data, LOW_THRESHOLD, false, true); + const { exitCode, cliOutput } = parse_audit_results(null, test_data, LOW_THRESHOLD, false, true); const expectedOutput = { "actions": [], "advisories": {}, @@ -57,7 +57,7 @@ test('Validate when err is NULL and JSON output is desired', () => { }, "runId": "3fdcb3d6-c9f3-4e6f-9e4f-c77d1e0dac86" } - const actualObject = JSON.parse(cli_output); + const actualObject = JSON.parse(cliOutput); expect(actualObject.actions).toEqual([]); expect(actualObject.advisories).toEqual({}); expect(actualObject.muted).toEqual([]); @@ -79,8 +79,8 @@ test('Validate when err is NULL and JSON output is desired', () => { */ test('Validate run with 0 vulnerabilities', () => { const test_data = readFileSync('test_data/zero_vulnerabilities.json', 'utf8'); - let { exitCode, cli_output } = parse_audit_results("", test_data, LOW_THRESHOLD, false); - expect(cli_output).toBe(''); + let { exitCode, cliOutput } = parse_audit_results("", test_data, LOW_THRESHOLD, false); + expect(cliOutput).toBe(''); expect(exitCode).toBe(0); }); @@ -90,11 +90,11 @@ test('Validate run with 0 vulnerabilities', () => { */ test('Validate run with 7 vulnerabilities', () => { const test_data = readFileSync('test_data/vue_js_app.json', 'utf8'); - let { exitCode, cli_output } = parse_audit_results("", test_data, LOW_THRESHOLD, false); - expect(cli_output).not.toContain('{'); - expect(cli_output).toContain("growl"); - expect(cli_output).toContain('https://www.npmjs.com/advisories/'); - expect(cli_output).toContain('The following vulnerabilities are low severity or higher:'); + let { exitCode, cliOutput } = parse_audit_results("", test_data, LOW_THRESHOLD, false); + expect(cliOutput).not.toContain('{'); + expect(cliOutput).toContain("growl"); + expect(cliOutput).toContain('https://www.npmjs.com/advisories/'); + expect(cliOutput).toContain('The following vulnerabilities are low severity or higher:'); expect(exitCode).toBe(1); }); @@ -104,11 +104,11 @@ test('Validate run with 7 vulnerabilities', () => { */ test('Validate run with 7 vulnerabilities, a high severity cutoff, and production-only', () => { const test_data = readFileSync('test_data/vue_js_app.json', 'utf8'); - let { exitCode, cli_output } = parse_audit_results("", test_data, HIGH_THRESHOLD, true); - expect(cli_output).not.toContain('{'); - expect(cli_output).toContain("https-proxy-agent@1.0.0"); - expect(cli_output).toContain('https://www.npmjs.com/advisories/'); - expect(cli_output).toContain('The following production vulnerabilities are high severity or higher:'); + let { exitCode, cliOutput } = parse_audit_results("", test_data, HIGH_THRESHOLD, true); + expect(cliOutput).not.toContain('{'); + expect(cliOutput).toContain("https-proxy-agent@1.0.0"); + expect(cliOutput).toContain('https://www.npmjs.com/advisories/'); + expect(cliOutput).toContain('The following production vulnerabilities are high severity or higher:'); expect(exitCode).toBe(1); }); @@ -117,13 +117,12 @@ test('Validate run with 7 vulnerabilities, a high severity cutoff, and productio */ test('Validate run with 7 vulnerabilities and JSON output', () => { const test_data = readFileSync('test_data/vue_js_app.json', 'utf8'); - let { exitCode, cli_output } = parse_audit_results("", test_data, LOW_THRESHOLD, true, true); - const actualObject = JSON.parse(cli_output); - console.log(cli_output+"\n\n"); - expect(cli_output).toContain('"https-proxy-agent"'); - expect(cli_output).toContain('"version"'); - expect(cli_output).toContain('"module_name"'); - expect(cli_output.substring((cli_output.length - 1), cli_output.length)).toBe('\n'); + let { exitCode, cliOutput } = parse_audit_results("", test_data, LOW_THRESHOLD, true, true); + const actualObject = JSON.parse(cliOutput); + expect(cliOutput).toContain('"https-proxy-agent"'); + expect(cliOutput).toContain('"version"'); + expect(cliOutput).toContain('"module_name"'); + expect(cliOutput.substring((cliOutput.length - 1), cliOutput.length)).toBe('\n'); expect(actualObject.metadata.dependencies).toBeDefined(); expect(actualObject.metadata.devDependencies).toBeDefined(); expect(actualObject.metadata.optionalDependencies).toBeDefined(); @@ -266,3 +265,10 @@ test('Validate advisories filtering on CRIT threshold and ignoring Dev dependenc const results = filter_advisories(Object.entries(data.advisories), true, CRIT_THRESHOLD); expect(results.length).toBe(0); }); + +test('Validate proper parsing with tar:4.4.1', () => { + const test_data = readFileSync('test_data/tar_error.json', 'utf8'); + const data = JSON.parse(test_data); + const results = filter_advisories(Object.entries(data.advisories), true, HIGH_THRESHOLD); + expect(results.length).toBe(1); +}); From 6b34798c1ab2e7f25a094eb0408060d1635b5ebc Mon Sep 17 00:00:00 2001 From: Deven Phillips Date: Wed, 23 Oct 2019 07:36:32 -0400 Subject: [PATCH 3/4] Bump release version --- package.json | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 514f286..550cc84 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "npm-audit-ci-wrapper", - "version": "2.3.0", + "version": "2.4.0", "description": "A wrapper for 'npm audit' which can be configurable for use in a CI/CD tool like Jenkins", "keywords": [ "npm", @@ -13,6 +13,7 @@ ], "main": "index.js", "scripts": { + "audit": "node bin/index.js -t low", "test": "jest --collect-coverage", "sonar": "sonar-scanner -Dsonar.host.url=https://sonarcloud.io/ -Dsonar.login=$(cat ~/.sonar_token) -Dsonar.projectVersion=$npm_package_version", "stryker": "node_modules/stryker-cli/bin/stryker-cli run" @@ -24,21 +25,21 @@ }, "license": "Apache-2.0", "dependencies": { - "argv": "0.0.2", + "argv": "^0.0.2", "cli-table": "^0.3.1" }, "bin": { "npm-audit-ci-wrapper": "./bin/index.js" }, "devDependencies": { - "@stryker-mutator/core": "^1.0.2", - "@stryker-mutator/html-reporter": "^1.0.2", - "@stryker-mutator/javascript-mutator": "^1.0.2", - "@stryker-mutator/jest-runner": "^1.0.2", + "@stryker-mutator/core": "^2.1.0", + "@stryker-mutator/html-reporter": "^2.1.0", + "@stryker-mutator/javascript-mutator": "^2.1.0", + "@stryker-mutator/jest-runner": "^2.1.0", "capture-stdout": "^1.0.0", - "jest": "^24.1.0", - "jest-cli": "^24.8.0", - "jest-html-reporter": "^2.5.0", + "jest": "^24.9.0", + "jest-cli": "^24.9.0", + "jest-html-reporter": "^2.6.2", "stryker-cli": "^1.0.0", "stryker-jest-runner": "^1.4.1" } From 9b6907f2343baf7afe6a960220b66f759fb00934 Mon Sep 17 00:00:00 2001 From: Deven Phillips Date: Wed, 23 Oct 2019 07:40:04 -0400 Subject: [PATCH 4/4] Remove obsolete test --- lib/parser.test.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/lib/parser.test.js b/lib/parser.test.js index 7fe779c..6f80f00 100644 --- a/lib/parser.test.js +++ b/lib/parser.test.js @@ -265,10 +265,3 @@ test('Validate advisories filtering on CRIT threshold and ignoring Dev dependenc const results = filter_advisories(Object.entries(data.advisories), true, CRIT_THRESHOLD); expect(results.length).toBe(0); }); - -test('Validate proper parsing with tar:4.4.1', () => { - const test_data = readFileSync('test_data/tar_error.json', 'utf8'); - const data = JSON.parse(test_data); - const results = filter_advisories(Object.entries(data.advisories), true, HIGH_THRESHOLD); - expect(results.length).toBe(1); -});