1 | /**
|
2 | * Copyright 2020 F5 Networks, Inc.
|
3 | *
|
4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | * you may not use this file except in compliance with the License.
|
6 | * You may obtain a copy of the License at
|
7 | *
|
8 | * http://www.apache.org/licenses/LICENSE-2.0
|
9 | *
|
10 | * Unless required by applicable law or agreed to in writing, software
|
11 | * distributed under the License is distributed on an "AS IS" BASIS,
|
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | * See the License for the specific language governing permissions and
|
14 | * limitations under the License.
|
15 | *
|
16 | *
|
17 | * Note: This is a light wrapper around "npm audit" to support features such as:
|
18 | * - Whitelisting specific vulnerabilities (typically until fix is released in downstream package)
|
19 | * - Providing options in package.json inside "auditProcessor" property
|
20 | *
|
21 | * Usage: node auditProcessor.js --help
|
22 | */
|
23 |
|
24 | ;
|
25 |
|
26 | const fs = require('fs');
|
27 | const path = require('path');
|
28 | const yargs = require('yargs'); // eslint-disable-line import/no-extraneous-dependencies
|
29 |
|
30 | const PACKAGE_JSON = path.join(process.cwd(), 'package.json');
|
31 | const AUDIT_REPORT = path.join(process.cwd(), '.auditReport.json');
|
32 | const DEFAULT_EXIT_CODE = 0;
|
33 |
|
34 |
|
35 | class AuditProcessor {
|
36 | constructor() {
|
37 | this.report = {};
|
38 | this.vulnerabilities = [];
|
39 | this.exitCode = DEFAULT_EXIT_CODE;
|
40 | }
|
41 |
|
42 | log(msg) { // eslint-disable-line class-methods-use-this
|
43 | console.log(msg); // eslint-disable-line no-console
|
44 | }
|
45 |
|
46 | /**
|
47 | * Load report - Loads "npm audit --json" output
|
48 | *
|
49 | * @returns {Void}
|
50 | */
|
51 | loadReport() {
|
52 | if (!fs.existsSync(AUDIT_REPORT)) {
|
53 | throw new Error('Please run "npm audit" first.');
|
54 | }
|
55 | this.report = JSON.parse(fs.readFileSync(AUDIT_REPORT, 'utf-8'));
|
56 | }
|
57 |
|
58 | /**
|
59 | * Process report
|
60 | *
|
61 | * @param {Object} options - function options
|
62 | * @param {Array} [options.whitelist] - array containing zero or more ID's to ignore
|
63 | *
|
64 | * @returns {Void}
|
65 | */
|
66 | processReport(options) {
|
67 | options = options || {}; // eslint-disable-line no-param-reassign
|
68 | const whitelist = options.whitelist || [];
|
69 |
|
70 | // parse out vulnerabilities
|
71 | this.report.actions.forEach((action) => {
|
72 | action.resolves.forEach((item) => {
|
73 | this.vulnerabilities.push({
|
74 | module: action.module,
|
75 | path: item.path,
|
76 | vulnerability: {
|
77 | id: item.id,
|
78 | url: this.report.advisories[item.id].url,
|
79 | recommendation: this.report.advisories[item.id].recommendation
|
80 | }
|
81 | });
|
82 | });
|
83 | });
|
84 | // determine if any vulnerabilities should be ignored
|
85 | if (whitelist.length) {
|
86 | this.vulnerabilities = this.vulnerabilities.filter(vuln =>
|
87 | !whitelist.includes(vuln.vulnerability.id)); // eslint-disable-line arrow-body-style
|
88 | }
|
89 | }
|
90 |
|
91 | /**
|
92 | * Notify - Determine exit code, what should be logged
|
93 | *
|
94 | * @returns {Void}
|
95 | */
|
96 | notify() {
|
97 | // check for vulnerabilities and act accordingly
|
98 | if (this.vulnerabilities.length) {
|
99 | this.log(this.vulnerabilities);
|
100 | this.log(`IMPORTANT: ${this.vulnerabilities.length} vulnerabilities exist, please resolve them!`);
|
101 | process.exit(1);
|
102 | }
|
103 | // good to go
|
104 | this.log('No dependency vulnerabilities exist!');
|
105 | process.exit(this.exitCode);
|
106 | }
|
107 | }
|
108 |
|
109 | function main() {
|
110 | const argv = yargs
|
111 | .version('1.0.0')
|
112 | .command('whitelist', 'Whitelist specific vulnerabilities by ID')
|
113 | .example('$0 --whitelist 1234,1235', 'Whitelist vulnerabilities 1234 and 1235')
|
114 | .help('help')
|
115 | .argv;
|
116 |
|
117 | const optionsFromConfig = JSON.parse(fs.readFileSync(PACKAGE_JSON, 'utf-8')).auditProcessor;
|
118 | const parsedArgs = {
|
119 | whitelist: argv.whitelist || optionsFromConfig.whitelist || ''
|
120 | };
|
121 |
|
122 | const auditProcessor = new AuditProcessor();
|
123 | auditProcessor.loadReport();
|
124 | auditProcessor.processReport({
|
125 | whitelist: parsedArgs.whitelist.toString()
|
126 | .split(',')
|
127 | .map(item => parseInt(item, 10)) // eslint-disable-line arrow-body-style
|
128 | });
|
129 | auditProcessor.notify();
|
130 | }
|
131 |
|
132 | main();
|