Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

better-npm-audit

Package Overview
Dependencies
Maintainers
1
Versions
70
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

better-npm-audit - npm Package Compare versions

Comparing version 3.2.0-rc.5 to 3.2.1

13

index.js

@@ -17,8 +17,7 @@ #!/usr/bin/env node

* Run audit
* @param {String} auditCommand The NPM audit command to use (with flags)
* @param {Array} exceptionIds List of vulnerability IDs to exclude
* @param {Object} options Parsed command options
* @param {Boolean} shouldScanModules Flag if we should scan the node_modules
* @param {String} auditCommand The NPM audit command to use (with flags)
* @param {String} auditLevel The level of vulnerabilities we care about
* @param {Array} exceptionIds List of vulnerability IDs to exclude
*/
function callback(auditCommand, exceptionIds, options) {
function callback(auditCommand, auditLevel, exceptionIds) {
// Increase the default max buffer size (1 MB)

@@ -33,3 +32,3 @@ var audit = child_process_1.exec(auditCommand + " --json", { maxBuffer: MAX_BUFFER_SIZE });

if (audit.stderr) {
audit.stderr.on('close', function () { return handleFinish_1.default(jsonBuffer, exceptionIds, options); });
audit.stderr.on('close', function () { return handleFinish_1.default(jsonBuffer, auditLevel, exceptionIds); });
// stderr

@@ -48,5 +47,3 @@ audit.stderr.on('data', console.error);

.option('-r, --registry <url>', 'The npm registry url to use.')
.option('-s, --scan-modules [boolean]', 'Scan through reported modules for .nsprc file.', true)
.option('-d, --debug', 'Enable debug mode.')
.action(function (options) { return handleInput_1.default(options, callback); });
program.parse(process.argv);
{
"name": "better-npm-audit",
"version": "3.2.0-rc.5",
"version": "3.2.1",
"author": "Jee Mok <jee.ict@hotmail.com>",
"description": "Reshape npm audit into the way the community would like, by the community itself, to encourage more people to do security audits.",
"description": "Reshape into a better npm audit for the community and encourage more people to include security audit into their process.",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/jeemok/better-npm-audit"
},
"keywords": [
"npm",
"audit",
"skip",
"ignore",
"exclude",
"exceptions",
"node",
"security",
"advisory",
"vulnerabilities",
"continuous integration",
"dependencies",
"check",
"build",
"script",
"nsp",
"ci"
],
"main": "lib/index.js",

@@ -34,7 +11,5 @@ "bin": {

},
"dependencies": {
"commander": "^8.0.0",
"dayjs": "^1.10.6",
"lodash.get": "^4.4.2",
"table": "^6.7.1"
"repository": {
"type": "git",
"url": "https://github.com/jeemok/better-npm-audit"
},

@@ -45,4 +20,3 @@ "engines": {

"scripts": {
"audit:only": "node . audit",
"audit": "npm run build && npm run audit:only",
"audit": "npm run build && node . audit",
"test": "mocha -r ts-node/register test/**/*.test.ts",

@@ -55,5 +29,11 @@ "lint": "eslint .",

"postbuild": "cp README.md lib",
"publish:live": "npm run build && npm publish ./lib --tag latest",
"publish:next": "npm run build && npm publish ./lib --tag next"
"publish:live": "npm run build && npm publish lib --tag latest",
"publish:next": "npm run build && npm publish lib --tag next"
},
"dependencies": {
"commander": "^8.0.0",
"dayjs": "^1.10.6",
"lodash.get": "^4.4.2",
"table": "^6.7.1"
},
"devDependencies": {

@@ -78,3 +58,22 @@ "@types/chai": "^4.2.19",

"typescript": "^4.3.5"
}
},
"keywords": [
"npm",
"audit",
"skip",
"ignore",
"exclude",
"exceptions",
"node",
"security",
"advisory",
"vulnerabilities",
"continuous integration",
"dependencies",
"check",
"build",
"script",
"nsp",
"ci"
]
}

@@ -74,10 +74,8 @@ # Better NPM Audit

| Flag | Short | Default | Description |
| ---------------- | ----- | ------- | ------------------------------------------------------------------------------------------------- |
| `--exclude` | `-x` | | Exceptions or the vulnerabilities ID(s) to exclude |
| `--level` | `-l` | | The minimum audit level to validate; Same as the original `--audit-level` flag |
| `--production` | `-p` | | Skip checking the `devDependencies` |
| `--registry` | `-r` | | The npm registry url to use |
| `--scan-modules` | `-s` | `true` | Scan through reported modules for `.nsprc` file. Note: this feature currently only support NPM v7 |
| `--debug` | `-d` | | Debug mode |
| Flag | Short | Description |
| -------------- | ----- | ------------------------------------------------------------------------------ |
| `--exclude` | `-x` | Exceptions or the vulnerabilities ID(s) to exclude |
| `--level` | `-l` | The minimum audit level to validate; Same as the original `--audit-level` flag |
| `--production` | `-p` | Skip checking the `devDependencies` |
| `--registry` | `-r` | The npm registry url to use |

@@ -132,42 +130,2 @@ <br />

## Auto trust security model
If we trust a package author enough to install their package, then we also trust them to create an `.nsprc` file that covers all the (transitive) dependencies of that package, in the context of that package.
So if we are working on a project `A`, and we install a package `B` as a dependency, then we trust the author of `B` to decide whether `B` is affected by a vulnerability in its dependency `C`. I also trust the author of `B` to make decisions about the author of package `C`, so if `C` contains an `.nsprc` file with an exception about a vulnerability in its dependency, `D`, then we trust that exception because the author of `B` trusts it, and we trust him.
More generally, we can imagine a chain like this:
`A` -> `B` -> `C` -> `D` -> `E` -> `F`
where npm audit reports a vulnerability in `F`, but we are trusting the authors of `B`, `C`, `D`, and `E` to say whether that vulnerability is relevant in the context of their packages.
Extending the example above, then, if we have a tree like this:
```
A -> B -> C -> D -> E -> F
|
-> X -> Y -> Z -> F
```
then the author of package `A` (us), still needs to worry about a vulnerability in `F` due to the way it may be used by `X`, `Y`, and `Z`. Again, though, any of the authors of `X`, `Y`, or `Z` can include an `.nsprc` exception for the vulnerability in `F`, and we will trust their judgement (because we are installing `X`'s package, and he trusts `Y`'s code, etc.)
The auto excepted vulnerabilities will be labeled as "auto" in the report table:
<img src="./.README/auto_exclusion.png" alt="Demo of excluding vulnerabilities flagged by the module maintainers" />
You can turn this feature off by using the flag `--scan-modules=false`
Special shout out to [@EdwinTaylor](https://github.com/alertme-edwin) for his effort in making this possible.
> Note: This feature currently only support npm v7
### Debug mode
To inspect the module `.nsprc` file paths and details, use `--debug` flag to turn on debug mode:
<img src="./.README/debug_mode.png" alt="Debug mode showing all scan paths" />
<br />
## Changelog

@@ -174,0 +132,0 @@

@@ -7,9 +7,9 @@ "use strict";

* Process and analyze the NPM audit JSON
* @param {String} jsonBuffer NPM audit stringified JSON payload
* @param {Array} exceptionIds List of vulnerability IDs to exclude
* @param {Object} options Parsed command options
* @param {String} jsonBuffer NPM audit stringified JSON payload
* @param {Number} auditLevel The level of vulnerabilities we care about
* @param {Array} exceptionIds List of vulnerability IDs to exclude
* @return {undefined}
*/
function handleFinish(jsonBuffer, exceptionIds, options) {
var _a = vulnerability_1.processAuditJson(jsonBuffer, exceptionIds, options), unhandledIds = _a.unhandledIds, vulnerabilityIds = _a.vulnerabilityIds, report = _a.report, maintainerReport = _a.maintainerReport, failed = _a.failed;
function handleFinish(jsonBuffer, auditLevel, exceptionIds) {
var _a = vulnerability_1.processAuditJson(jsonBuffer, auditLevel, exceptionIds), unhandledIds = _a.unhandledIds, vulnerabilityIds = _a.vulnerabilityIds, report = _a.report, failed = _a.failed;
// If unable to process the audit JSON

@@ -20,11 +20,11 @@ if (failed) {

process.exit(1);
return; // This seem unused but it is actually using in the test files to stop the process when we stubbing `process.exit()` above
return;
}
// Print the security report
if (report.length) {
print_1.printSecurityReport(report, options);
print_1.printSecurityReport(report);
}
// Grab any un-filtered vulnerabilities at the appropriate level
var unusedExceptionIds = exceptionIds.filter(function (id) { return !vulnerabilityIds.includes(id); });
// Display the unused exception IDs
// Display the unused exceptionId's
if (unusedExceptionIds.length) {

@@ -37,7 +37,2 @@ var messages = [

}
// Display the auto excluded vulnerabilities
if (maintainerReport.length) {
print_1.printMaintainerReport(maintainerReport);
console.info('The auto scanning and exclusion is enabled by default, use `--scan-modules=false` to turn off this feature.');
}
// Display the found unhandled vulnerabilities

@@ -44,0 +39,0 @@ if (unhandledIds.length) {

"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -22,3 +11,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

/**
* Process and clean user's input
* Handle user's input
* @param {Object} options User's command options or flags

@@ -40,3 +29,2 @@ * @param {Function} fn The function to handle the inputs

var auditLevel = lodash_get_1.default(options, 'level', envVar) || 'info';
var parsedOptions = __assign(__assign({}, options), { level: auditLevel, scanModules: options.scanModules !== 'false' });
// Get the exceptions

@@ -46,4 +34,4 @@ var nsprc = file_1.readFile('.nsprc');

var exceptionIds = vulnerability_1.getExceptionsIds(nsprc, cmdExceptions);
fn(auditCommand, exceptionIds, parsedOptions);
fn(auditCommand, auditLevel, exceptionIds);
}
exports.default = handleInput;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isJsonString = exports.isWholeNumber = void 0;
exports.trimArray = exports.isJsonString = exports.isWholeNumber = void 0;
/**

@@ -32,1 +32,18 @@ * @param {String | Number | Null | Boolean} value The input number

exports.isJsonString = isJsonString;
// TODO: Add unit tests
/**
* Trim array size to a maximum number
* @param {Array} array Array to trim
* @param {Number} maxLength Desired length
* @return {Array} Trimmed array with additional message
*/
function trimArray(array, maxLength) {
var originalLength = array.length;
var removedLength = Math.max(0, originalLength - maxLength);
if (removedLength === 0) {
return array;
}
array.length = maxLength;
return array.concat("...and " + removedLength + " more");
}
exports.trimArray = trimArray;

@@ -11,34 +11,41 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.printMaintainerReport = exports.printExceptionReport = exports.printSecurityReport = void 0;
exports.printExceptionReport = exports.printSecurityReport = exports.getColumnWidth = void 0;
var lodash_get_1 = __importDefault(require("lodash.get"));
var table_1 = require("table");
var SECURITY_REPORT_HEADER = ['ID', 'Module', 'Title', 'Sev.', 'URL', 'Ex.'];
var SECURITY_REPORT_HEADER = ['ID', 'Module', 'Title', 'Paths', 'Sev.', 'URL', 'Ex.'];
var EXCEPTION_REPORT_HEADER = ['ID', 'Status', 'Expiry', 'Notes'];
var MAINTAINER_REPORT_HEADER = ['ID', 'Status', 'Expiry', 'Notes', 'Path'];
var SCAN_PATH = 'Scan path(s)';
var SCAN_PATH_COLUMN_MIN_WIDTH = 15;
var SCAN_PATH_COLUMN_MAX_WIDTH = 45;
// TODO: Add unit tests
/**
* Get the column width size for the table
* @param {Array} tableData Table data (Array of array)
* @param {Number} columnIndex Target column index
* @param {Number} maxWidth Maximum width
* @param {Number} minWidth Minimum width
* @return {Number} width
*/
function getColumnWidth(tableData, columnIndex, maxWidth, minWidth) {
if (maxWidth === void 0) { maxWidth = 50; }
if (minWidth === void 0) { minWidth = 15; }
// Find the maximum length in the column
var contentLength = tableData.reduce(function (max, cur) {
var content = JSON.stringify(lodash_get_1.default(cur, columnIndex, ''));
// Remove the color codes
content = content.replace(/\\x1b\[\d{1,2}m/g, '');
content = content.replace(/\\u001b\[\d{1,2}m/g, '');
content = content.replace(/"/g, '');
// Keep whichever number that is bigger
return content.length > max ? content.length : max;
},
// Start with minimum width (also auto handling empty column case)
minWidth);
// Return the content length up to a maximum point
return Math.min(contentLength, maxWidth);
}
exports.getColumnWidth = getColumnWidth;
/**
* Print the security report in a table format
* @param {Array} data Array of arrays
* @param {Object} options Parsed command options
* @return {undefined} Returns void
* @param {Array} data Array of arrays
* @return {undefined} Returns void
*/
function printSecurityReport(data, options) {
var _a;
// Additional header for debug mode
var headers = options.debug ? __spreadArray(__spreadArray([], SECURITY_REPORT_HEADER), [SCAN_PATH, 'Found file(s)']) : SECURITY_REPORT_HEADER;
// Set table column configs
var columns = {};
if (options.debug) {
var scanPathColumnIndex_1 = headers.findIndex(function (header) { return header === SCAN_PATH; });
// Find the maximum scan path size
var maxLength = data.reduce(function (max, cur) { return (lodash_get_1.default(cur, scanPathColumnIndex_1, '').length > max ? lodash_get_1.default(cur, scanPathColumnIndex_1, '').length : max); }, 0);
columns = (_a = {},
_a[scanPathColumnIndex_1] = {
width: maxLength === 0 ? SCAN_PATH_COLUMN_MIN_WIDTH : Math.min(maxLength, SCAN_PATH_COLUMN_MAX_WIDTH),
wrapWord: true,
},
_a);
}
function printSecurityReport(data) {
var configs = {

@@ -50,5 +57,16 @@ singleLine: true,

},
columns: columns,
columns: {
// "Title" column index
2: {
width: getColumnWidth(data, 2),
wrapWord: true,
},
// "Paths" column index
3: {
width: getColumnWidth(data, 3),
wrapWord: true,
},
},
};
console.info(table_1.table(__spreadArray([headers], data), configs));
console.info(table_1.table(__spreadArray([SECURITY_REPORT_HEADER], data), configs));
}

@@ -72,17 +90,1 @@ exports.printSecurityReport = printSecurityReport;

exports.printExceptionReport = printExceptionReport;
/**
* Print the exception report in a table format
* @param {Array} data Array of arrays
* @return {undefined} Returns void
*/
function printMaintainerReport(data) {
var configs = {
singleLine: true,
header: {
alignment: 'center',
content: '=== internal modules exceptions ===\n',
},
};
console.info(table_1.table(__spreadArray([MAINTAINER_REPORT_HEADER], data), configs));
}
exports.printMaintainerReport = printMaintainerReport;
"use strict";
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -6,3 +11,3 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

Object.defineProperty(exports, "__esModule", { value: true });
exports.processExceptions = exports.getExceptionsIds = exports.processAuditJson = exports.checkTrustInDependencies = exports.mapLevelToNumber = exports.mapModuleDependencies = void 0;
exports.processExceptions = exports.getExceptionsIds = exports.processAuditJson = exports.mapLevelToNumber = void 0;
var lodash_get_1 = __importDefault(require("lodash.get"));

@@ -13,20 +18,4 @@ var common_1 = require("./common");

var date_1 = require("./date");
var file_1 = require("./file");
var MAX_PATHS_SIZE = 5;
/**
* Map out all the dependencies path for a module
* @param {String} path Full dependency path for the reported module
* @return {Array} Array of dependency paths
*/
function mapModuleDependencies(path) {
return path.split('node_modules/').reduce(function (acc, cur) {
// Ends with '/' meaning it is the dependency and not the final module (reported module)
if (!cur.endsWith('/')) {
return acc;
}
// Append with last dependencies path
return acc.concat((acc[acc.length - 1] || '') + "node_modules/" + cur);
}, []);
}
exports.mapModuleDependencies = mapModuleDependencies;
/**
* Converts an audit level to a numeric value

@@ -54,66 +43,12 @@ * @param {String} auditLevel Audit level

/**
* Check if it is fine to except the reported vulnerability by checking the inner dependencies
* @param {Number} vulnerabilityId Reported vulnerability ID
* @param {Array} affectedModulePaths Reported module path
* @return {Object} Return the details if it can be excepted
*/
function checkTrustInDependencies(vulnerabilityId, affectedModulePaths) {
var scannedPaths = [];
var foundPaths = [];
var report = [];
// Using `.reduce` instead of `.every` to provide better overview details in the report
var trust = affectedModulePaths.reduce(function (finalTrust, affectedModule) {
// Get all the dependencies that is using this reported module
var dependencyPaths = mapModuleDependencies(affectedModule);
// Audit the scanned paths
scannedPaths.push.apply(scannedPaths, dependencyPaths);
// Trust any of the dependency's decision if they say to ignore it
var canTrust = dependencyPaths.some(function (path) {
var nsprcPath = path + ".nsprc";
// Try retrieving the `.nsprc` file
var nsprcFile = file_1.readFile(nsprcPath);
// File not found
if (typeof nsprcFile === 'boolean') {
return false;
}
// Audit the found paths
foundPaths.push(nsprcPath);
// Process the file to get valid exceptions
var _a = processExceptions(nsprcFile, []), exceptionIds = _a.exceptionIds, fileReport = _a.report;
var exceptionRow = fileReport.find(function (_a) {
var exceptionId = _a[0];
return Number(exceptionId) === vulnerabilityId;
});
// Append the relevant exception into the maintainer report
if (exceptionRow) {
// Include the used path
report.push(exceptionRow.concat(nsprcPath));
}
// Check if the maintainer have explicitly exclude the vulnerability
return exceptionIds.includes(vulnerabilityId);
});
// We want every affected module paths to be validated `true`;
// so if trust is broken already (previous round returns `false`), we will continue to return `false` until the end
if (!finalTrust) {
return false;
}
return canTrust;
}, true);
return {
scannedPaths: scannedPaths,
foundPaths: foundPaths,
trust: trust,
report: report,
};
}
exports.checkTrustInDependencies = checkTrustInDependencies;
/**
* Analyze the JSON string buffer
* @param {String} jsonBuffer NPM Audit JSON string buffer
* @param {Array} exceptionIds User's exception IDs
* @param {Object} options Parsed command options
* @return {Object} Processed vulnerabilities details
* @param {String} jsonBuffer NPM Audit JSON string buffer
* @param {String} auditLevel User's target audit level
* @param {Array} exceptionIds User's exception IDs
* @return {Object} Processed vulnerabilities details
*/
function processAuditJson(jsonBuffer, exceptionIds, options) {
function processAuditJson(jsonBuffer, auditLevel, exceptionIds) {
if (jsonBuffer === void 0) { jsonBuffer = ''; }
if (auditLevel === void 0) { auditLevel = 'info'; }
if (exceptionIds === void 0) { exceptionIds = []; }
if (!common_1.isJsonString(jsonBuffer)) {

@@ -124,3 +59,2 @@ return {

report: [],
maintainerReport: [],
failed: true,

@@ -136,3 +70,3 @@ };

return Object.values(advisories).reduce(function (acc, cur) {
var shouldAudit = mapLevelToNumber(cur.severity) >= mapLevelToNumber(options.level);
var shouldAudit = mapLevelToNumber(cur.severity) >= mapLevelToNumber(auditLevel);
var isExcepted = exceptionIds.includes(Number(cur.id));

@@ -144,2 +78,3 @@ // Record this vulnerability into the report, and highlight it using yellow color if it's new

color_1.color(cur.title, isExcepted ? '' : 'yellow'),
color_1.color(common_1.trimArray(cur.findings.reduce(function (a, c) { return __spreadArray(__spreadArray([], a), c.paths); }, []), MAX_PATHS_SIZE).join('\n'), isExcepted ? '' : 'yellow'),
color_1.color(cur.severity, isExcepted ? '' : 'yellow', color_1.getSeverityBgColor(cur.severity)),

@@ -159,3 +94,2 @@ color_1.color(cur.url, isExcepted ? '' : 'yellow'),

report: [],
maintainerReport: [],
});

@@ -168,3 +102,2 @@ }

lodash_get_1.default(cur, 'via', []).forEach(function (vul) {
var _a;
// The vulnerability ID is labeled as `source`

@@ -176,45 +109,14 @@ var id = lodash_get_1.default(vul, 'source', '');

}
var shouldAudit = mapLevelToNumber(vul.severity) >= mapLevelToNumber(options.level);
var isExceptedByMaintainers = false;
var scannedDependenciesPaths = [];
var foundDependenciesPaths = [];
// If scan internal modules flag is enabled,
if (options.scanModules && typeof cur !== 'string') {
// Check inner dependencies if we can except this vulnerability
var _b = checkTrustInDependencies(id, cur.nodes), scannedPaths = _b.scannedPaths, foundPaths = _b.foundPaths, trust = _b.trust, report = _b.report;
isExceptedByMaintainers = trust;
if (scannedPaths.length) {
scannedDependenciesPaths = scannedPaths;
}
if (foundPaths.length) {
foundDependenciesPaths = foundPaths;
}
if (report.length) {
(_a = acc.maintainerReport).push.apply(_a, report);
}
}
var isExceptedByUs = exceptionIds.includes(id);
var isExcepted = isExceptedByUs || isExceptedByMaintainers;
// Construct `isExcepted` value to display in the report
var isExceptedValue = color_1.color('n', 'red');
if (isExceptedByMaintainers) {
isExceptedValue = 'auto';
}
else if (isExceptedByUs) {
isExceptedValue = 'y';
}
var shouldAudit = mapLevelToNumber(vul.severity) >= mapLevelToNumber(auditLevel);
var isExcepted = exceptionIds.includes(id);
// Record this vulnerability into the report, and highlight it using yellow color if it's new
var securityReportRow = [
acc.report.push([
color_1.color(String(id), isExcepted ? '' : 'yellow'),
color_1.color(vul.name, isExcepted ? '' : 'yellow'),
color_1.color(vul.title, isExcepted ? '' : 'yellow'),
color_1.color(common_1.trimArray(lodash_get_1.default(cur, 'nodes', []), MAX_PATHS_SIZE).join('\n'), isExcepted ? '' : 'yellow'),
color_1.color(vul.severity, isExcepted ? '' : 'yellow', color_1.getSeverityBgColor(vul.severity)),
color_1.color(vul.url, isExcepted ? '' : 'yellow'),
isExceptedValue,
];
// Add additional info in debug mode
if (options.debug) {
securityReportRow.push(scannedDependenciesPaths.join(', '), foundDependenciesPaths.length + "/" + scannedDependenciesPaths.length);
}
acc.report.push(securityReportRow);
isExcepted ? 'y' : color_1.color('n', 'yellow'),
]);
acc.vulnerabilityIds.push(id);

@@ -231,3 +133,2 @@ // Found unhandled vulnerabilities

report: [],
maintainerReport: [],
});

@@ -239,3 +140,2 @@ }

report: [],
maintainerReport: [],
failed: true,

@@ -242,0 +142,0 @@ };

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc