Skip to content
This repository has been archived by the owner on Feb 2, 2022. It is now read-only.

Issue 41: Fix issues with large audit data response #42

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 38 additions & 15 deletions bin/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand All @@ -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);
});
// 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);
// });

32 changes: 16 additions & 16 deletions lib/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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 };
}

/**
Expand All @@ -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) => {
Expand Down
45 changes: 22 additions & 23 deletions lib/parser.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});

Expand All @@ -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": {},
Expand All @@ -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([]);
Expand All @@ -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);
});

Expand All @@ -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);
});

Expand All @@ -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);
});

Expand All @@ -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();
Expand Down
19 changes: 10 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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"
Expand All @@ -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"
}
Expand Down