UNPKG

7.92 kBPlain TextView Raw
1#!/usr/bin/env node
2/*jshint esversion: 6 */
3
4var utils = require('../lib/utils'),
5 program = require('commander'),
6 retire = require('../lib/retire'),
7 repo = require('../lib/repo'),
8 resolve = require('../lib/resolve'),
9 scanner = require('../lib/scanner'),
10 reporting = require('../lib/reporting'),
11 forward = require('../lib/utils').forwardEvent,
12 os = require('os'),
13 path = require('path'),
14 fs = require('fs'),
15 colors = require('colors/safe'),
16 emitter = new require('events').EventEmitter;
17
18var events = new emitter();
19var jsRepo = null;
20var bowerRepo = null;
21var nodeRepo = null;
22var vulnsFound = false;
23var failProcess = false;
24var defaultIgnoreFiles = ['.retireignore', '.retireignore.json'];
25var finalResults = [];
26
27var severityLevels = {
28 none: 0,
29 low: 1,
30 medium: 2,
31 high: 3,
32 critical: 4
33};
34
35colors.setTheme({
36 warn: 'red'
37});
38
39
40/*
41 * Parse command line flags.
42 */
43program
44 .version(retire.version)
45 .option('')
46 .option('-p, --package', 'limit node scan to packages where parent is mentioned in package.json (ignore node_modules)')
47 .option('-n, --node', 'Run node dependency scan only')
48 .option('-j, --js', 'Run scan of JavaScript files only')
49 .option('-v, --verbose', 'Show identified files (by default only vulnerable files are shown)')
50 .option('-x, --dropexternal', "Don't include project provided vulnerability repository")
51 .option('-c, --nocache', "Don't use local cache")
52 .option('')
53 .option('--jspath <path>', 'Folder to scan for javascript files')
54 .option('--nodepath <path>', 'Folder to scan for node files')
55 .option('--path <path>', 'Folder to scan for both')
56 .option('--jsrepo <path|url>', 'Local or internal version of repo')
57 .option('--noderepo <path|url>', 'Local or internal version of repo')
58 .option('--cachedir <path>', 'Path to use for local cache instead of /tmp/.retire-cache')
59 .option('--proxy <url>', 'Proxy url (http://some.sever:8080)')
60 .option('--outputformat <format>', 'Valid formats: text, json, jsonsimple, depcheck (experimental) and cyclonedx')
61 .option('--outputpath <path>', 'File to which output should be written')
62 .option('--ignore <paths>', 'Comma delimited list of paths to ignore')
63 .option('--ignorefile <path>', 'Custom ignore file, defaults to .retireignore / .retireignore.json')
64 .option('--severity <level>', 'Specify the bug severity level from which the process fails. Allowed levels none, low, medium, high, critical. Default: none')
65 .option('--exitwith <code>', 'Custom exit code (default: 13) when vulnerabilities are found')
66 .option('--colors', 'Enable color output (console output only)')
67 .parse(process.argv);
68
69var config = utils.extend({ path: '.' }, utils.pick(program, [
70 'package', 'node', 'js', 'jspath', 'verbose', 'nodepath', 'path', 'jsrepo', 'noderepo',
71 'dropexternal', 'nocache', 'proxy', 'ignore', 'ignorefile', 'outputformat', 'outputpath',
72 'severity', 'exitwith', 'colors', 'includemeta', 'cachedir'
73]));
74
75if (!config.nocache && !config.cachedir) {
76 config.cachedir = path.resolve(os.tmpdir(), '.retire-cache/');
77}
78
79config.ignore = config.ignore ? utils.map(config.ignore.split(','), function(e) { return path.resolve(e); }) : [];
80config.ignore = { paths : config.ignore, descriptors: [] };
81config.colorwarn = config.colors ? colors.warn : x => x;
82
83if (!config.ignorefile) {
84 config.ignorefile = defaultIgnoreFiles.filter(function(x){ return fs.existsSync(x); })[0];
85}
86var log = reporting.open(config);
87config.log = log;
88log.info("retire.js v" + retire.version);
89
90function exitWithError(msg) {
91 log.error(config.colorwarn(msg));
92 process.exitCode = 1;
93 log.close();
94}
95
96
97if(!config.severity) {
98 config.severity = 'none';
99} else if (!severityLevels.hasOwnProperty(config.severity)) {
100 exitWithError('Error: Invalid severity level (' + config.severity + '). Valid levels are: ' + Object.keys(severityLevels).join(', '));
101}
102
103if(config.ignorefile) {
104 if (!fs.existsSync(config.ignorefile)) {
105 exitWithError('Error: Could not read ignore file: ' + config.ignorefile);
106 }
107 if (config.ignorefile.substr(-5) === ".json") {
108 try {
109 var ignored = JSON.parse(fs.readFileSync(config.ignorefile).toString());
110 } catch(e) {
111 exitWithError('Error: Invalid ignore file: ' + config.ignorefile, e);
112 }
113 config.ignore.descriptors = ignored;
114 var ignoredPaths = ignored
115 .map(function(x) { return x.path; })
116 .filter(function(x) { return x; });
117 config.ignore.paths = config.ignore.paths.concat(ignoredPaths);
118 } else {
119 var lines = fs.readFileSync(config.ignorefile).toString().split(/\r\n|\n/g).filter(function(e) { return e !== ''; });
120 ignored = utils.map(lines, function(e) { return e[0] === '@' ? e.slice(1) : path.resolve(e); });
121 config.ignore.paths = config.ignore.paths.concat(ignored);
122 }
123}
124
125scanner.on('vulnerable-dependency-found', function(result) {
126 vulnsFound = true;
127 var levels = result.results
128 .map(function(r) {
129 return r.vulnerabilities ? r.vulnerabilities.map(function(v) {
130 return severityLevels[v.severity || 'critical'];
131 }) : []; });
132 var severity = utils.flatten(levels).reduce(function(x,y) { return x > y ? x : y; });
133 if(severity >= severityLevels[config.severity]) {
134 failProcess = true;
135 }
136});
137
138scanner.on('vulnerable-dependency-found', log.logVulnerableDependency);
139scanner.on('dependency-found', log.logDependency);
140
141
142events.on('load-js-repo', function() {
143 (config.jsrepo ?
144 (config.jsrepo.match(/^https?:\/\//) ?
145 repo.loadrepository(config.jsrepo, config)
146 : repo.loadrepositoryFromFile(config.jsrepo, config))
147 : repo.loadrepository('https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository.json', config)
148 ).on('stop', forward(events, 'stop'))
149 .on('done', function(repo) {
150 jsRepo = repo;
151 events.emit('js-repo-loaded');
152 });
153});
154
155
156events.on('load-node-repo', function() {
157 (config.noderepo ?
158 (config.noderepo.match(/^https?:\/\//) ?
159 repo.loadrepository(config.noderepo, config)
160 : repo.loadrepositoryFromFile(config.noderepo, config))
161 : repo.loadrepository('https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/npmrepository.json', config)
162 ).on('done', function(repo) {
163 nodeRepo = repo;
164 events.emit('node-repo-loaded');
165 }).on('stop', forward(events, 'stop'));
166});
167
168events.on('js-repo-loaded', function() {
169 events.emit(config.js ? 'scan-js' : 'load-node-repo');
170});
171
172events.on('node-repo-loaded', function() {
173 events.emit(config.node ? 'scan-node' : 'scan-js');
174});
175
176
177events.on('scan-js', function() {
178 resolve.scanJsFiles(config.jspath || config.path)
179 .on('jsfile', function(file) {
180 scanner.scanJsFile(file, jsRepo, config);
181 })
182 .on('bowerfile', function(bowerfile) {
183 bowerRepo = bowerRepo || repo.asbowerrepo(jsRepo);
184 scanner.scanBowerFile(bowerfile, bowerRepo, config);
185 })
186 .on('end', function() {
187 events.emit('js-scanned');
188 });
189});
190
191events.on('scan-node', function() {
192 resolve.getNodeDependencies(config.nodepath || config.path, config.package).on('done', function(dependencies) {
193 scanner.scanDependencies(dependencies, nodeRepo, config);
194 events.emit('scan-done');
195 }).on('error', function(err) {
196 console.warn("ERROR: " + err);
197 process.exit(1);
198 });
199});
200
201events.on('js-scanned', function() {
202 events.emit(!config.js ? 'scan-node' : 'scan-done');
203});
204
205events.on('scan-done', function() {
206 process.exitCode = failProcess ? (config.exitwith || 13) : 0;
207 log.close();
208});
209
210
211process.on('uncaughtException', function (err) {
212 console.warn('Exception caught: ', arguments);
213 console.warn(err.stack);
214 process.exit(1);
215});
216
217events.on('stop', function() {
218 exitWithError.apply(null, arguments);
219});
220
221if (config.node) {
222 events.emit('load-node-repo');
223} else {
224 events.emit('load-js-repo');
225}