From 011dcbc56a56da5aabc0ed9787b49cd76f12ecd9 Mon Sep 17 00:00:00 2001 From: Deven Phillips Date: Mon, 18 Mar 2019 09:51:03 -0400 Subject: [PATCH 1/3] Added Snyk Badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 76d9c96..2466b0c 100755 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Code Coverage](https://sonarcloud.io/api/project_badges/measure?project=com.zanclus%3Anpm-audit-ci-wrapper&metric=coverage)](https://sonarcloud.io/dashboard?id=com.zanclus%3Anpm-audit-ci-wrapper) [![Bugs](https://sonarcloud.io/api/project_badges/measure?project=com.zanclus%3Anpm-audit-ci-wrapper&metric=bugs)](https://sonarcloud.io/dashboard?id=com.zanclus%3Anpm-audit-ci-wrapper) [![Quality](https://sonarcloud.io/api/project_badges/measure?project=com.zanclus%3Anpm-audit-ci-wrapper&metric=sqale_index)](https://sonarcloud.io/dashboard?id=com.zanclus%3Anpm-audit-ci-wrapper) +[![Known Vulnerabilities](https://snyk.io/test/github/InfoSec812/npm-audit-ci-wrapper/badge.svg?targetFile=package.json)](https://snyk.io/test/github/InfoSec812/npm-audit-ci-wrapper?targetFile=package.json) This utility is a wrapper around `npm audit --json` which allows for finer grained control over what will cause a CI build to fail. Options include setting the severity threshold and ignoring dev dependencies. From 6854b7862bc98fb46ae747eca7bd555c17976237 Mon Sep 17 00:00:00 2001 From: Brian Kurek Date: Mon, 29 Apr 2019 10:18:19 -0400 Subject: [PATCH 2/3] Fail if invalid threshold is provided (#35) * Fail if invalid threshold is provided * Sonar only on non-PRs * Bump version to 2.2.0 --- .travis.yml | 2 +- bin/index.js | 5 +++++ lib/parse_args.js | 5 +---- lib/parse_args.test.js | 2 +- package.json | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 0a0acee..2f442b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,5 +25,5 @@ addons: token: "${SONAR_TOKEN}" script: - npm run test - - sonar-scanner + - if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then sonar-scanner; fi # sonar only on non-PRs - npm run stryker \ No newline at end of file diff --git a/bin/index.js b/bin/index.js index bfb11a1..6952132 100755 --- a/bin/index.js +++ b/bin/index.js @@ -27,6 +27,11 @@ if (!check_npm_version()) { process.exit(1); } +if (threshold === -1) { + console.error(`Invalid threshold provided. Threshold must be one of the following: ${validThresholds.join(', ')}`); + process.exit(1); +} + // Build the npm audit command command = 'npm audit --json' if( registry !== null ) { diff --git a/lib/parse_args.js b/lib/parse_args.js index 80466a9..d877281 100644 --- a/lib/parse_args.js +++ b/lib/parse_args.js @@ -89,10 +89,7 @@ function parse_args(cli_args = process.argv) { // Define which threshold this script should cause a non-zero exit status let threshold = validThresholds.indexOf('critical'); - if ( - args.options.hasOwnProperty('threshold') && - validThresholds.indexOf(args.options.threshold.toLocaleLowerCase()) > -1 - ) { + if (args.options.hasOwnProperty('threshold')) { threshold = validThresholds.indexOf(args.options.threshold.toLocaleLowerCase()); // Set the threshold } diff --git a/lib/parse_args.test.js b/lib/parse_args.test.js index 47c799e..6aab514 100644 --- a/lib/parse_args.test.js +++ b/lib/parse_args.test.js @@ -40,7 +40,7 @@ test('Validate help output', async () => { test('Test undefined threshold', () => { const argv = ['-t', 'undef']; const { threshold } = parse_args(argv); - expect(threshold).toBe(validThresholds.indexOf('critical')); + expect(threshold).toBe(-1); }); test('Test with MODERATE threshold', () => { diff --git a/package.json b/package.json index e184abf..c7a64e2 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "npm-audit-ci-wrapper", - "version": "2.1.8", + "version": "2.2.0", "description": "A wrapper for 'npm audit' which can be configurable for use in a CI/CD tool like Jenkins", "keywords": [ "npm", From 0cf541bc0c89c026530be1a1eb86157cf4082285 Mon Sep 17 00:00:00 2001 From: Deven Phillips Date: Wed, 22 May 2019 18:25:55 -0400 Subject: [PATCH 3/3] Issue 32 fix json output compatibility (#38) * Stash changes * Fixed JSON output with 0 vulnerabilities * Bumped version * Fixed output for JSON --- bin/index.js | 2 +- jest.conf.js | 14 +++++++++++ lib/parse_args.js | 6 ++--- lib/parser.js | 15 ++++++++---- lib/parser.test.js | 49 +++++++++++++++++++++++++++++++++++++++ package.json | 12 +++------- test_data/vue_js_app.json | 17 +++++++++++++- 7 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 jest.conf.js diff --git a/bin/index.js b/bin/index.js index 6952132..d35f915 100755 --- a/bin/index.js +++ b/bin/index.js @@ -20,7 +20,7 @@ const { exec } = require('child_process'); const { parse_audit_results } = require('../lib/parser'); const { parse_args, validThresholds, check_npm_version } = require('../lib/parse_args'); -const { threshold, ignoreDev, json_output, registry, whitelist } = parse_args(); +const { threshold, ignoreDev, json_output, registry, whitelist } = parse_args(process.argv); if (!check_npm_version()) { console.error('NPM Version does not support npm audit. Install a version >= 6.0.0'); diff --git a/jest.conf.js b/jest.conf.js new file mode 100644 index 0000000..258d54c --- /dev/null +++ b/jest.conf.js @@ -0,0 +1,14 @@ +module.exports = { + "testPathIgnorePatterns": [ + "/.stryker-tmp/" + ], + "testMatch": [ + "**/?(*.)+(spec|test).[jt]s?(x)" + ], + "reporters": [ + "default", + ["./node_modules/jest-html-reporter", { + "pageTitle": "Test Report" + }] + ] +}; \ No newline at end of file diff --git a/lib/parse_args.js b/lib/parse_args.js index d877281..0cf15ba 100644 --- a/lib/parse_args.js +++ b/lib/parse_args.js @@ -14,7 +14,7 @@ * limitations under the License. */ -const argv = require( 'argv' ); +const argv = require('argv'); const util = require('util'); const exec = util.promisify(require('child_process').exec); @@ -80,7 +80,7 @@ async function check_npm_version() { * Parse CLI arguments and extract configuration for application * @param {string[]} cli_args */ -function parse_args(cli_args = process.argv) { +function parse_args(cli_args) { let args = argv.option( options ).run(cli_args); // Check to see if this script should ignore dev dependencies @@ -111,4 +111,4 @@ module.exports = { 'parse_args': parse_args, 'validThresholds': validThresholds, 'check_npm_version': check_npm_version -} +}; diff --git a/lib/parser.js b/lib/parser.js index 827437b..3f4a679 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -27,20 +27,27 @@ const { validThresholds } = require('./parse_args'); function parse_audit_results(err, stdout, threshold, ignoreDev, json_output = false, whitelist = []) { let exitCode = 0; let cli_output = ""; + const data = JSON.parse(stdout); if (err === null) { - cli_output += 'No vulnerabilities found.\n'; + if (json_output) { + data['advisories'] = {}; + data['actions'] = []; + data['muted'] = []; + cli_output = JSON.stringify(data, null, 2) + "\n"; + } else { + cli_output += 'No vulnerabilities found.\n'; + } } else { - let data = JSON.parse(stdout); let advisories = Object.entries(data.advisories); let flaggedDepenencies = 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 = data; + var retVal = JSON.parse(JSON.stringify(data)); retVal.advisories = {}; retVal.advisories = flaggedDepenencies; - cli_output = JSON.stringify(retVal) + '\n'; + cli_output = JSON.stringify(retVal, null, 2) + '\n'; } else { // If any vulnerabilities exceed the threshold and are not filtered, print the details and fail the build. if (flaggedDepenencies.length > 0) { cli_output += 'There are vulnerable dependencies which exceed the selected threshold and scope:\n'; diff --git a/lib/parser.test.js b/lib/parser.test.js index 443469d..8e9e1d9 100644 --- a/lib/parser.test.js +++ b/lib/parser.test.js @@ -32,6 +32,48 @@ test('Validate when err is NULL', () => { expect(exitCode).toBe(0); }); +/* + * When err is NULL, and JSON is requested, expect default JSON output as well + */ +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 expectedOutput = { + "actions": [], + "advisories": {}, + "muted": [], + "metadata": { + "vulnerabilities": { + "info": 0, + "low": 0, + "moderate": 0, + "high": 0, + "critical": 0 + }, + "dependencies": 879278, + "devDependencies": 387, + "optionalDependencies": 9709, + "totalDependencies": 889374 + }, + "runId": "3fdcb3d6-c9f3-4e6f-9e4f-c77d1e0dac86" + } + const actualObject = JSON.parse(cli_output); + expect(actualObject.actions).toEqual([]); + expect(actualObject.advisories).toEqual({}); + expect(actualObject.muted).toEqual([]); + expect(actualObject.metadata.vulnerabilities.info).toEqual(0); + expect(actualObject.metadata.vulnerabilities.low).toEqual(0); + expect(actualObject.metadata.vulnerabilities.moderate).toEqual(0); + expect(actualObject.metadata.vulnerabilities.high).toEqual(0); + expect(actualObject.metadata.vulnerabilities.critical).toEqual(0); + expect(actualObject.metadata.dependencies).toBeDefined(); + expect(actualObject.metadata.devDependencies).toBeDefined(); + expect(actualObject.metadata.optionalDependencies).toBeDefined(); + expect(actualObject.metadata.totalDependencies).toBeDefined(); + expect(actualObject.runId).toBeDefined(); + expect(exitCode).toBe(0); +}); + /* * When there are 0 vulnerable dependencies, expect a 0 exit code */ @@ -61,10 +103,17 @@ test('Validate run with 7 vulnerabilities', () => { 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'); + expect(actualObject.metadata.dependencies).toBeDefined(); + expect(actualObject.metadata.devDependencies).toBeDefined(); + expect(actualObject.metadata.optionalDependencies).toBeDefined(); + expect(actualObject.metadata.totalDependencies).toBeDefined(); + expect(actualObject.runId).toBeDefined(); expect(exitCode).toBe(0); }); diff --git a/package.json b/package.json index c7a64e2..54f395a 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "npm-audit-ci-wrapper", - "version": "2.2.0", + "version": "2.2.1", "description": "A wrapper for 'npm audit' which can be configurable for use in a CI/CD tool like Jenkins", "keywords": [ "npm", @@ -17,14 +17,6 @@ "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" }, - "jest": { - "testPathIgnorePatterns": [ - "/.stryker-tmp/" - ], - "testMatch": [ - "**/?(*.)+(spec|test).[jt]s?(x)" - ] - }, "author": "Deven Phillips ", "repository": { "type": "git", @@ -44,6 +36,8 @@ "@stryker-mutator/jest-runner": "^1.0.2", "capture-stdout": "^1.0.0", "jest": "^24.1.0", + "jest-cli": "^24.8.0", + "jest-html-reporter": "^2.5.0", "stryker-cli": "^1.0.0", "stryker-jest-runner": "^1.4.1" } diff --git a/test_data/vue_js_app.json b/test_data/vue_js_app.json index a73d7bd..5ea811a 100644 --- a/test_data/vue_js_app.json +++ b/test_data/vue_js_app.json @@ -312,5 +312,20 @@ }, "url": "https://npmjs.com/advisories/755" } - } + }, + "muted": [], + "metadata": { + "vulnerabilities": { + "info": 0, + "low": 0, + "moderate": 0, + "high": 0, + "critical": 0 + }, + "dependencies": 1, + "devDependencies": 74642, + "optionalDependencies": 396, + "totalDependencies": 74643 + }, + "runId": "13b24789-ae58-468a-83ff-7eb3faafa8ae" } \ No newline at end of file