@whittjs/better-npm-audit 3.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +182 -0
- package/index.js +52 -0
- package/package.json +78 -0
- package/src/handlers/handleFinish.js +41 -0
- package/src/handlers/handleInput.js +69 -0
- package/src/utils/color.js +89 -0
- package/src/utils/common.js +59 -0
- package/src/utils/date.js +38 -0
- package/src/utils/file.js +26 -0
- package/src/utils/npm.js +13 -0
- package/src/utils/print.js +93 -0
- package/src/utils/vulnerability.js +365 -0
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Better NPM Audit
|
|
2
|
+
|
|
3
|
+
The goal of this project is to provide additional features on top of the existing npm audit options. We hope to encourage more people to do security audits for their projects.
|
|
4
|
+
|
|
5
|
+
[](https://npmjs.org/package/better-npm-audit)
|
|
6
|
+
|
|
7
|
+
       
|
|
8
|
+
|
|
9
|
+
## Looking for Collaborators
|
|
10
|
+
|
|
11
|
+
Hi there! đź‘‹
|
|
12
|
+
|
|
13
|
+
I’m currently looking for collaborators to help maintain and develop this project. Due to time constraints, I haven’t been able to give it the attention it deserves, but I believe it has great potential to grow with the help of passionate contributors.
|
|
14
|
+
|
|
15
|
+
Become a Collaborator
|
|
16
|
+
If you’re interested in contributing on a regular basis, I’d love to have you on board as a collaborator. Whether you’re interested in fixing bugs, adding new features, or improving documentation, your contributions will be highly valued.
|
|
17
|
+
|
|
18
|
+
As a collaborator, you’ll have push access to the repository and play a key role in shaping the future of the project. If this sounds like something you’d be interested in, please reach out! You can open an issue titled “Interested in Collaborating” or contact me directly via email.
|
|
19
|
+
|
|
20
|
+
Let’s work together to make this project even better!
|
|
21
|
+
|
|
22
|
+
## NPM version 6 and 7, and 8
|
|
23
|
+
|
|
24
|
+
NPM has upgraded to version 7 in late 2020 and has breaking changes on the `npm audit`. The output of npm audit has significantly changed both in the human-readable and `--json` output styles. Even more unfortunately, when NPM changed the JSON output in npm v7, they removed many of the other useful identifiers (`cves`, `cwe`, `github_advisory_id`) and the only thing left is the URL. We are trying our best to handle each version and provide consistent functionality to all of them. Related docs on v6 and v7 changes:
|
|
25
|
+
|
|
26
|
+
| Docs | Link |
|
|
27
|
+
| -------------------------- | ------------------------------------------------------------------------------------------ |
|
|
28
|
+
| NPM v6 & v7 changes | https://github.blog/2020-10-13-presenting-v7-0-0-of-the-npm-cli/ |
|
|
29
|
+
| NPM v7 blog post | https://blog.npmjs.org/post/626173315965468672/npm-v7-series-beta-release-and-semver-major |
|
|
30
|
+
| Official NPM v6 audit docs | https://docs.npmjs.com/cli/v6/commands/npm-audit |
|
|
31
|
+
| Official NPM v7 audit docs | https://docs.npmjs.com/cli/v7/commands/npm-audit |
|
|
32
|
+
| Dealing with new npm audit | https://uko.codes/dealing-with-npm-v7-audit-changes |
|
|
33
|
+
|
|
34
|
+
You may find the sample JSON outputs for each NPM versions in our codebase: [v6](https://github.com/jeemok/better-npm-audit/blob/master/test/__mocks__/v6-json-buffer.json), [v7](https://github.com/jeemok/better-npm-audit/blob/master/test/__mocks__/v7-json-buffer.json) & [v8](https://github.com/jeemok/better-npm-audit/blob/master/test/__mocks__/v8-json-buffer.json).
|
|
35
|
+
|
|
36
|
+
<br />
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
$ npm install --save better-npm-audit
|
|
41
|
+
|
|
42
|
+
or
|
|
43
|
+
|
|
44
|
+
$ npm install -g better-npm-audit
|
|
45
|
+
|
|
46
|
+
<br />
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
### Run global
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
better-npm-audit audit
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Run with exceptions
|
|
57
|
+
|
|
58
|
+
<img src="./.README/all_good.png" alt="Demo of table displaying the security report" />
|
|
59
|
+
|
|
60
|
+
Unhandled or newly reported vulnerabilities will be highlighted:
|
|
61
|
+
|
|
62
|
+
<img src="./.README/highlighted_exceptions.png" alt="Demo of table displaying the security report" />
|
|
63
|
+
|
|
64
|
+
Unused exceptions will be notified:
|
|
65
|
+
|
|
66
|
+
<img src="./.README/unused_exception.png" alt="Demo of displaying the unused exception" />
|
|
67
|
+
|
|
68
|
+
### Add into package scripts
|
|
69
|
+
|
|
70
|
+
```JSON
|
|
71
|
+
{
|
|
72
|
+
"scripts": {
|
|
73
|
+
"prepush": "npm run test && npm run audit",
|
|
74
|
+
"audit": "better-npm-audit audit"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Now you can run locally or in your CI pipeline:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
npm run audit
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Filter vulnerability table
|
|
86
|
+
|
|
87
|
+
You can filter the vulnerability table to show only vulnerabilities at or above a specified severity level using the `--filter-table` flag. This is useful for reducing noise in the output while maintaining the original audit behavior for exit codes.
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Filter table to show only high and critical vulnerabilities
|
|
91
|
+
better-npm-audit audit --filter-table high
|
|
92
|
+
|
|
93
|
+
# Filter table to match the audit level
|
|
94
|
+
better-npm-audit audit --level moderate --filter-table
|
|
95
|
+
|
|
96
|
+
# Set different levels for exit behavior vs table display
|
|
97
|
+
better-npm-audit audit --level high --filter-table moderate
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Note:** The `--filter-table` flag only affects what vulnerabilities are displayed in the table. The audit level (`--level`) still controls the exit behavior and vulnerability counting.
|
|
101
|
+
|
|
102
|
+
<br />
|
|
103
|
+
|
|
104
|
+
## Options
|
|
105
|
+
|
|
106
|
+
| Flag | Short | Description |
|
|
107
|
+
| ------------------- | ----- | ----------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| `--exclude` | `-x` | Exceptions or the vulnerabilities ID(s) to exclude; the ID can be the numeric ID, CVE, CWE or GHSA ID |
|
|
109
|
+
| `--module-ignore` | `-m` | Names of modules to exclude |
|
|
110
|
+
| `--level` | `-l` | The minimum audit level to validate; Same as the original `--audit-level` flag |
|
|
111
|
+
| `--filter-table` | `-f` | Filter the vulnerability table to show only vulnerabilities at or above the specified level. Accepts a level (`info`, `low`, `moderate`, `high`, `critical`) or can be used as a boolean flag to filter by the audit level |
|
|
112
|
+
| `--production` | `-p` | Skip the `devDependencies` |
|
|
113
|
+
| `--registry` | `-r` | The npm registry url to use |
|
|
114
|
+
| `--include-columns` | `-i` | Columns to include in report. Available columns: `ID`, `Module`, `Title`, `Paths`, `Severity`, `URL`, `Ex.`, `Fix` (e.g. `-i Module,Title,Severity`) |
|
|
115
|
+
|
|
116
|
+
<br />
|
|
117
|
+
|
|
118
|
+
## Environment Variables
|
|
119
|
+
|
|
120
|
+
| Variable | Description |
|
|
121
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
|
122
|
+
| `NO_COLOR` | Support the [no-color standard](https://no-color.org/) to allow users use the tool without colored output. |
|
|
123
|
+
| `NPM_CONFIG_AUDIT_LEVEL` | Used in setting the audit level. <br /> _Note: this will be disregard if the audit level flag is passed onto the command._ |
|
|
124
|
+
|
|
125
|
+
<br />
|
|
126
|
+
|
|
127
|
+
## Using `.nsprc` file to manage exceptions
|
|
128
|
+
|
|
129
|
+
You may add a file `.nsprc` to your project root directory to manage the exceptions. For example:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"1337": {
|
|
134
|
+
"active": true,
|
|
135
|
+
"notes": "Ignored since we don't use xxx method",
|
|
136
|
+
"expiry": 1615462134681
|
|
137
|
+
},
|
|
138
|
+
"4501": {
|
|
139
|
+
"active": false,
|
|
140
|
+
"notes": "Ignored since we don't use xxx method"
|
|
141
|
+
},
|
|
142
|
+
"CWE-471": "CWE ID is acceptable",
|
|
143
|
+
"GHSA-ww39-953v-wcq6": "GHSA ID is acceptable",
|
|
144
|
+
"https://npmjs.com/advisories/1213": "Full or partial URL is acceptable too"
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Fields
|
|
149
|
+
|
|
150
|
+
| Attribute | Type | Description | Default | Examples |
|
|
151
|
+
| --------- | ---------------- | --------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
152
|
+
| `active` | Boolean | If the tool should use it for exception | `true` | `true` |
|
|
153
|
+
| `expiry` | String \| Number | Human-readable date, or milliseconds since the UNIX Epoch | | - `'2020-01-31'` <br> - `'2020/01/31'` <br> - `'01/31/2021, 11:03:58'` <br> - `'1 March 2016 15:00'` <br> - `'1 March 2016 3:00 pm'` <br> - `'2012-01-26T13:51:50.417-07:00'` <br> - `'Sun, 11 Jul 2021 03:03:13 GMT'` <br> - `'Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time)'` <br> - `327611110417` |
|
|
154
|
+
| `notes` | String | Notes related to the vulnerability. | |
|
|
155
|
+
|
|
156
|
+
<br />
|
|
157
|
+
|
|
158
|
+
When using a `.nsprc` file, a report will be displayed when it starts running:
|
|
159
|
+
|
|
160
|
+
<img src="./.README/exceptions_table.png" alt="Demo of table displaying a list of exceptions" />
|
|
161
|
+
|
|
162
|
+
> Note: the expiry date will be styled in yellow and red color if it is detected more than one or five years ago.
|
|
163
|
+
|
|
164
|
+
<br />
|
|
165
|
+
|
|
166
|
+
## Changelog
|
|
167
|
+
|
|
168
|
+
You can find the changelog [here](https://github.com/jeemok/better-npm-audit/blob/master/CHANGELOG.md).
|
|
169
|
+
|
|
170
|
+
<br />
|
|
171
|
+
|
|
172
|
+
## Contributors
|
|
173
|
+
|
|
174
|
+
[Ian Wright](https://github.com/IPWright83), [Edwin Taylor](https://github.com/alertme-edwin), [Maarten Hus](https://github.com/MrHus), [Alex Burkowsky](https://github.com/alexburkowskypolysign), [David M. Lee](https://github.com/leedm777), [Kyle Clark](https://github.com/kyle-clark1824), [Guillermo Pincay](https://github.com/guillermaster), [Grzegorz Pawłowski](https://github.com/GrzesiekP), [CSLTech](https://github.com/CSLTech), [Paul Clarkin](https://github.com/paulclarkin), [mgdodge](https://github.com/mgdodge), [Ricky Sullivan](https://github.com/rickysullivan), [Sam Gregory](https://github.com/samgregory88), [Tristan WAGNER](https://github.com/tristanwagner), [Zak](https://github.com/ZedLove), [Eric Cornelissen](https://github.com/ericcornelissen), [Gaurav Chinavle](https://github.com/GauravChinavle)
|
|
175
|
+
|
|
176
|
+
<br />
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
If you like this project,
|
|
181
|
+
|
|
182
|
+
<a href="https://www.buymeacoffee.com/jeemok" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
package/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.callback = void 0;
|
|
8
|
+
var commander_1 = require("commander");
|
|
9
|
+
var child_process_1 = require("child_process");
|
|
10
|
+
var handleInput_1 = __importDefault(require("./src/handlers/handleInput"));
|
|
11
|
+
var handleFinish_1 = __importDefault(require("./src/handlers/handleFinish"));
|
|
12
|
+
var package_json_1 = __importDefault(require("./package.json"));
|
|
13
|
+
var MAX_BUFFER_SIZE = 1024 * 1000 * 50; // 50 MB
|
|
14
|
+
var program = new commander_1.Command();
|
|
15
|
+
/**
|
|
16
|
+
* Run audit
|
|
17
|
+
* @param {String} auditCommand The NPM audit command to use (with flags)
|
|
18
|
+
* @param {String} auditLevel The level of vulnerabilities we care about
|
|
19
|
+
* @param {Array} exceptionIds List of vulnerability IDs to exclude
|
|
20
|
+
* @param {Array} modulesToIgnore List of vulnerable modules to ignore in audit results
|
|
21
|
+
* @param {Array} columnsToInclude List of columns to include in audit results
|
|
22
|
+
* @param {String} filterLevel Optional level to filter table display
|
|
23
|
+
*/
|
|
24
|
+
function callback(auditCommand, auditLevel, exceptionIds, modulesToIgnore, columnsToInclude, filterLevel) {
|
|
25
|
+
// Increase the default max buffer size (1 MB)
|
|
26
|
+
var audit = (0, child_process_1.exec)("".concat(auditCommand, " --json"), { maxBuffer: MAX_BUFFER_SIZE });
|
|
27
|
+
// Grab the data in chunks and buffer it as we're unable to parse JSON straight from stdout
|
|
28
|
+
var jsonBuffer = '';
|
|
29
|
+
if (audit.stdout) {
|
|
30
|
+
audit.stdout.on('data', function (data) { return (jsonBuffer += data); });
|
|
31
|
+
}
|
|
32
|
+
// Once the stdout has completed, process the output
|
|
33
|
+
if (audit.stderr) {
|
|
34
|
+
audit.stderr.on('close', function () { return (0, handleFinish_1.default)(jsonBuffer, auditLevel, exceptionIds, modulesToIgnore, columnsToInclude, filterLevel); });
|
|
35
|
+
// stderr
|
|
36
|
+
audit.stderr.on('data', console.error);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.callback = callback;
|
|
40
|
+
program.name(package_json_1.default.name).version(package_json_1.default.version);
|
|
41
|
+
program
|
|
42
|
+
.command('audit')
|
|
43
|
+
.description('execute npm audit')
|
|
44
|
+
.option('-x, --exclude <ids>', 'Exceptions or the vulnerabilities ID(s) to exclude.')
|
|
45
|
+
.option('-m, --module-ignore <moduleNames>', 'Names of modules to ignore.')
|
|
46
|
+
.option('-l, --level <auditLevel>', 'The minimum audit level to validate.')
|
|
47
|
+
.option('-f, --filter-table [level]', 'Filter table to show only vulnerabilities at or above specified level (defaults to audit level if no value provided).')
|
|
48
|
+
.option('-p, --production', 'Skip checking the devDependencies.')
|
|
49
|
+
.option('-r, --registry <url>', 'The npm registry url to use.')
|
|
50
|
+
.option('-i, --include-columns <columnName1>,<columnName2>,..,<columnNameN>', 'Columns to include in report.')
|
|
51
|
+
.action(function (options) { return (0, handleInput_1.default)(options, callback); });
|
|
52
|
+
program.parse(process.argv);
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@whittjs/better-npm-audit",
|
|
3
|
+
"version": "3.12.0",
|
|
4
|
+
"author": "Jee Mok <jee.ict@hotmail.com>",
|
|
5
|
+
"description": "Reshape into a better npm audit for the community and encourage more people to include security audit into their process.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"better-npm-audit": "index.js"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/WhittJS/better-npm-audit"
|
|
14
|
+
},
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">= 8.12"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"preaudit": "npm run build",
|
|
20
|
+
"audit": "node lib audit -x 1064843,1067245",
|
|
21
|
+
"test": "mocha -r ts-node/register test/**/*.test.ts",
|
|
22
|
+
"lint": "eslint .",
|
|
23
|
+
"qc": "npm run test && npm run lint",
|
|
24
|
+
"clean": "rimraf lib",
|
|
25
|
+
"prebuild": "npm run qc && npm run clean",
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"postbuild": "cp README.md lib && chmod +x ./lib/index.js",
|
|
28
|
+
"publish:live": "npm run build && npm publish ./lib --tag latest",
|
|
29
|
+
"publish:next": "npm run build && npm publish ./lib --tag next"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"commander": "^8.0.0",
|
|
33
|
+
"dayjs": "^1.10.6",
|
|
34
|
+
"lodash.get": "^4.4.2",
|
|
35
|
+
"semver": "^7.6.3",
|
|
36
|
+
"table": "^6.7.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/chai": "^4.2.19",
|
|
40
|
+
"@types/lodash.get": "^4.4.6",
|
|
41
|
+
"@types/mocha": "^8.2.3",
|
|
42
|
+
"@types/node": "^16.0.0",
|
|
43
|
+
"@types/semver": "^7.5.8",
|
|
44
|
+
"@types/sinon": "^10.0.2",
|
|
45
|
+
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
|
46
|
+
"@typescript-eslint/parser": "^4.28.2",
|
|
47
|
+
"chai": "^4.3.0",
|
|
48
|
+
"eslint": "^7.25.0",
|
|
49
|
+
"eslint-config-google": "^0.14.0",
|
|
50
|
+
"eslint-config-prettier": "^8.3.0",
|
|
51
|
+
"eslint-plugin-prettier": "^3.4.0",
|
|
52
|
+
"mocha": "^10.7.3",
|
|
53
|
+
"prettier": "2.3.2",
|
|
54
|
+
"rimraf": "^3.0.2",
|
|
55
|
+
"sinon": "^9.2.4",
|
|
56
|
+
"ts-node": "^10.0.0",
|
|
57
|
+
"typescript": "^4.3.5"
|
|
58
|
+
},
|
|
59
|
+
"keywords": [
|
|
60
|
+
"npm",
|
|
61
|
+
"audit",
|
|
62
|
+
"skip",
|
|
63
|
+
"ignore",
|
|
64
|
+
"exclude",
|
|
65
|
+
"exceptions",
|
|
66
|
+
"node",
|
|
67
|
+
"security",
|
|
68
|
+
"advisory",
|
|
69
|
+
"vulnerabilities",
|
|
70
|
+
"continuous integration",
|
|
71
|
+
"dependencies",
|
|
72
|
+
"check",
|
|
73
|
+
"build",
|
|
74
|
+
"script",
|
|
75
|
+
"nsp",
|
|
76
|
+
"ci"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
var print_1 = require("../utils/print");
|
|
4
|
+
var vulnerability_1 = require("../utils/vulnerability");
|
|
5
|
+
/**
|
|
6
|
+
* Process and analyze the NPM audit JSON
|
|
7
|
+
* @param {String} jsonBuffer NPM audit stringified JSON payload
|
|
8
|
+
* @param {Number} auditLevel The level of vulnerabilities we care about
|
|
9
|
+
* @param {Array} exceptionIds List of vulnerability IDs to exclude
|
|
10
|
+
* @param {Array} exceptionModules List of vulnerable modules to ignore in audit results
|
|
11
|
+
* @param {Array} columnsToInclude List of columns to include in audit results
|
|
12
|
+
* @param {String} filterLevel Optional level to filter table display
|
|
13
|
+
*/
|
|
14
|
+
function handleFinish(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude, filterLevel) {
|
|
15
|
+
var _a = (0, vulnerability_1.processAuditJson)(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude, filterLevel), unhandledIds = _a.unhandledIds, report = _a.report, failed = _a.failed, unusedExceptionIds = _a.unusedExceptionIds, unusedExceptionModules = _a.unusedExceptionModules;
|
|
16
|
+
// If unable to process the audit JSON
|
|
17
|
+
if (failed) {
|
|
18
|
+
console.error('Unable to process the JSON buffer string.');
|
|
19
|
+
// Exit failed
|
|
20
|
+
process.exit(1);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
// Print the security report
|
|
24
|
+
if (report.length) {
|
|
25
|
+
(0, print_1.printSecurityReport)(report, columnsToInclude);
|
|
26
|
+
}
|
|
27
|
+
// Handle unused exceptions
|
|
28
|
+
(0, vulnerability_1.handleUnusedExceptions)(unusedExceptionIds, unusedExceptionModules);
|
|
29
|
+
// Display the found unhandled vulnerabilities
|
|
30
|
+
if (unhandledIds.length) {
|
|
31
|
+
console.error("".concat(unhandledIds.length, " vulnerabilities found. Node security advisories: ").concat(unhandledIds.join(', ')));
|
|
32
|
+
// Exit failed
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
// Happy happy, joy joy
|
|
37
|
+
console.info('🤝 All good!');
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.default = handleFinish;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
var lodash_get_1 = __importDefault(require("lodash.get"));
|
|
7
|
+
var semver_1 = __importDefault(require("semver"));
|
|
8
|
+
var npm_1 = require("../utils/npm");
|
|
9
|
+
var file_1 = require("../utils/file");
|
|
10
|
+
var vulnerability_1 = require("../utils/vulnerability");
|
|
11
|
+
/**
|
|
12
|
+
* Get the `npm audit` flag to audit only production dependencies.
|
|
13
|
+
* @return {String} The flag.
|
|
14
|
+
*/
|
|
15
|
+
function getProductionOnlyOption() {
|
|
16
|
+
var npmVersion = (0, npm_1.getNpmVersion)();
|
|
17
|
+
if (semver_1.default.satisfies(npmVersion, '<=8.13.2')) {
|
|
18
|
+
return '--production';
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return '--omit=dev';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Handle user's input
|
|
26
|
+
* @param {Object} options User's command options or flags
|
|
27
|
+
* @param {Function} fn The function to handle the inputs
|
|
28
|
+
*/
|
|
29
|
+
function handleInput(options, fn) {
|
|
30
|
+
// Generate NPM Audit command
|
|
31
|
+
var auditCommand = [
|
|
32
|
+
'npm audit',
|
|
33
|
+
// flags
|
|
34
|
+
(0, lodash_get_1.default)(options, 'production') ? getProductionOnlyOption() : '',
|
|
35
|
+
(0, lodash_get_1.default)(options, 'registry') ? "--registry=".concat(options.registry) : '',
|
|
36
|
+
]
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join(' ');
|
|
39
|
+
// Taking the audit level from the command or environment variable
|
|
40
|
+
var envVar = process.env.NPM_CONFIG_AUDIT_LEVEL;
|
|
41
|
+
var auditLevel = (0, lodash_get_1.default)(options, 'level', envVar) || 'info';
|
|
42
|
+
// Process filter table option
|
|
43
|
+
var filterLevel;
|
|
44
|
+
var filterTableOption = (0, lodash_get_1.default)(options, 'filterTable');
|
|
45
|
+
if (filterTableOption) {
|
|
46
|
+
if (typeof filterTableOption === 'string') {
|
|
47
|
+
// User provided a specific level for filtering
|
|
48
|
+
filterLevel = filterTableOption;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// User provided true flag, use the audit level
|
|
52
|
+
filterLevel = auditLevel;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Get the exceptions
|
|
56
|
+
var nsprc = (0, file_1.readFile)('.nsprc');
|
|
57
|
+
var cmdExceptions = (0, lodash_get_1.default)(options, 'exclude', '')
|
|
58
|
+
.split(',')
|
|
59
|
+
.map(function (each) { return each.trim(); })
|
|
60
|
+
.filter(function (each) { return each !== ''; });
|
|
61
|
+
var exceptionIds = (0, vulnerability_1.getExceptionsIds)(nsprc, cmdExceptions);
|
|
62
|
+
var cmdModuleIgnore = (0, lodash_get_1.default)(options, 'moduleIgnore', '').split(',');
|
|
63
|
+
var cmdIncludeColumns = (0, lodash_get_1.default)(options, 'includeColumns', '')
|
|
64
|
+
.split(',')
|
|
65
|
+
.map(function (each) { return each.trim(); })
|
|
66
|
+
.filter(function (each) { return !!each; });
|
|
67
|
+
fn(auditCommand, auditLevel, exceptionIds, cmdModuleIgnore, cmdIncludeColumns, filterLevel);
|
|
68
|
+
}
|
|
69
|
+
exports.default = handleInput;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getSeverityBgColor = exports.color = void 0;
|
|
7
|
+
var lodash_get_1 = __importDefault(require("lodash.get"));
|
|
8
|
+
var RESET = '\x1b[0m';
|
|
9
|
+
var COLORS = {
|
|
10
|
+
reset: {
|
|
11
|
+
fg: '\x1b[0m',
|
|
12
|
+
bg: '\x1b[0m',
|
|
13
|
+
},
|
|
14
|
+
black: {
|
|
15
|
+
fg: '\x1b[30m',
|
|
16
|
+
bg: '\x1b[40m',
|
|
17
|
+
},
|
|
18
|
+
red: {
|
|
19
|
+
fg: '\x1b[31m',
|
|
20
|
+
bg: '\x1b[41m',
|
|
21
|
+
},
|
|
22
|
+
green: {
|
|
23
|
+
fg: '\x1b[32m',
|
|
24
|
+
bg: '\x1b[42m',
|
|
25
|
+
},
|
|
26
|
+
yellow: {
|
|
27
|
+
fg: '\x1b[33m',
|
|
28
|
+
bg: '\x1b[43m',
|
|
29
|
+
},
|
|
30
|
+
blue: {
|
|
31
|
+
fg: '\x1b[34m',
|
|
32
|
+
bg: '\x1b[44m',
|
|
33
|
+
},
|
|
34
|
+
magenta: {
|
|
35
|
+
fg: '\x1b[35m',
|
|
36
|
+
bg: '\x1b[45m',
|
|
37
|
+
},
|
|
38
|
+
cyan: {
|
|
39
|
+
fg: '\x1b[36m',
|
|
40
|
+
bg: '\x1b[46m',
|
|
41
|
+
},
|
|
42
|
+
white: {
|
|
43
|
+
fg: '\x1b[37m',
|
|
44
|
+
bg: '\x1b[47m',
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Color a console message's foreground and background
|
|
49
|
+
* @param {String} message Message
|
|
50
|
+
* @param {String} fgColor Foreground color
|
|
51
|
+
* @param {String} bgColor Background color
|
|
52
|
+
* @return {String} Message
|
|
53
|
+
*/
|
|
54
|
+
function color(message, fgColor, bgColor) {
|
|
55
|
+
if ('NO_COLOR' in process.env) {
|
|
56
|
+
return message;
|
|
57
|
+
}
|
|
58
|
+
return [
|
|
59
|
+
(0, lodash_get_1.default)(COLORS, "".concat(fgColor, ".fg"), ''),
|
|
60
|
+
(0, lodash_get_1.default)(COLORS, "".concat(bgColor, ".bg"), ''),
|
|
61
|
+
message,
|
|
62
|
+
RESET,
|
|
63
|
+
].join('');
|
|
64
|
+
}
|
|
65
|
+
exports.color = color;
|
|
66
|
+
/**
|
|
67
|
+
* Get background color based on severity
|
|
68
|
+
* @param {String} severity Vulnerability's severity
|
|
69
|
+
* @return {(String | undefined)} Background color or undefined
|
|
70
|
+
*/
|
|
71
|
+
function getSeverityBgColor(severity) {
|
|
72
|
+
switch (severity) {
|
|
73
|
+
case 'info':
|
|
74
|
+
return undefined;
|
|
75
|
+
case 'low':
|
|
76
|
+
return undefined;
|
|
77
|
+
case 'moderate':
|
|
78
|
+
return undefined;
|
|
79
|
+
case 'high':
|
|
80
|
+
return 'red';
|
|
81
|
+
case 'critical':
|
|
82
|
+
return 'red';
|
|
83
|
+
default: {
|
|
84
|
+
var exhaustiveCheck = severity;
|
|
85
|
+
return exhaustiveCheck;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.getSeverityBgColor = getSeverityBgColor;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shortenNodePath = exports.trimArray = exports.isJsonString = exports.isWholeNumber = void 0;
|
|
4
|
+
// TODO: This might be unused
|
|
5
|
+
/**
|
|
6
|
+
* @param {String | Number | Null | Boolean} value The input number
|
|
7
|
+
* @return {Boolean} Returns true if the input is a whole number
|
|
8
|
+
*/
|
|
9
|
+
function isWholeNumber(value) {
|
|
10
|
+
if (value === null || value === undefined) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
if (!Number(value)) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return Number(value) % 1 === 0;
|
|
17
|
+
}
|
|
18
|
+
exports.isWholeNumber = isWholeNumber;
|
|
19
|
+
/**
|
|
20
|
+
* @param {String} string The JSON stringified object
|
|
21
|
+
* @return {Boolean} Returns true if the input string is parse-able
|
|
22
|
+
*/
|
|
23
|
+
function isJsonString(string) {
|
|
24
|
+
try {
|
|
25
|
+
JSON.parse(string);
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
console.log('Failed parsing .nsprc file: ' + e);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
exports.isJsonString = isJsonString;
|
|
34
|
+
// TODO: Add unit tests
|
|
35
|
+
/**
|
|
36
|
+
* Trim array size to a maximum number
|
|
37
|
+
* @param {Array} array Array to trim
|
|
38
|
+
* @param {Number} maxLength Desired length
|
|
39
|
+
* @return {Array} Trimmed array with additional message
|
|
40
|
+
*/
|
|
41
|
+
function trimArray(array, maxLength) {
|
|
42
|
+
var originalLength = array.length;
|
|
43
|
+
var removedLength = Math.max(0, originalLength - maxLength);
|
|
44
|
+
if (removedLength === 0) {
|
|
45
|
+
return array;
|
|
46
|
+
}
|
|
47
|
+
array.length = maxLength;
|
|
48
|
+
return array.concat("...and ".concat(removedLength, " more"));
|
|
49
|
+
}
|
|
50
|
+
exports.trimArray = trimArray;
|
|
51
|
+
/**
|
|
52
|
+
* Shorten node path (node_modules/nodemon/node_modules/chokidar/node_modules/fsevents) to (nodemon>chokidar>fsevents)
|
|
53
|
+
* @param {String} path Full node path
|
|
54
|
+
* @return {String} Shorten Path
|
|
55
|
+
*/
|
|
56
|
+
function shortenNodePath(path) {
|
|
57
|
+
return path.replace('node_modules/', '').replace(/\/node_modules\//g, '>');
|
|
58
|
+
}
|
|
59
|
+
exports.shortenNodePath = shortenNodePath;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.analyzeExpiry = exports.isValidDate = void 0;
|
|
7
|
+
var dayjs_1 = __importDefault(require("dayjs"));
|
|
8
|
+
/**
|
|
9
|
+
* Validate if the given timestamp is a valid UNIX timestamp
|
|
10
|
+
* @param {Any} timestamp The given timestamp
|
|
11
|
+
* @return {Boolean} Returns true if it is a valid UNIX timestamp
|
|
12
|
+
*/
|
|
13
|
+
function isValidDate(timestamp) {
|
|
14
|
+
return new Date(timestamp).getTime() > 0;
|
|
15
|
+
}
|
|
16
|
+
exports.isValidDate = isValidDate;
|
|
17
|
+
/**
|
|
18
|
+
* Analyze the given date time if it has expired (in the past)
|
|
19
|
+
* @param {String | Number} expiry Expiry timestamp
|
|
20
|
+
* @param {String | Number} now The date to compare with
|
|
21
|
+
* @return {Object} Return the analysis
|
|
22
|
+
*/
|
|
23
|
+
function analyzeExpiry(expiry, now) {
|
|
24
|
+
if (now === void 0) { now = new Date().valueOf(); }
|
|
25
|
+
if (!expiry) {
|
|
26
|
+
return { valid: true };
|
|
27
|
+
}
|
|
28
|
+
if (!isValidDate(expiry) || !isValidDate(now)) {
|
|
29
|
+
return { valid: false };
|
|
30
|
+
}
|
|
31
|
+
var dayjsNow = (0, dayjs_1.default)(now);
|
|
32
|
+
return {
|
|
33
|
+
valid: true,
|
|
34
|
+
expired: dayjsNow.isAfter(expiry),
|
|
35
|
+
years: dayjsNow.diff(expiry, 'years'),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
exports.analyzeExpiry = analyzeExpiry;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readFile = void 0;
|
|
7
|
+
var fs_1 = __importDefault(require("fs"));
|
|
8
|
+
var common_1 = require("./common");
|
|
9
|
+
/**
|
|
10
|
+
* Read file from path
|
|
11
|
+
* @param {String} path File path
|
|
12
|
+
* @return {(Object | Boolean)} Returns the parsed data if found, or else returns `false`
|
|
13
|
+
*/
|
|
14
|
+
function readFile(path) {
|
|
15
|
+
try {
|
|
16
|
+
var data = fs_1.default.readFileSync(path, 'utf8');
|
|
17
|
+
if (!(0, common_1.isJsonString)(data)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return JSON.parse(data);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.readFile = readFile;
|
package/src/utils/npm.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNpmVersion = void 0;
|
|
4
|
+
var child_process_1 = require("child_process");
|
|
5
|
+
/**
|
|
6
|
+
* Get the current npm version
|
|
7
|
+
* @return {String} The npm version
|
|
8
|
+
*/
|
|
9
|
+
function getNpmVersion() {
|
|
10
|
+
var version = (0, child_process_1.execSync)('npm --version');
|
|
11
|
+
return version.toString();
|
|
12
|
+
}
|
|
13
|
+
exports.getNpmVersion = getNpmVersion;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
3
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
4
|
+
if (ar || !(i in from)) {
|
|
5
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
6
|
+
ar[i] = from[i];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.printExceptionReport = exports.printSecurityReport = exports.getColumnWidth = void 0;
|
|
16
|
+
var lodash_get_1 = __importDefault(require("lodash.get"));
|
|
17
|
+
var table_1 = require("table");
|
|
18
|
+
var SECURITY_REPORT_HEADER = ['ID', 'Module', 'Title', 'Paths', 'Severity', 'URL', 'Ex.', 'Fix'];
|
|
19
|
+
var EXCEPTION_REPORT_HEADER = ['ID', 'Status', 'Expiry', 'Notes'];
|
|
20
|
+
// TODO: Add unit tests
|
|
21
|
+
/**
|
|
22
|
+
* Get the column width size for the table
|
|
23
|
+
* @param {Array} tableData Table data (Array of array)
|
|
24
|
+
* @param {Number} columnIndex Target column index
|
|
25
|
+
* @param {Number} maxWidth Maximum width
|
|
26
|
+
* @param {Number} minWidth Minimum width
|
|
27
|
+
* @return {Number} width
|
|
28
|
+
*/
|
|
29
|
+
function getColumnWidth(tableData, columnIndex, maxWidth, minWidth) {
|
|
30
|
+
if (maxWidth === void 0) { maxWidth = 50; }
|
|
31
|
+
if (minWidth === void 0) { minWidth = 15; }
|
|
32
|
+
// Find the maximum length in the column
|
|
33
|
+
var contentLength = tableData.reduce(function (max, cur) {
|
|
34
|
+
var content = JSON.stringify((0, lodash_get_1.default)(cur, columnIndex, ''));
|
|
35
|
+
// Remove the color codes
|
|
36
|
+
content = content.replace(/\\x1b\[\d{1,2}m/g, '');
|
|
37
|
+
content = content.replace(/\\u001b\[\d{1,2}m/g, '');
|
|
38
|
+
content = content.replace(/"/g, '');
|
|
39
|
+
// Keep whichever number that is bigger
|
|
40
|
+
return content.length > max ? content.length : max;
|
|
41
|
+
},
|
|
42
|
+
// Start with minimum width (also auto handling empty column case)
|
|
43
|
+
minWidth);
|
|
44
|
+
// Return the content length up to a maximum point
|
|
45
|
+
return Math.min(contentLength, maxWidth);
|
|
46
|
+
}
|
|
47
|
+
exports.getColumnWidth = getColumnWidth;
|
|
48
|
+
/**
|
|
49
|
+
* Print the security report in a table format
|
|
50
|
+
* @param {Array} data Array of arrays
|
|
51
|
+
* @return {undefined} Returns void
|
|
52
|
+
* @param {Array} columnsToInclude List of columns to include in audit results
|
|
53
|
+
*/
|
|
54
|
+
function printSecurityReport(data, columnsToInclude) {
|
|
55
|
+
var configs = {
|
|
56
|
+
singleLine: true,
|
|
57
|
+
header: {
|
|
58
|
+
alignment: 'center',
|
|
59
|
+
content: '=== npm audit security report ===\n',
|
|
60
|
+
},
|
|
61
|
+
columns: {
|
|
62
|
+
// "Title" column index
|
|
63
|
+
2: {
|
|
64
|
+
width: getColumnWidth(data, 2),
|
|
65
|
+
wrapWord: true,
|
|
66
|
+
},
|
|
67
|
+
// "Paths" column index
|
|
68
|
+
3: {
|
|
69
|
+
width: getColumnWidth(data, 3),
|
|
70
|
+
wrapWord: true,
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
var headers = columnsToInclude.length ? SECURITY_REPORT_HEADER.filter(function (h) { return columnsToInclude.includes(h); }) : SECURITY_REPORT_HEADER;
|
|
75
|
+
console.info((0, table_1.table)(__spreadArray([headers], data, true), configs));
|
|
76
|
+
}
|
|
77
|
+
exports.printSecurityReport = printSecurityReport;
|
|
78
|
+
/**
|
|
79
|
+
* Print the exception report in a table format
|
|
80
|
+
* @param {Array} data Array of arrays
|
|
81
|
+
* @return {undefined} Returns void
|
|
82
|
+
*/
|
|
83
|
+
function printExceptionReport(data) {
|
|
84
|
+
var configs = {
|
|
85
|
+
singleLine: true,
|
|
86
|
+
header: {
|
|
87
|
+
alignment: 'center',
|
|
88
|
+
content: '=== list of exceptions ===\n',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
console.info((0, table_1.table)(__spreadArray([EXCEPTION_REPORT_HEADER], data, true), configs));
|
|
92
|
+
}
|
|
93
|
+
exports.printExceptionReport = printExceptionReport;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
3
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
4
|
+
if (ar || !(i in from)) {
|
|
5
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
6
|
+
ar[i] = from[i];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.handleUnusedExceptions = exports.processExceptions = exports.getExceptionsIds = exports.processAuditJson = exports.validateV7Vulnerability = exports.validateV6Vulnerability = exports.mapLevelToNumber = void 0;
|
|
16
|
+
var lodash_get_1 = __importDefault(require("lodash.get"));
|
|
17
|
+
var common_1 = require("./common");
|
|
18
|
+
var color_1 = require("./color");
|
|
19
|
+
var print_1 = require("./print");
|
|
20
|
+
var date_1 = require("./date");
|
|
21
|
+
var MAX_PATHS_SIZE = 5;
|
|
22
|
+
/**
|
|
23
|
+
* Converts an audit level to a numeric value
|
|
24
|
+
* @param {String} auditLevel Audit level
|
|
25
|
+
* @return {Number} Numeric level: the higher the number, the more severe it is
|
|
26
|
+
*/
|
|
27
|
+
function mapLevelToNumber(auditLevel) {
|
|
28
|
+
switch (auditLevel) {
|
|
29
|
+
case 'info':
|
|
30
|
+
return 0;
|
|
31
|
+
case 'low':
|
|
32
|
+
return 1;
|
|
33
|
+
case 'moderate':
|
|
34
|
+
return 2;
|
|
35
|
+
case 'high':
|
|
36
|
+
return 3;
|
|
37
|
+
case 'critical':
|
|
38
|
+
return 4;
|
|
39
|
+
default:
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.mapLevelToNumber = mapLevelToNumber;
|
|
44
|
+
/**
|
|
45
|
+
* Validate if the vulnerability should be excepted
|
|
46
|
+
* @param {Object} vulnerability NPM v6 audit report's vulnerability
|
|
47
|
+
* @param {Array} exceptionIds Exception IDs
|
|
48
|
+
* @return {Object} Validation result
|
|
49
|
+
*/
|
|
50
|
+
function validateV6Vulnerability(vulnerability, exceptionIds) {
|
|
51
|
+
return exceptionIds.reduce(function (acc, id) {
|
|
52
|
+
// check if ID matches
|
|
53
|
+
if (id === String(vulnerability.id)) {
|
|
54
|
+
return { isExcepted: true, usedExceptionKey: id };
|
|
55
|
+
}
|
|
56
|
+
// check if any of the CVEs matches
|
|
57
|
+
if (Array.isArray(vulnerability.cves) && vulnerability.cves.includes(id)) {
|
|
58
|
+
return { isExcepted: true, usedExceptionKey: id };
|
|
59
|
+
}
|
|
60
|
+
// check if the CWE matches
|
|
61
|
+
if (vulnerability.cwe === id) {
|
|
62
|
+
return { isExcepted: true, usedExceptionKey: id };
|
|
63
|
+
}
|
|
64
|
+
// check if the URL matches
|
|
65
|
+
if (vulnerability.url && vulnerability.url.includes(id)) {
|
|
66
|
+
return { isExcepted: true, usedExceptionKey: id };
|
|
67
|
+
}
|
|
68
|
+
return acc;
|
|
69
|
+
}, {
|
|
70
|
+
isExcepted: false,
|
|
71
|
+
usedExceptionKey: '',
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
exports.validateV6Vulnerability = validateV6Vulnerability;
|
|
75
|
+
/**
|
|
76
|
+
* Validate if the vulnerability should be excepted
|
|
77
|
+
* @param {Object} vulnerability NPM v7 audit report's vulnerability
|
|
78
|
+
* @param {Array} exceptionIds Exception IDs
|
|
79
|
+
* @return {Object} Validation result
|
|
80
|
+
*/
|
|
81
|
+
function validateV7Vulnerability(vulnerability, exceptionIds) {
|
|
82
|
+
return exceptionIds.reduce(function (acc, id) {
|
|
83
|
+
// check if ID matches
|
|
84
|
+
if (id === String(vulnerability.source)) {
|
|
85
|
+
return { isExcepted: true, usedExceptionKey: id };
|
|
86
|
+
}
|
|
87
|
+
// check if the URL matches
|
|
88
|
+
if (vulnerability.url && vulnerability.url.includes(id)) {
|
|
89
|
+
return { isExcepted: true, usedExceptionKey: id };
|
|
90
|
+
}
|
|
91
|
+
return acc;
|
|
92
|
+
}, {
|
|
93
|
+
isExcepted: false,
|
|
94
|
+
usedExceptionKey: '',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
exports.validateV7Vulnerability = validateV7Vulnerability;
|
|
98
|
+
/**
|
|
99
|
+
* Analyze the JSON string buffer
|
|
100
|
+
* @param {String} jsonBuffer NPM Audit JSON string buffer
|
|
101
|
+
* @param {String} auditLevel User's target audit level
|
|
102
|
+
* @param {Array} exceptionIds Exception IDs (ID to be ignored)
|
|
103
|
+
* @param {Array} exceptionModules Exception modules (modules to be ignored)
|
|
104
|
+
* @param {Array} columnsToInclude List of columns to include in audit results
|
|
105
|
+
* @param {String} filterLevel Optional level to filter table display (if not provided, shows all vulnerabilities)
|
|
106
|
+
* @return {Object} Processed vulnerabilities details
|
|
107
|
+
*/
|
|
108
|
+
function processAuditJson(jsonBuffer, auditLevel, exceptionIds, exceptionModules, columnsToInclude, filterLevel) {
|
|
109
|
+
if (jsonBuffer === void 0) { jsonBuffer = ''; }
|
|
110
|
+
if (auditLevel === void 0) { auditLevel = 'info'; }
|
|
111
|
+
if (exceptionIds === void 0) { exceptionIds = []; }
|
|
112
|
+
if (exceptionModules === void 0) { exceptionModules = []; }
|
|
113
|
+
if (columnsToInclude === void 0) { columnsToInclude = []; }
|
|
114
|
+
if (!(0, common_1.isJsonString)(jsonBuffer)) {
|
|
115
|
+
return {
|
|
116
|
+
unhandledIds: [],
|
|
117
|
+
vulnerabilityIds: [],
|
|
118
|
+
vulnerabilityModules: [],
|
|
119
|
+
unusedExceptionIds: exceptionIds,
|
|
120
|
+
unusedExceptionModules: exceptionModules,
|
|
121
|
+
report: [],
|
|
122
|
+
failed: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
// NPM v6 uses `advisories`
|
|
126
|
+
// NPM v7 uses `vulnerabilities`
|
|
127
|
+
// Refer to the `test/__mocks__` folder for some sample mockups
|
|
128
|
+
var _a = JSON.parse(jsonBuffer), advisories = _a.advisories, vulnerabilities = _a.vulnerabilities;
|
|
129
|
+
// NPM v6 handling
|
|
130
|
+
if (advisories) {
|
|
131
|
+
return Object.values(advisories).reduce(function (acc, cur) {
|
|
132
|
+
var shouldAudit = mapLevelToNumber(cur.severity) >= mapLevelToNumber(auditLevel);
|
|
133
|
+
var _a = validateV6Vulnerability(cur, exceptionIds), isIdExcepted = _a.isExcepted, usedExceptionKey = _a.usedExceptionKey;
|
|
134
|
+
var isModuleExcepted = exceptionModules.includes(cur.module_name);
|
|
135
|
+
var isExcepted = isIdExcepted || isModuleExcepted;
|
|
136
|
+
// Record used exception ID/module
|
|
137
|
+
if (isIdExcepted) {
|
|
138
|
+
acc.unusedExceptionIds = acc.unusedExceptionIds.filter(function (id) { return id !== usedExceptionKey; });
|
|
139
|
+
}
|
|
140
|
+
if (isModuleExcepted) {
|
|
141
|
+
acc.unusedExceptionModules = acc.unusedExceptionModules.filter(function (module) { return module !== cur.module_name; });
|
|
142
|
+
}
|
|
143
|
+
var rowData = [
|
|
144
|
+
{ key: 'ID', value: cur.id.toString() },
|
|
145
|
+
{ key: 'Module', value: cur.module_name },
|
|
146
|
+
{ key: 'Title', value: cur.title },
|
|
147
|
+
{
|
|
148
|
+
key: 'Paths',
|
|
149
|
+
value: (0, common_1.trimArray)(cur.findings.reduce(function (a, c) { return __spreadArray(__spreadArray([], a, true), c.paths, true); }, []), MAX_PATHS_SIZE).join('\n'),
|
|
150
|
+
},
|
|
151
|
+
{ key: 'Severity', value: cur.severity },
|
|
152
|
+
{ key: 'URL', value: cur.url },
|
|
153
|
+
{ key: 'Ex.', value: isExcepted ? 'y' : 'n' },
|
|
154
|
+
{ key: 'Fix', value: 'N/A' },
|
|
155
|
+
]
|
|
156
|
+
.filter(function (_a) {
|
|
157
|
+
var key = _a.key;
|
|
158
|
+
return (columnsToInclude.length ? columnsToInclude.includes(key) : true);
|
|
159
|
+
})
|
|
160
|
+
.map(function (_a) {
|
|
161
|
+
var key = _a.key, value = _a.value;
|
|
162
|
+
return (0, color_1.color)(value, isExcepted ? '' : 'yellow', key === 'Severity' ? (0, color_1.getSeverityBgColor)(cur.severity) : undefined);
|
|
163
|
+
});
|
|
164
|
+
// Record this vulnerability into the report based on filter level
|
|
165
|
+
var shouldIncludeInTable = filterLevel ? mapLevelToNumber(cur.severity) >= mapLevelToNumber(filterLevel) : true;
|
|
166
|
+
if (shouldIncludeInTable) {
|
|
167
|
+
acc.report.push(rowData);
|
|
168
|
+
}
|
|
169
|
+
acc.vulnerabilityIds.push(cur.id.toString());
|
|
170
|
+
if (!acc.vulnerabilityModules.includes(cur.module_name)) {
|
|
171
|
+
acc.vulnerabilityModules.push(cur.module_name);
|
|
172
|
+
}
|
|
173
|
+
// Found unhandled vulnerabilities
|
|
174
|
+
if (shouldAudit && !isExcepted) {
|
|
175
|
+
acc.unhandledIds.push(cur.id.toString());
|
|
176
|
+
}
|
|
177
|
+
return acc;
|
|
178
|
+
}, {
|
|
179
|
+
unhandledIds: [],
|
|
180
|
+
vulnerabilityIds: [],
|
|
181
|
+
vulnerabilityModules: [],
|
|
182
|
+
unusedExceptionIds: exceptionIds,
|
|
183
|
+
unusedExceptionModules: exceptionModules,
|
|
184
|
+
report: [],
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
// NPM v7 handling
|
|
188
|
+
if (vulnerabilities) {
|
|
189
|
+
return Object.values(vulnerabilities).reduce(function (acc, cur) {
|
|
190
|
+
// Inside `via` array, its either the related module name or the vulnerability source object.
|
|
191
|
+
(0, lodash_get_1.default)(cur, 'via', []).forEach(function (vul) {
|
|
192
|
+
// The vulnerability ID is labeled as `source`
|
|
193
|
+
var id = (0, lodash_get_1.default)(vul, 'source');
|
|
194
|
+
var moduleName = (0, lodash_get_1.default)(vul, 'name', '');
|
|
195
|
+
// Let's skip if ID is a string (module name), and only focus on the root vulnerabilities
|
|
196
|
+
if (!id || typeof id === 'string' || typeof vul === 'string') {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
var shouldAudit = mapLevelToNumber(vul.severity) >= mapLevelToNumber(auditLevel);
|
|
200
|
+
var _a = validateV7Vulnerability(vul, exceptionIds), isIdExcepted = _a.isExcepted, usedExceptionKey = _a.usedExceptionKey;
|
|
201
|
+
var isModuleExcepted = exceptionModules.includes(moduleName);
|
|
202
|
+
var isExcepted = isIdExcepted || isModuleExcepted;
|
|
203
|
+
// Record used exception ID/module
|
|
204
|
+
if (isIdExcepted) {
|
|
205
|
+
acc.unusedExceptionIds = acc.unusedExceptionIds.filter(function (id) { return id !== usedExceptionKey; });
|
|
206
|
+
}
|
|
207
|
+
if (isModuleExcepted) {
|
|
208
|
+
acc.unusedExceptionModules = acc.unusedExceptionModules.filter(function (module) { return module !== moduleName; });
|
|
209
|
+
}
|
|
210
|
+
var rowData = [
|
|
211
|
+
{ key: 'ID', value: String(id) },
|
|
212
|
+
{ key: 'Module', value: vul.name },
|
|
213
|
+
{ key: 'Title', value: vul.title },
|
|
214
|
+
{ key: 'Paths', value: (0, common_1.trimArray)((0, lodash_get_1.default)(cur, 'nodes', []).map(common_1.shortenNodePath), MAX_PATHS_SIZE).join('\n') },
|
|
215
|
+
{ key: 'Severity', value: vul.severity, bgColor: (0, color_1.getSeverityBgColor)(vul.severity) },
|
|
216
|
+
{ key: 'URL', value: vul.url },
|
|
217
|
+
{ key: 'Ex.', value: isExcepted ? 'y' : 'n' },
|
|
218
|
+
{
|
|
219
|
+
key: 'Fix',
|
|
220
|
+
value: (function () {
|
|
221
|
+
var fixAvailable = (0, lodash_get_1.default)(cur, 'fixAvailable');
|
|
222
|
+
if (typeof fixAvailable === 'object' && fixAvailable !== null && fixAvailable.isSemVerMajor) {
|
|
223
|
+
return 'major';
|
|
224
|
+
}
|
|
225
|
+
return String(Boolean(fixAvailable));
|
|
226
|
+
})(),
|
|
227
|
+
},
|
|
228
|
+
]
|
|
229
|
+
.filter(function (_a) {
|
|
230
|
+
var key = _a.key;
|
|
231
|
+
return (columnsToInclude.length ? columnsToInclude.includes(key) : true);
|
|
232
|
+
})
|
|
233
|
+
.map(function (_a) {
|
|
234
|
+
var key = _a.key, value = _a.value, bgColor = _a.bgColor;
|
|
235
|
+
return (0, color_1.color)(value, isExcepted ? '' : 'yellow', key === 'Severity' ? bgColor : undefined);
|
|
236
|
+
});
|
|
237
|
+
// Record this vulnerability into the report based on filter level
|
|
238
|
+
var shouldIncludeInTable = filterLevel ? mapLevelToNumber(vul.severity) >= mapLevelToNumber(filterLevel) : true;
|
|
239
|
+
if (shouldIncludeInTable) {
|
|
240
|
+
acc.report.push(rowData);
|
|
241
|
+
}
|
|
242
|
+
acc.vulnerabilityIds.push(String(id));
|
|
243
|
+
if (!acc.vulnerabilityModules.includes(moduleName)) {
|
|
244
|
+
acc.vulnerabilityModules.push(moduleName);
|
|
245
|
+
}
|
|
246
|
+
// Found unhandled vulnerabilities
|
|
247
|
+
if (shouldAudit && !isExcepted) {
|
|
248
|
+
acc.unhandledIds.push(String(id));
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
return acc;
|
|
252
|
+
}, {
|
|
253
|
+
unhandledIds: [],
|
|
254
|
+
vulnerabilityIds: [],
|
|
255
|
+
vulnerabilityModules: [],
|
|
256
|
+
unusedExceptionIds: exceptionIds,
|
|
257
|
+
unusedExceptionModules: exceptionModules,
|
|
258
|
+
report: [],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
unhandledIds: [],
|
|
263
|
+
vulnerabilityIds: [],
|
|
264
|
+
vulnerabilityModules: [],
|
|
265
|
+
unusedExceptionIds: exceptionIds,
|
|
266
|
+
unusedExceptionModules: exceptionModules,
|
|
267
|
+
report: [],
|
|
268
|
+
failed: true,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
exports.processAuditJson = processAuditJson;
|
|
272
|
+
/**
|
|
273
|
+
* Process all exceptions and return a list of exception IDs
|
|
274
|
+
* @param {Object | Boolean} nsprc File content from `.nsprc`
|
|
275
|
+
* @param {Array} cmdExceptions Exceptions passed in via command line
|
|
276
|
+
* @return {Array} List of found vulnerabilities
|
|
277
|
+
*/
|
|
278
|
+
function getExceptionsIds(nsprc, cmdExceptions) {
|
|
279
|
+
if (cmdExceptions === void 0) { cmdExceptions = []; }
|
|
280
|
+
// If file does not exists
|
|
281
|
+
if (!nsprc || typeof nsprc !== 'object') {
|
|
282
|
+
// If there are exceptions passed in from command line
|
|
283
|
+
if (cmdExceptions.length) {
|
|
284
|
+
// Display simple info
|
|
285
|
+
console.info("Exception IDs: ".concat(cmdExceptions.join(', ')));
|
|
286
|
+
return cmdExceptions;
|
|
287
|
+
}
|
|
288
|
+
return [];
|
|
289
|
+
}
|
|
290
|
+
// Process the content of the file along with the command line exceptions
|
|
291
|
+
var _a = processExceptions(nsprc, cmdExceptions), exceptionIds = _a.exceptionIds, report = _a.report;
|
|
292
|
+
(0, print_1.printExceptionReport)(report);
|
|
293
|
+
return exceptionIds;
|
|
294
|
+
}
|
|
295
|
+
exports.getExceptionsIds = getExceptionsIds;
|
|
296
|
+
/**
|
|
297
|
+
* Filter the given list in the `.nsprc` file for valid exceptions
|
|
298
|
+
* @param {Object} nsprc The nsprc file content, contains exception info
|
|
299
|
+
* @param {Array} cmdExceptions Exceptions passed in via command line
|
|
300
|
+
* @return {Object} Processed vulnerabilities details
|
|
301
|
+
*/
|
|
302
|
+
function processExceptions(nsprc, cmdExceptions) {
|
|
303
|
+
if (cmdExceptions === void 0) { cmdExceptions = []; }
|
|
304
|
+
return Object.entries(nsprc).reduce(function (acc, _a) {
|
|
305
|
+
var id = _a[0], details = _a[1];
|
|
306
|
+
var isActive = Boolean((0, lodash_get_1.default)(details, 'active', true)); // default to true
|
|
307
|
+
var notes = typeof details === 'string' ? details : (0, lodash_get_1.default)(details, 'notes', '');
|
|
308
|
+
var _b = (0, date_1.analyzeExpiry)((0, lodash_get_1.default)(details, 'expiry')), valid = _b.valid, expired = _b.expired, years = _b.years;
|
|
309
|
+
// Color the status accordingly
|
|
310
|
+
var status = (0, color_1.color)('active', 'green');
|
|
311
|
+
if (expired) {
|
|
312
|
+
status = (0, color_1.color)('expired', 'red');
|
|
313
|
+
}
|
|
314
|
+
else if (!valid) {
|
|
315
|
+
status = (0, color_1.color)('invalid', 'red');
|
|
316
|
+
}
|
|
317
|
+
else if (!isActive) {
|
|
318
|
+
status = (0, color_1.color)('inactive', 'yellow');
|
|
319
|
+
}
|
|
320
|
+
// Color the date accordingly
|
|
321
|
+
var expiry = (0, lodash_get_1.default)(details, 'expiry');
|
|
322
|
+
var expiryDate = expiry !== undefined && expiry !== null ? new Date(expiry).toUTCString() : '';
|
|
323
|
+
// If it was expired for more than 5 years ago, warn by coloring the date in red
|
|
324
|
+
if (years && years <= -5) {
|
|
325
|
+
expiryDate = (0, color_1.color)(expiryDate, 'red');
|
|
326
|
+
}
|
|
327
|
+
else if (years && years <= -1) {
|
|
328
|
+
expiryDate = (0, color_1.color)(expiryDate, 'yellow');
|
|
329
|
+
}
|
|
330
|
+
acc.report.push([id, status, expiryDate, notes]);
|
|
331
|
+
if (isActive && !expired) {
|
|
332
|
+
acc.exceptionIds.push(id);
|
|
333
|
+
}
|
|
334
|
+
return acc;
|
|
335
|
+
}, {
|
|
336
|
+
exceptionIds: cmdExceptions,
|
|
337
|
+
report: cmdExceptions.map(function (id) { return [String(id), (0, color_1.color)('active', 'green'), '', '']; }),
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
exports.processExceptions = processExceptions;
|
|
341
|
+
/**
|
|
342
|
+
* Handle unused exceptions from user: console log them
|
|
343
|
+
* @param {Array} unusedExceptionIds List of unused exception IDs
|
|
344
|
+
* @param {Array} unusedExceptionModules List of unused exception module names
|
|
345
|
+
*/
|
|
346
|
+
function handleUnusedExceptions(unusedExceptionIds, unusedExceptionModules) {
|
|
347
|
+
var cleanedUnusedExceptionIds = unusedExceptionIds.filter(Boolean);
|
|
348
|
+
var cleanedUnusedExceptionModules = unusedExceptionModules.filter(Boolean);
|
|
349
|
+
var message = [
|
|
350
|
+
cleanedUnusedExceptionIds.length &&
|
|
351
|
+
"".concat(cleanedUnusedExceptionIds.length, " of the excluded vulnerabilities did not match any of the found vulnerabilities: ").concat(cleanedUnusedExceptionIds.join(', '), "."),
|
|
352
|
+
cleanedUnusedExceptionIds.length &&
|
|
353
|
+
"".concat(cleanedUnusedExceptionIds.length > 1 ? 'They' : 'It', " can be removed from the .nsprc file or --exclude -x flags."),
|
|
354
|
+
cleanedUnusedExceptionModules.length &&
|
|
355
|
+
"".concat(cleanedUnusedExceptionModules.length, " of the ignored modules did not match any of the found vulnerabilities: ").concat(cleanedUnusedExceptionModules.join(', '), "."),
|
|
356
|
+
cleanedUnusedExceptionModules.length &&
|
|
357
|
+
"".concat(cleanedUnusedExceptionModules.length > 1 ? 'They' : 'It', " can be removed from the --module-ignore -m flags."),
|
|
358
|
+
]
|
|
359
|
+
.filter(Boolean)
|
|
360
|
+
.join(' ');
|
|
361
|
+
if (message) {
|
|
362
|
+
console.warn(message);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
exports.handleUnusedExceptions = handleUnusedExceptions;
|