better-npm-audit
Advanced tools
Comparing version 3.2.0-rc.1 to 3.2.0-rc.2
{ | ||
"name": "better-npm-audit", | ||
"version": "3.2.0-rc.1", | ||
"version": "3.2.0-rc.2", | ||
"author": "Jee Mok <jee.ict@hotmail.com>", | ||
@@ -5,0 +5,0 @@ "description": "Reshape npm audit into the way the community would like, by the community itself, to encourage more people to do security audits.", |
@@ -131,8 +131,32 @@ # Better NPM Audit | ||
## Auto exclusion from maintainers' notes | ||
## Auto trust security model | ||
Module that has `.nsprc` file will be used in the audit process if `--scan-modules` flag is enabled: | ||
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 | ||
@@ -139,0 +163,0 @@ |
@@ -6,3 +6,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.processExceptions = exports.getExceptionsIds = exports.processAuditJson = exports.mapLevelToNumber = void 0; | ||
exports.processExceptions = exports.getExceptionsIds = exports.processAuditJson = exports.checkTrustInDependencies = exports.mapLevelToNumber = exports.mapModuleDependencies = void 0; | ||
var lodash_get_1 = __importDefault(require("lodash.get")); | ||
@@ -15,2 +15,18 @@ var common_1 = require("./common"); | ||
/** | ||
* 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 | ||
@@ -38,2 +54,51 @@ * @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 = []; | ||
var trust = affectedModulePaths.every(function (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 | ||
return 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); | ||
}); | ||
}); | ||
return { | ||
scannedPaths: scannedPaths, | ||
foundPaths: foundPaths, | ||
trust: trust, | ||
report: report, | ||
}; | ||
} | ||
exports.checkTrustInDependencies = checkTrustInDependencies; | ||
/** | ||
* Analyze the JSON string buffer | ||
@@ -92,2 +157,3 @@ * @param {String} jsonBuffer NPM Audit JSON string buffer | ||
lodash_get_1.default(cur, 'via', []).forEach(function (vul) { | ||
var _a; | ||
// The vulnerability ID is labeled as `source` | ||
@@ -101,29 +167,18 @@ var id = lodash_get_1.default(vul, 'source', ''); | ||
var isExceptedByMaintainers = false; | ||
var foundModuleExceptionFileNum = 0; | ||
var scannedDependenciesPaths = []; | ||
var foundDependenciesPaths = []; | ||
// If scan internal modules flag is enabled, | ||
if (options.scanModules && typeof cur !== 'string') { | ||
// Find all the affected modules | ||
isExceptedByMaintainers = cur.nodes.every(function (path) { | ||
// Try checking if the module itself if the maintainer has a `.nsprc` file | ||
var maintainerNsprc = file_1.readFile(path + "/.nsprc"); | ||
// File not found | ||
if (typeof maintainerNsprc === 'boolean') { | ||
return false; | ||
} | ||
// Count the found files | ||
foundModuleExceptionFileNum += 1; | ||
// Process the file to get valid exceptions | ||
var _a = processExceptions(maintainerNsprc, []), maintainerExceptionIds = _a.exceptionIds, maintainerReport = _a.report; | ||
var exceptionRow = maintainerReport.find(function (_a) { | ||
var exceptionId = _a[0]; | ||
return Number(exceptionId) === id; | ||
}); | ||
// Append the relevant exception into the maintainer report | ||
if (exceptionRow) { | ||
// Include the file path | ||
acc.maintainerReport.push(exceptionRow.concat(path + "/.nsprc")); | ||
} | ||
// Check if the maintainer have explicitly exclude the vulnerability | ||
return maintainerExceptionIds.includes(id); | ||
}); | ||
// 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); | ||
} | ||
} | ||
@@ -151,3 +206,3 @@ var isExceptedByUs = exceptionIds.includes(id); | ||
if (options.debug) { | ||
securityReportRow.push(lodash_get_1.default(cur, 'nodes', []).join(', '), foundModuleExceptionFileNum + "/" + lodash_get_1.default(cur, 'nodes', []).length); | ||
securityReportRow.push(scannedDependenciesPaths.join(', '), foundDependenciesPaths.length + "/" + scannedDependenciesPaths.length); | ||
} | ||
@@ -154,0 +209,0 @@ acc.report.push(securityReportRow); |
40992
704
192