better-npm-audit
Advanced tools
Comparing version 1.1.1 to 1.2.0
189
index.js
@@ -7,14 +7,31 @@ #!/usr/bin/env node | ||
const program = require('commander'); | ||
const { exec } = require('child_process'); | ||
const program = require("commander"); | ||
const { exec } = require("child_process"); | ||
const AUDIT_COMMAND = 'npm audit'; | ||
const SEPARATOR = ','; | ||
const BASE_COMMAND = "npm audit"; | ||
const SEPARATOR = ","; | ||
const SPLIT_REGEX = /(https:\/\/(nodesecurity.io|npmjs.com)\/advisories\/)/; | ||
const DIGIT_REGEX = /^\d+$/; | ||
const DEFAULT_MESSSAGE_LIMIT = 100000; // characters | ||
const MAX_BUFFER_SIZE = 1024 * 1000 * 50; // 50 MB | ||
function isNumber(string) { | ||
return DIGIT_REGEX.test(string); | ||
} | ||
/** | ||
* Converts an audit level to a numeric value for filtering purposes | ||
* @param {String} auditLevel The npm audit level | ||
* @return {Number} The numeric value, higher is more severe | ||
*/ | ||
const mapLevelToNumber = (auditLevel) => { | ||
switch (auditLevel) { | ||
case "low": | ||
return 1; | ||
case "moderate": | ||
return 2; | ||
case "high": | ||
return 3; | ||
case "critical": | ||
return 4; | ||
default: | ||
return 0; | ||
} | ||
}; | ||
@@ -25,68 +42,118 @@ function unique(value, index, self) { | ||
/** | ||
* Re-runs the audit in human readable form | ||
* @param {String} auditCommand The NPM audit command to use (with flags) | ||
* @param {Boolean} fullLog True if the full log should be displayed in the case of no vulerabilities | ||
*/ | ||
function auditLog(auditCommand, fullLog) { | ||
// Execute `npm audit` command again, but this time we don't use the JSON flag | ||
const audit = exec(auditCommand); | ||
audit.stdout.on("data", (data) => { | ||
if (fullLog) { | ||
console.info(data); | ||
} else { | ||
const toDisplay = data.substring(0, DEFAULT_MESSSAGE_LIMIT); | ||
console.info(toDisplay); | ||
// Display additional info if it is not the full message | ||
if (toDisplay.length < data.length) { | ||
console.info(""); | ||
console.info("..."); | ||
console.info(""); | ||
console.info( | ||
"[MAXIMUM EXCEEDED] Logs exceeded the maximum characters limit. Add the flag `-f` to see the full audit logs." | ||
); | ||
console.info(""); | ||
} | ||
} | ||
// Happy happy, joy joy | ||
console.info("🤝 All good!"); | ||
}); | ||
// stderr | ||
audit.stderr.on("data", (data) => { | ||
console.info(data); | ||
}); | ||
} | ||
/** | ||
* Run the main Audit | ||
* @param {String} auditCommand The NPM audit command to use (with flags) | ||
* @param {Number} auditLevel The level of vulernabilities we care about | ||
* @param {Boolean} fullLog True if the full log should be displayed in the case of no vulerabilities | ||
*/ | ||
function audit(auditCommand, auditLevel, fullLog) { | ||
// Execute `npm audit` command to get the security report, taking into account | ||
// any additional flags that have been passed through. Using the JSON flag | ||
// to make this easier to process | ||
// NOTE: Increase max buffer size from default 1MB | ||
const audit = exec(`${auditCommand} --json` , { maxBuffer: MAX_BUFFER_SIZE }); | ||
// Grab the data in chunks and buffer it as we're unable to | ||
// parse JSON straight from stdout | ||
let jsonBuffer = ""; | ||
audit.stdout.on("data", (data) => (jsonBuffer += data)); | ||
// Once the stdout has completed process the output | ||
audit.stderr.on("close", (code) => { | ||
const { advisories } = JSON.parse(jsonBuffer); | ||
// Grab any un-filtered vunerablities at the appropriate level | ||
const vulnerabilities = Object.values(advisories) | ||
.filter((advisory) => mapLevelToNumber(advisory.severity) >= auditLevel) | ||
.map((advisory) => advisory.id) | ||
.filter((id) => userExceptionIds.indexOf(id) === -1); | ||
// Display an error if we found vulnerabilities | ||
if (vulnerabilities.length > 0) { | ||
const message = `${vulnerabilities.length} vulnerabilities found. Node security advisories: ${vulnerabilities}`; | ||
throw new Error(message); | ||
} else { | ||
// Let's display the audit log instead | ||
auditLog(auditCommand, fullLog); | ||
} | ||
}); | ||
// stderr | ||
audit.stderr.on("data", (data) => { | ||
console.info(data); | ||
}); | ||
} | ||
let userExceptionIds = []; | ||
program | ||
.version('1.1.0') | ||
program.version("1.1.0"); | ||
program | ||
.command('audit') | ||
.description('execute npm audit') | ||
.option("-i, --ignore <ids>", 'Vulnerabilities ID(s) to ignore') | ||
.option("-f, --full", `Display the full audit logs. Default to ${DEFAULT_MESSSAGE_LIMIT} characters.`) | ||
.command("audit") | ||
.description("execute npm audit") | ||
.option("-i, --ignore <ids>", "Vulnerabilities ID(s) to ignore") | ||
.option( | ||
"-f, --full", | ||
`Display the full audit logs. Default to ${DEFAULT_MESSSAGE_LIMIT} characters.` | ||
) | ||
.option("-l, --level <auditLevel>", "The minimum audit level to include") | ||
.option("-p, --production", "Skip checking devDependencies") | ||
.action(function(options) { | ||
if (options && options.ignore) { | ||
userExceptionIds = options.ignore.split(SEPARATOR); | ||
console.info('Exception vulnerabilities ID(s): ', userExceptionIds); | ||
userExceptionIds = options.ignore.split(SEPARATOR).map(Number); | ||
console.info("Exception vulnerabilities ID(s): ", userExceptionIds); | ||
} | ||
// Execute `npm audit` command to get the security report | ||
const audit = exec(AUDIT_COMMAND); | ||
// Grab the audit level passed in, or all by default | ||
let auditLevel = 0; | ||
if (options && options.level) { | ||
auditLevel = mapLevelToNumber(options.level); | ||
} | ||
// stdout | ||
audit.stdout.on('data', data => { | ||
// Split the security report string by the URL at the end, and get the first 4 characters; | ||
// This might contains of other words than the IDs, | ||
// eg: ['===', 'http', 'node', '534', 'http', 'node', '118', 'http', 'node', '146', 'http', 'node', '975', 'http', 'node', '976'] | ||
const rawIds = data.split(SPLIT_REGEX).map(str => str.substring(0, 4).trim()); | ||
// Remove everything except for numbers from the array | ||
const numberIds = rawIds.filter(str => isNumber(str)); | ||
// Remove duplicates | ||
const uniqueIds = numberIds.filter(unique); | ||
// Check if there is any more exceptions other than the user selected to ignore | ||
const vulnerabilities = uniqueIds.filter(id => (userExceptionIds.indexOf(id) === -1)); | ||
// Throw error if we found more exceptions | ||
if (vulnerabilities.length > 0) { | ||
const message = `${vulnerabilities.length} vulnerabilities found. Node security advisories: ${vulnerabilities}`; | ||
throw new Error(message); | ||
} | ||
else { | ||
// If the display-all flag is passed in, display full audit logs | ||
if (options.full) { | ||
console.info(data); | ||
} | ||
// Otherwise, trim audit logs within the maximum characters limit | ||
else { | ||
const toDisplay = data.substring(0, DEFAULT_MESSSAGE_LIMIT); | ||
// Display into console | ||
console.info(toDisplay); | ||
// Display additional info if it is not the full message | ||
if (toDisplay.length < data.length) { | ||
console.info(''); | ||
console.info('...'); | ||
console.info(''); | ||
console.info('[MAXIMUM EXCEEDED] Logs exceeded the maximum characters limit. Add the flag `-f` to see the full audit logs.'); | ||
console.info(''); | ||
} | ||
} | ||
// Happy happy, joy joy | ||
console.info('🤝 All good!'); | ||
} | ||
}); | ||
// Modify the audit command to only include production | ||
let auditCommand = BASE_COMMAND; | ||
if (options && options.production) { | ||
auditCommand += " --production"; | ||
} | ||
// stderr | ||
audit.stderr.on('data', data => { | ||
console.info(data); | ||
}); | ||
audit(auditCommand, auditLevel, options.full); | ||
}); | ||
program.parse(process.argv); |
{ | ||
"name": "better-npm-audit", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"author": "Jee Mok <jee.ict@hotmail.com>", | ||
@@ -5,0 +5,0 @@ "description": "Made to allow skipping certain vulnerabilities, and any extra handling that are not supported by the default npm audit in the future.", |
@@ -28,2 +28,3 @@ # Better NPM Audit | ||
#### Ignore certain vulnerabilities | ||
For skipping certain advisories, you can use `-i` or verbose `--ignore` flags | ||
@@ -35,3 +36,4 @@ | ||
To avoid waterfall logging on your console, there is a character limit set to the output. To view the full audit logs, you can use `-f` or verbose `--full` flags | ||
#### Display full report | ||
To avoid waterfall logging on your console screen, there is a character limit set to the output. To view the full audit logs, you can use `-f` or verbose `--full` flags | ||
@@ -42,2 +44,14 @@ ``` | ||
#### Minimum audit level (`--audit-level`) | ||
Fail an audit only if the results include a vulnerability with a level of moderate or higher: | ||
``` | ||
node node_modules/better-npm-audit audit -l critical | ||
``` | ||
#### Production mode (`--production`) | ||
Skip checking `devDependencies` | ||
``` | ||
node node_modules/better-npm-audit audit -p | ||
``` | ||
## Examples | ||
@@ -147,1 +161,4 @@ | ||
``` | ||
## Special thanks | ||
Thank you [@IPWright83](https://github.com/IPWright83) for his solutions in improving the vulnerability validation for us to have the minimum-audit-level and production-mode flags. |
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
12268
5
135
161