retire 3.0.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ ## [3.0.1]
4
+
5
+ ### Dependency update
6
+ - always output JSON to stdout, to avoid conflict with deprecation warning
7
+
8
+ ## [3.0.1]
9
+
10
+ ### Dependency update
11
+ - glob-parent, lodash and hosted-git-info had vulnerabilities and was updated
12
+
13
+ ## [3.0.0]
14
+
15
+ ### Deprecation notice
16
+ - The node scanner is deprecated: https://github.com/RetireJS/retire.js/wiki/Deprecating-the-node.js-scanner
17
+
18
+ ## [2.2.5]
19
+
20
+ ### Dependency update
21
+ - y18n had a vulnerability and was updated
22
+
23
+ ## [2.2.4]
24
+
25
+ ### Bugfix
26
+ - Fixes [#343](https://github.com/RetireJS/retire.js/pull/343) where symlink to nonexistent file causes it to crash with exception. Now it will log it as warn instead.
27
+
28
+ ## [2.2.3]
29
+
30
+ ### Bugfix
31
+ - Fixes [#337](https://github.com/RetireJS/retire.js/issues/337) where symlinks are not read
32
+
33
+
34
+ ## [2.2.2]
35
+
36
+ ### Bugfix
37
+ - Fixes [#334](https://github.com/RetireJS/retire.js/issues/334) where detected libraries without vulnerabilities show in output even when verbose is not specified
38
+
39
+
40
+ ## [2.2.1]
41
+
42
+ ### Bugfix
43
+ - Fixes [#321](https://github.com/RetireJS/retire.js/issues/321) where write output to file did not always work as expected
44
+
45
+ ## [2.2.0]
46
+
47
+ ### Added
48
+ - Support `--cacert <path>` or `--insecure` when loading the repos (thanks to [adamcohen](https://github.com/adamcohen)) [PR#322](https://github.com/RetireJS/retire.js/pull/322)
49
+
50
+
51
+ ## [2.1.1] - 2020-03-20
52
+
53
+ ### Bugfix
54
+ - Fix compatibility with node 6
55
+
56
+
57
+ ## [2.1.1] - 2020-03-16
58
+
59
+ ### Modified
60
+ - Remove `request` as it is deprecated
61
+
62
+
63
+ ## [2.1.0] - 2020-03-16
64
+
65
+ ### Modified
66
+ - Support ** and * in ignore paths (** = any number of folders)
67
+
package/README.md ADDED
@@ -0,0 +1,80 @@
1
+ Command line scanner looking for use of known vulnerable js files and node modules in web projects and/or node projects.
2
+
3
+ Install
4
+ -------
5
+
6
+ npm install -g retire
7
+
8
+
9
+ Usage
10
+ -----
11
+
12
+ ````
13
+ Usage: retire [options]
14
+
15
+ Options:
16
+
17
+ -h, --help output usage information
18
+ -V, --version output the version number
19
+
20
+ -p, --package limit node scan to packages where parent is mentioned in package.json (ignore node_modules)
21
+ -n, --node Run node dependency scan only
22
+ -j, --js Run scan of JavaScript files only
23
+ -v, --verbose Show identified files (by default only vulnerable files are shown)
24
+ -x, --dropexternal Don't include project provided vulnerability repository
25
+ -c, --nocache Don't use local cache
26
+
27
+ --jspath <path> Folder to scan for javascript files
28
+ --nodepath <path> Folder to scan for node files
29
+ --path <path> Folder to scan for both
30
+ --jsrepo <path|url> Local or internal version of repo
31
+ --noderepo <path|url> Local or internal version of repo
32
+ --cachedir <path> Path to use for local cache instead of /tmp/.retire-cache
33
+ --proxy <url> Proxy url (http://some.sever:8080)
34
+ --outputformat <format> Valid formats: text, json, jsonsimple, depcheck (experimental) and cyclonedx
35
+ --outputpath <path> File to which output should be written
36
+ --ignore <paths> Comma delimited list of paths to ignore
37
+ --ignorefile <path> Custom ignore file, defaults to .retireignore / .retireignore.json
38
+ --severity <level> Specify the bug severity level from which the process fails. Allowed levels none, low, medium, high, critical. Default: none
39
+ --exitwith <code> Custom exit code (default: 13) when vulnerabilities are found
40
+ --colors Enable color output (console output only)
41
+ --insecure Enable fetching remote jsrepo/noderepo files from hosts using an insecure or self-signed SSL (TLS) certificate
42
+ --cacert <path> Use the specified certificate file to verify the peer used for fetching remote jsrepo/noderepo files
43
+ ````
44
+
45
+ The `depcheck` output format mimics the output of OWASP Dependency Check, but lacks some information compared to OWASP Dependency Check, because that information is not in the repo.
46
+ The `cyclonedx` output format is based on based on the https://github.com/CycloneDX spec.
47
+
48
+ .retireignore
49
+ -------------
50
+ ````
51
+ @qs # ignore this module regardless of location
52
+ node_modules/connect/node_modules/body-parser/node_modules/qs # ignore specific path
53
+ ````
54
+ Due to a bug in ignore resolving, please upgrade to >= 1.1.3
55
+
56
+ .retireignore.json
57
+ ------------------
58
+ ````
59
+ [
60
+ {
61
+ "component": "jquery",
62
+ "identifiers" : { "issue": "2432"},
63
+ "justification" : "We dont call external resources with jQuery"
64
+ },
65
+ {
66
+ "component": "jquery",
67
+ "version" : "2.1.4",
68
+ "justification" : "We dont call external resources with jQuery"
69
+ },
70
+ {
71
+ "path" : "node_modules",
72
+ "justification" : "The node modules are only used for building - client side dependencies are using bower"
73
+ }
74
+
75
+ ]
76
+ ````
77
+
78
+ Source code / Reporting an issue
79
+ --------------------------------
80
+ The source code and issue tracker can be found at [https://github.com/RetireJS/retire.js](https://github.com/RetireJS/retire.js)
package/bin/retire ADDED
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ /*jshint esversion: 6 */
3
+
4
+ var 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
+
18
+ var events = new emitter();
19
+ var jsRepo = null;
20
+ var bowerRepo = null;
21
+ var nodeRepo = null;
22
+ var vulnsFound = false;
23
+ var failProcess = false;
24
+ var defaultIgnoreFiles = ['.retireignore', '.retireignore.json'];
25
+ var finalResults = [];
26
+
27
+ var severityLevels = {
28
+ none: 0,
29
+ low: 1,
30
+ medium: 2,
31
+ high: 3,
32
+ critical: 4
33
+ };
34
+
35
+ colors.setTheme({
36
+ warn: 'red'
37
+ });
38
+
39
+
40
+ /*
41
+ * Parse command line flags.
42
+ */
43
+ program
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
+ .option('--insecure', 'Enable fetching remote jsrepo/noderepo files from hosts using an insecure or self-signed SSL (TLS) certificate')
68
+ .option('--cacert <path>', 'Use the specified certificate file to verify the peer used for fetching remote jsrepo/noderepo files')
69
+ .parse(process.argv);
70
+
71
+ var config = utils.extend({ path: '.' }, utils.pick(program, [
72
+ 'package', 'node', 'js', 'jspath', 'verbose', 'nodepath', 'path', 'jsrepo', 'noderepo',
73
+ 'dropexternal', 'nocache', 'proxy', 'ignore', 'ignorefile', 'outputformat', 'outputpath',
74
+ 'severity', 'exitwith', 'colors', 'includemeta', 'cachedir', 'insecure', 'cacert'
75
+ ]));
76
+
77
+ if (!config.nocache && !config.cachedir) {
78
+ config.cachedir = path.resolve(os.tmpdir(), '.retire-cache/');
79
+ }
80
+
81
+ config.ignore = config.ignore ? utils.map(config.ignore.split(','), function(e) { return path.resolve(e); }) : [];
82
+ config.ignore = { paths : config.ignore, descriptors: [] };
83
+ config.colorwarn = config.colors ? colors.warn : x => x;
84
+
85
+ if (!config.ignorefile) {
86
+ config.ignorefile = defaultIgnoreFiles.filter(function(x){ return fs.existsSync(x); })[0];
87
+ }
88
+ var log = reporting.open(config);
89
+ config.log = log;
90
+ log.info("retire.js v" + retire.version);
91
+
92
+ function exitWithError(msg) {
93
+ log.error(config.colorwarn(msg));
94
+ process.exitCode = 1;
95
+ log.close();
96
+ }
97
+
98
+
99
+ if(!config.severity) {
100
+ config.severity = 'none';
101
+ } else if (!severityLevels.hasOwnProperty(config.severity)) {
102
+ exitWithError('Error: Invalid severity level (' + config.severity + '). Valid levels are: ' + Object.keys(severityLevels).join(', '));
103
+ }
104
+
105
+ if(config.cacert) {
106
+ if (!fs.existsSync(config.cacert)) {
107
+ exitWithError('Error: Could not read cacert file: ' + config.cacert);
108
+ }
109
+ config.cacertbuf = fs.readFileSync(config.cacert);
110
+ }
111
+
112
+ if(config.ignorefile) {
113
+ if (!fs.existsSync(config.ignorefile)) {
114
+ exitWithError('Error: Could not read ignore file: ' + config.ignorefile);
115
+ }
116
+ if (config.ignorefile.substr(-5) === ".json") {
117
+ try {
118
+ var ignored = JSON.parse(fs.readFileSync(config.ignorefile).toString());
119
+ } catch(e) {
120
+ exitWithError('Error: Invalid ignore file: ' + config.ignorefile, e);
121
+ }
122
+ config.ignore.descriptors = ignored;
123
+ var ignoredPaths = ignored
124
+ .map(function(x) { return x.path; })
125
+ .filter(function(x) { return x; });
126
+ config.ignore.paths = config.ignore.paths.concat(ignoredPaths);
127
+ } else {
128
+ var lines = fs.readFileSync(config.ignorefile).toString().split(/\r\n|\n/g).filter(function(e) { return e !== ''; });
129
+ ignored = utils.map(lines, function(e) { return e[0] === '@' ? e.slice(1) : path.resolve(e); });
130
+ config.ignore.paths = config.ignore.paths.concat(ignored);
131
+ }
132
+ }
133
+ config.ignore.paths = config.ignore.paths
134
+ .map(p => p.replace(/[.+?^${}()|[\]\\]/g, '\\$&'))
135
+ .map(p => p.replace(/[*]{1,2}/g, (a) => a.length == 2 ? ".*" : "[^/]*"))
136
+ .map(s => new RegExp(s)
137
+ );
138
+
139
+ scanner.on('vulnerable-dependency-found', function(result) {
140
+ vulnsFound = true;
141
+ var levels = result.results
142
+ .map(function(r) {
143
+ return r.vulnerabilities ? r.vulnerabilities.map(function(v) {
144
+ return severityLevels[v.severity || 'critical'];
145
+ }) : []; });
146
+ var severity = utils.flatten(levels).reduce(function(x,y) { return x > y ? x : y; });
147
+ if(severity >= severityLevels[config.severity]) {
148
+ failProcess = true;
149
+ }
150
+ });
151
+
152
+ scanner.on('vulnerable-dependency-found', log.logVulnerableDependency);
153
+ scanner.on('dependency-found', log.logDependency);
154
+
155
+
156
+ events.on('load-js-repo', function() {
157
+ (config.jsrepo ?
158
+ (config.jsrepo.match(/^https?:\/\//) ?
159
+ repo.loadrepository(config.jsrepo, config)
160
+ : repo.loadrepositoryFromFile(config.jsrepo, config))
161
+ : repo.loadrepository('https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository.json', config)
162
+ ).on('stop', forward(events, 'stop'))
163
+ .on('done', function(repo) {
164
+ jsRepo = repo;
165
+ events.emit('js-repo-loaded');
166
+ });
167
+ });
168
+
169
+
170
+ events.on('load-node-repo', function() {
171
+ (config.noderepo ?
172
+ (config.noderepo.match(/^https?:\/\//) ?
173
+ repo.loadrepository(config.noderepo, config)
174
+ : repo.loadrepositoryFromFile(config.noderepo, config))
175
+ : repo.loadrepository('https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/npmrepository.json', config)
176
+ ).on('done', function(repo) {
177
+ nodeRepo = repo;
178
+ events.emit('node-repo-loaded');
179
+ }).on('stop', forward(events, 'stop'));
180
+ });
181
+
182
+ events.on('js-repo-loaded', function() {
183
+ events.emit(config.js ? 'scan-js' : 'load-node-repo');
184
+ });
185
+
186
+ events.on('node-repo-loaded', function() {
187
+ events.emit(config.node ? 'scan-node' : 'scan-js');
188
+ });
189
+
190
+
191
+ events.on('scan-js', function() {
192
+ resolve.scanJsFiles(config.jspath || config.path, config)
193
+ .on('jsfile', function(file) {
194
+ scanner.scanJsFile(file, jsRepo, config);
195
+ })
196
+ .on('bowerfile', function(bowerfile) {
197
+ bowerRepo = bowerRepo || repo.asbowerrepo(jsRepo);
198
+ scanner.scanBowerFile(bowerfile, bowerRepo, config);
199
+ })
200
+ .on('end', function() {
201
+ events.emit('js-scanned');
202
+ });
203
+ });
204
+
205
+ events.on('scan-node', function() {
206
+ console.warn('DEPRECATION NOTICE: The node scanning is deprecated and will be removed soon. See https://github.com/RetireJS/retire.js/wiki/Deprecating-the-node.js-scanner ')
207
+ resolve.getNodeDependencies(config.nodepath || config.path, config.package).on('done', function(dependencies) {
208
+ scanner.scanDependencies(dependencies, nodeRepo, config);
209
+ events.emit('scan-done');
210
+ }).on('error', function(err) {
211
+ console.warn("ERROR: " + err);
212
+ process.exit(1);
213
+ });
214
+ });
215
+
216
+ events.on('js-scanned', function() {
217
+ events.emit(!config.js ? 'scan-node' : 'scan-done');
218
+ });
219
+
220
+ events.on('scan-done', function() {
221
+ process.exitCode = failProcess ? (config.exitwith || 13) : 0;
222
+ log.close();
223
+ });
224
+
225
+
226
+ process.on('uncaughtException', function (err) {
227
+ console.warn('Exception caught: ', arguments);
228
+ console.warn(err.stack);
229
+ process.exit(1);
230
+ });
231
+
232
+ events.on('stop', function() {
233
+ exitWithError.apply(null, arguments);
234
+ });
235
+
236
+ if (config.node) {
237
+ events.emit('load-node-repo');
238
+ } else {
239
+ events.emit('load-js-repo');
240
+ }
package/lib/repo.js ADDED
@@ -0,0 +1,111 @@
1
+ /* global require, exports */
2
+ /*jshint esversion: 6 */
3
+ var utils = require('./utils'),
4
+ fs = require('fs'),
5
+ path = require('path'),
6
+ forward = require('../lib/utils').forwardEvent,
7
+ http = require('http'),
8
+ https = require('https'),
9
+ retire = require('./retire'),
10
+ URL = require('url'),
11
+ HttpsProxyAgent = require('https-proxy-agent');
12
+
13
+ var emitter = require('events').EventEmitter;
14
+
15
+
16
+ function loadJson(url, options) {
17
+ var events = new emitter();
18
+ options.log.info('Downloading ' + url + ' ...');
19
+ var reqOptions = Object.assign({}, URL.parse(url), { method: 'GET' });
20
+ if (options.proxy) {
21
+ reqOptions.agent = new HttpsProxyAgent(options.proxy);
22
+ }
23
+ if (options.insecure) {
24
+ reqOptions.rejectUnauthorized = false;
25
+ }
26
+ if (options.cacertbuf) {
27
+ reqOptions.ca = [ options.cacertbuf ];
28
+ }
29
+ var req = (url.startsWith("http:") ? http : https).request(reqOptions, function (res) {
30
+ if (res.statusCode != 200) return events.emit('stop', 'Error downloading: ' + url + ": HTTP " + res.statusCode + " " + res.statusText);
31
+ var data = [];
32
+ res.on('data', c => data.push(c));
33
+ res.on('end', () => {
34
+ var d = Buffer.concat(data).toString();
35
+ d = options.process ? options.process(d) : d;
36
+ events.emit('done', JSON.parse(d));
37
+ });
38
+ });
39
+ req.on('error', e => events.emit('stop', 'Error downloading: ' + url + ": " + e.toString()));
40
+ req.end();
41
+ return events;
42
+ }
43
+
44
+ function loadJsonFromFile(file, options) {
45
+ options.log.debug('Reading ' + file + ' ...');
46
+ var events = new emitter();
47
+ fs.readFile(file, { encoding : 'utf8'}, function(err, data) {
48
+ if (err) { return events.emit('stop', err.toString()); }
49
+ data = options.process ? options.process(data) : data;
50
+ var obj = JSON.parse(data);
51
+ events.emit('done', obj);
52
+ });
53
+ return events;
54
+ }
55
+
56
+ function loadFromCache(url, cachedir, options) {
57
+ var cacheIndex = path.resolve(cachedir, 'index.json');
58
+ if (!fs.existsSync(cachedir)) fs.mkdirSync(cachedir);
59
+ var cache = fs.existsSync(cacheIndex) ? JSON.parse(fs.readFileSync(cacheIndex)) : {};
60
+ var now = new Date().getTime();
61
+ if (cache[url]) {
62
+ if (now - cache[url].date < 60*60*1000) {
63
+ options.log.info('Loading from cache: ' + url);
64
+ return loadJsonFromFile(path.resolve(cachedir, cache[url].file), options);
65
+ } else {
66
+ if (fs.existsSync(path.resolve(cachedir, cache[url].date + '.json'))) {
67
+ try {
68
+ fs.unlinkSync(path.resolve(cachedir, cache[url].date + '.json'));
69
+ } catch (error) {
70
+ if (error.code !== 'ENOENT') {
71
+ throw error;
72
+ } else {
73
+ console.warn("Could not delete cache. Ignore this error if you are running multiple retire.js in parallel");
74
+ }
75
+ }
76
+ }
77
+ }
78
+ }
79
+ var events = new emitter();
80
+ loadJson(url, options).on('done', function(data) {
81
+ cache[url] = { date : now, file : now + '.json' };
82
+ fs.writeFileSync(path.resolve(cachedir, cache[url].file), JSON.stringify(data), { encoding : 'utf8' });
83
+ fs.writeFileSync(cacheIndex, JSON.stringify(cache), { encoding : 'utf8' });
84
+ events.emit('done', data);
85
+ }).on('stop', forward(events, 'stop'));
86
+ return events;
87
+ }
88
+
89
+ exports.asbowerrepo = function(jsRepo) {
90
+ var result = {};
91
+ Object.keys(jsRepo).map(function(k) {
92
+ (jsRepo[k].bowername || [k]).map(function(b) {
93
+ result[b] = result[b] || { vulnerabilities: [] };
94
+ result[b].vulnerabilities = result[b].vulnerabilities.concat(jsRepo[k].vulnerabilities);
95
+ });
96
+ });
97
+ return result;
98
+ };
99
+
100
+ exports.loadrepository = function(repoUrl, options) {
101
+ options = utils.extend(options, { process : retire.replaceVersion });
102
+ if (options.nocache) {
103
+ return loadJson(repoUrl, options);
104
+ }
105
+ return loadFromCache(repoUrl, options.cachedir, options);
106
+ };
107
+
108
+ exports.loadrepositoryFromFile = function(filepath, options) {
109
+ options = utils.extend(options, { process : retire.replaceVersion });
110
+ return loadJsonFromFile(filepath, options);
111
+ };
@@ -0,0 +1,43 @@
1
+ var retire = require('../retire');
2
+ var utils = require('../utils');
3
+
4
+
5
+ function printResults(logger, finding, config) {
6
+ if (finding.results && finding.results.length > 0) {
7
+ var logFunc = retire.isVulnerable(finding.results) ? logger.warn : logger.info;
8
+ var printed = {};
9
+ finding.results.forEach(function(elm) {
10
+ if (!config.verbose && !retire.isVulnerable([elm])) return;
11
+ var key = elm.component + ' ' + elm.version;
12
+ logFunc(finding.file);
13
+ logFunc(' ' + String.fromCharCode(8627) + ' ' + key);
14
+ if (printed[key]) return;
15
+ if (retire.isVulnerable([elm])) {
16
+ logFunc(key + ' has known vulnerabilities:' + printVulnerability(logger, elm, config));
17
+ }
18
+ printed[key] = true;
19
+ });
20
+ }
21
+ }
22
+
23
+ function printVulnerability(logger, component, config) {
24
+ var string = '';
25
+ component.vulnerabilities.forEach(function(vulnerability){
26
+ string += config.outputformat === 'clean' ? '\n ' : ' ';
27
+ if (vulnerability.severity) {
28
+ string += 'severity: ' + vulnerability.severity + '; ';
29
+ }
30
+ if (vulnerability.identifiers) {
31
+ string += utils.map(vulnerability.identifiers, function(id, name) {
32
+ return name + ': ' + utils.flatten([id]).join(' ');
33
+ }).join(', ') + '; ';
34
+ }
35
+ string += vulnerability.info.join(config.outputformat === 'clean' ? '\n' : ' ');
36
+ });
37
+ return string;
38
+ }
39
+
40
+ exports.configure = function(logger, writer, config, hash) {
41
+ logger.logDependency = function(finding) { if (config.verbose) printResults(logger, finding, config); };
42
+ logger.logVulnerableDependency = function(component) { printResults(logger, component, config); };
43
+ };
@@ -0,0 +1,59 @@
1
+ /*jshint esversion: 6 */
2
+
3
+ var retire = require('../retire');
4
+ var fs = require('fs');
5
+
6
+
7
+ function configureCycloneDXLogger(logger, writer, config, hash) {
8
+ var vulnsFound = false;
9
+ var finalResults = { version: retire.version, start: new Date(), data: [], messages: [], errors: [] };
10
+ logger.info = finalResults.messages.push;
11
+ logger.debug = config.verbose ? finalResults.messages.push : function() {};
12
+ logger.warn = logger.error = finalResults.errors.push;
13
+ logger.logVulnerableDependency = function(finding) {
14
+ vulnsFound = true;
15
+ finalResults.data.push(finding);
16
+ };
17
+ logger.logDependency = function(finding) {
18
+ if (finding.results.length > 0) {
19
+ finalResults.data.push(finding);
20
+ }
21
+ };
22
+
23
+ logger.close = function(callback) {
24
+ var write = vulnsFound ? writer.err : writer.out;
25
+ finalResults.start = finalResults.start.toISOString().replace("Z", "+0000");
26
+ var seen = {};
27
+ var components = finalResults.data.filter(d => d.results).map(r => r.results.map(dep => {
28
+ dep.version = (dep.version.split(".").length >= 3 ? dep.version : dep.version + ".0").replace(/-/g, ".");
29
+ var filepath = r.file || dep.file;
30
+ var filename = filepath.split("/").slice(-1);
31
+ var file = fs.readFileSync(filepath);
32
+ var purl = `pkg:npm/${dep.component}@${dep.version}`;
33
+ if (seen[purl]) return '';
34
+ seen[purl] = true;
35
+ return `
36
+ <component type="library">
37
+ <name>${dep.component}</name>
38
+ <version>${dep.version}</version>
39
+ <hashes>
40
+ <hash alg="MD5">${hash.md5(file)}</hash>
41
+ <hash alg="SHA-1">${hash.sha1(file)}</hash>
42
+ <hash alg="SHA-256">${hash.sha256(file)}</hash>
43
+ <hash alg="SHA-512">${hash.sha512(file)}</hash>
44
+ </hashes>
45
+ <licenses><license></license></licenses>
46
+ <purl>${purl}</purl>
47
+ <modified>false</modified>
48
+ </component>`;
49
+ }).join("")).join("");
50
+ write(`<?xml version="1.0"?>
51
+ <bom xmlns="http://cyclonedx.org/schema/bom/1.0" version="1">
52
+ <components>${components}
53
+ </components>
54
+ </bom>`);
55
+ writer.close(callback);
56
+ };
57
+ }
58
+
59
+ exports.configure = configureCycloneDXLogger;
@@ -0,0 +1,113 @@
1
+ /*jshint esversion: 6 */
2
+
3
+ var retire = require('../retire');
4
+ var fs = require('fs');
5
+
6
+
7
+ function configureDepCheckLogger(logger, writer, config, hash) {
8
+ var vulnsFound = false;
9
+ var finalResults = { version: retire.version, start: new Date(), data: [], messages: [], errors: [] };
10
+ logger.info = finalResults.messages.push;
11
+ logger.debug = config.verbose ? finalResults.messages.push : function() {};
12
+ logger.warn = logger.error = finalResults.errors.push;
13
+ logger.logVulnerableDependency = function(finding) {
14
+ vulnsFound = true;
15
+ finalResults.data.push(finding);
16
+ };
17
+ logger.logDependency = function(finding) { 
18
+ if (config.verbose && finding.results.length > 0) {
19
+ finalResults.data.push(finding);
20
+ }
21
+ };
22
+
23
+ logger.close = function(callback) {
24
+ var write = vulnsFound ? writer.err : writer.out;
25
+ finalResults.start = finalResults.start.toISOString().replace("Z", "+0000");
26
+ write(`<?xml version="1.0"?>
27
+ <analysis xmlns="https://jeremylong.github.io/DependencyCheck/dependency-check.2.3.xsd">
28
+ <scanInfo>
29
+ <engineVersion>${retire.version}</engineVersion>
30
+ <dataSource><name>${config.jsRepo || "Retire.js github js repo"}</name><timestamp>${finalResults.start}</timestamp></dataSource>
31
+ <dataSource><name>${config.nodeRepo || "Retire.js github node repo"}</name><timestamp>${finalResults.start}</timestamp></dataSource>
32
+ </scanInfo>
33
+ <projectInfo>
34
+ <name>${config.path}</name>
35
+ <reportDate>${finalResults.start}</reportDate>
36
+ <credits>retire.js</credits>
37
+ </projectInfo>
38
+ <dependencies>`);
39
+ write(finalResults.data.filter(d => d.results).map(r => r.results.map((dep, i) => {
40
+ var filepath = r.file || dep.file;
41
+ var filename = filepath.split("/").slice(-1);
42
+ var file = fs.readFileSync(filepath);
43
+ var md5 = hash.md5(file);
44
+ var sha1 = hash.sha1(file);
45
+ var sha256 = hash.sha256(file);
46
+ var evidence = `
47
+ <evidence type="product" confidence="HIGH">
48
+ <source>file</source>
49
+ <name>name</name>
50
+ <value>${dep.component}</value>
51
+ </evidence>
52
+ <evidence type="version" confidence="HIGH">
53
+ <source>file</source>
54
+ <name>version</name>
55
+ <value>${dep.version}</value>
56
+ </evidence>`;
57
+ var identifiers = `
58
+ <package confidence="HIGH">
59
+ <description>(${dep.component}:${dep.version})</description>
60
+ <id>${i}</id>
61
+ </package>`;
62
+ var vulns = dep.vulnerabilities && dep.vulnerabilities.length > 0 ? dep.vulnerabilities.map(v => {
63
+ var references = v.info.map(i => `
64
+ <reference>
65
+ <source>Retire.js</source>
66
+ <url>${i}</url>
67
+ <name>${i}</name>
68
+ </reference>`).join("");
69
+ var id = [v.identifiers && v.identifiers.CVE && v.identifiers.CVE[0], v.identifiers && v.identifiers.issue, dep.component + '@' + v.info[0]]
70
+ .filter(n => n !== null)[0];
71
+ //TODO: Fix CVSS stuff - add to repo? add id to every bug in repo?
72
+ return `
73
+ <vulnerability source="retire">
74
+ <name>${dep.component}:${dep.version}</name>
75
+ <cvssV2>
76
+ <score>7.5</score>
77
+ <accessVector>NETWORK</accessVector>
78
+ <accessComplexity>LOW</accessComplexity>
79
+ <authenticationr>NONE</authenticationr>
80
+ <confidentialImpact>PARTIAL</confidentialImpact>
81
+ <integrityImpact>PARTIAL</integrityImpact>
82
+ <availabilityImpact>PARTIAL</availabilityImpact>
83
+ <severity>${v.severity || "medium"}</severity>
84
+ </cvssV2>
85
+ <description>${v.identifiers && v.identifiers.summary || "None"}</description>
86
+ <references>${references}
87
+ </references>
88
+ <vulnerableSoftware>
89
+ <software>${ v.atOrAbove ? "&gt;= " + v.atOrAbove: "" } &lt; ${v.below}</software>
90
+ </vulnerableSoftware>
91
+ </vulnerability>`;
92
+ }).join('') : "";
93
+ return ` <dependency>
94
+ <fileName>${filename}</fileName>
95
+ <filePath>${filepath}</filePath>
96
+ <md5>${md5}</md5>
97
+ <sha1>${sha1}</sha1>
98
+ <sha256>${sha256}</sha256>
99
+ <evidenceCollected>${evidence}
100
+ </evidenceCollected>
101
+ <identifiers>${identifiers}
102
+ </identifiers>
103
+ <vulnerabilities>${vulns}
104
+ </vulnerabilities>
105
+ </dependency>`; }).join("\n")).join("\n"));
106
+ write(` </dependencies>
107
+ </analysis>`);
108
+ writer.close(callback);
109
+ };
110
+ }
111
+
112
+
113
+ exports.configure = configureDepCheckLogger;
@@ -0,0 +1,29 @@
1
+ /*jshint esversion: 6 */
2
+
3
+ var retire = require('../retire');
4
+
5
+ function configureJsonLogger(logger, writer, config) {
6
+ var scanStart = Date.now();
7
+ var vulnsFound = false;
8
+ var finalResults = { version: retire.version, start: new Date(), data: [], messages: [], errors: [] };
9
+ logger.info = finalResults.messages.push;
10
+ logger.debug = config.verbose ? finalResults.messages.push : function() {};
11
+ logger.warn = logger.error = (message) => finalResults.errors.push(message);
12
+ logger.logVulnerableDependency = function(finding) {
13
+ vulnsFound = true;
14
+ finalResults.data.push(finding);
15
+ };
16
+ logger.logDependency = function(finding) { 
17
+ if (config.verbose && finding.results.length > 0) {
18
+ finalResults.data.push(finding);
19
+ }
20
+ };
21
+ logger.close = function(callback) {
22
+ finalResults.time = (Date.now() - scanStart)/1000;
23
+ var res = (config.outputformat === "jsonsimple") ? finalResults.data : finalResults;
24
+ writer.out(JSON.stringify(res));
25
+ writer.close(callback);
26
+ };
27
+ }
28
+
29
+ exports.configure = configureJsonLogger;
@@ -0,0 +1,93 @@
1
+ /*jshint esversion: 6 */
2
+
3
+ var retire = require('./retire');
4
+ var utils = require('./utils');
5
+ var fs = require('fs');
6
+ var crypto = require('crypto');
7
+
8
+ var loggers = {
9
+ console : require("./reporters/console"),
10
+ text : require("./reporters/console"),
11
+ json : require("./reporters/json"),
12
+ depcheck : require("./reporters/depcheck"),
13
+ cyclonedx: require("./reporters/cyclonedx")
14
+ };
15
+ loggers.clean = loggers.console;
16
+ loggers.jsonsimple = loggers.json;
17
+
18
+
19
+ var colorwarn = function(x) { return x; };
20
+
21
+ var verbose = false;
22
+
23
+ function hashContent(hash, content) {
24
+ var h = crypto.createHash(hash);
25
+ h.update(content);
26
+ return h.digest('hex');
27
+ }
28
+
29
+ var hash = {
30
+ md5: (file) => hashContent('md5', file),
31
+ sha1: (file) => hashContent('sha1', file),
32
+ sha256: (file) => hashContent('sha256', file),
33
+ sha512: (file) => hashContent('sha512', file),
34
+ };
35
+
36
+
37
+
38
+ var writer = {
39
+ out: console.log,
40
+ err: function(x) { console.warn(colorwarn(x)); },
41
+ close : function() { }
42
+ };
43
+
44
+ var logger = {
45
+ info : function(x) { writer.out(x); },
46
+ debug : function(x) { if (verbose) writer.out(x); },
47
+ warn : function(x) { writer.err(x); },
48
+ error : function(x) { writer.err(x); },
49
+
50
+ logDependency : function(finding) { },
51
+ logVulnerableDependency: function(finding) { },
52
+ close: function() { writer.close(); }
53
+ };
54
+
55
+
56
+
57
+ function configureFileWriter(config) {
58
+ var fileOutput = {
59
+ fileDescriptor: fs.openSync(config.outputpath, "w")
60
+ };
61
+ if (fileOutput.fileDescriptor < 0) {
62
+ console.error("Could not open " + config.outputpath + " for writing");
63
+ process.exit(9);
64
+ }
65
+ fileOutput.stream = fs.createWriteStream('', {fd: fileOutput.fileDescriptor, autoClose: false});
66
+ var writeToFile = function(message) {
67
+ fileOutput.stream.write(message);
68
+ fileOutput.stream.write('\n');
69
+ };
70
+ writer.out = writer.err = writeToFile;
71
+ writer.close = function() {
72
+ fileOutput.stream.on('finish', function() {
73
+ fs.closeSync(fileOutput.fileDescriptor);
74
+ });
75
+ fileOutput.stream.end();
76
+ };
77
+ }
78
+
79
+ exports.open = function(config) {
80
+ verbose = config.verbose;
81
+ if (config.colors) colorwarn = config.colorwarn;
82
+ var format = config.outputformat || "console";
83
+ if (Object.keys(loggers).indexOf(format) == -1) {
84
+ console.warn("Invalid outputformat: " + format);
85
+ process.exit(1);
86
+ }
87
+ loggers[format].configure(logger, writer, config, hash);
88
+
89
+ if (typeof config.outputpath === 'string') {
90
+ configureFileWriter(config);
91
+ }
92
+ return logger;
93
+ };
package/lib/resolve.js ADDED
@@ -0,0 +1,101 @@
1
+ /* global require, exports */
2
+
3
+ var walkdir = require('walkdir'),
4
+ fs = require('fs'),
5
+ readInstalled = require('read-installed'),
6
+ emitter = require('events').EventEmitter;
7
+
8
+
9
+ function listdep(parent, dep, level, deps) {
10
+ var stack = [];
11
+ var dedup = {};
12
+ stack.push({parent: parent, dep: dep, level: level});
13
+ while (typeof (o = stack.pop()) !== 'undefined') {
14
+ for (var i in o.dep.dependencies) {
15
+ cyclic = false;
16
+ dep_parent = o.parent;
17
+ while (typeof dep_parent !== 'undefined') {
18
+ if (dep_parent.component === i) {
19
+ cyclic = true;
20
+ break;
21
+ } else {
22
+ dep_parent = dep_parent.parent;
23
+ }
24
+ }
25
+ if (cyclic) {
26
+ continue;
27
+ }
28
+ var id = i + "@" + o.dep.dependencies[i].version;
29
+ if (dedup[id]) continue;
30
+ dedup[id] = true;
31
+ var d = {
32
+ module: { component: i, version: o.dep.dependencies[i].version }
33
+ };
34
+ if (o.dep.dependencies[i].path) {
35
+ d.file = "node_modules" + o.dep.dependencies[i].path.split("node_modules").slice(1).join("node_modules") + '/package.json';
36
+ }
37
+ deps.push(d);
38
+ stack.push({parent: d, dep: o.dep.dependencies[i], level: o.level + 1});
39
+ }
40
+ }
41
+ }
42
+
43
+ function getNodeDependencies(path, limit) {
44
+ var events = new emitter();
45
+ readInstalled(path, {}, function (er, pkginfo) {
46
+ var deps = [];
47
+ if (limit) {
48
+ var packages = JSON.parse(fs.readFileSync(path +'/package.json'));
49
+ filter = [];
50
+
51
+ var filter = packages.dependencies ? Object.keys(packages.dependencies) : [];
52
+
53
+ Object.keys(pkginfo.dependencies)
54
+ .filter(function(d) { return !pkginfo.dependencies[d]._requiredBy || pkginfo.dependencies[d]._requiredBy.indexOf("/") > -1 || pkginfo.dependencies[d]._requiredBy.indexOf("#DEV:/") > -1; })
55
+ .filter(function(d) { return filter.indexOf(d) == -1; })
56
+ .forEach(function(d) { delete pkginfo.dependencies[d]; });
57
+ }
58
+ var notInstalled = Object.keys(pkginfo.dependencies).filter(function (d) {
59
+ return !pkginfo.dependencies[d].path;
60
+ });
61
+ if (notInstalled.length > 0) {
62
+ return events.emit('error', 'Could not find dependencies: ' + notInstalled.join(', ') + '. You may need to run npm install');
63
+ }
64
+ listdep({file: 'package.json',component: pkginfo.name, version: pkginfo.version}, pkginfo, 1, deps);
65
+ events.emit('done', deps);
66
+ });
67
+ return events;
68
+ }
69
+
70
+ function scanJsFiles(path, options) {
71
+ var finder = walkdir.find(path, { "follow_symlinks" : false, "no_return": true });
72
+ function onFile(file){
73
+ if (file.match(/\.js$/)) {
74
+ finder.emit('jsfile', file);
75
+ }
76
+ if (file.match(/\/bower.json$/)) {
77
+ finder.emit('bowerfile', file);
78
+ }
79
+ }
80
+ finder.on('file', onFile);
81
+ finder.on('link', function(link) {
82
+ if (fs.existsSync(link)) {
83
+ var file = fs.realpathSync(link);
84
+ if (fs.lstatSync(file).isFile()) {
85
+ onFile(link);
86
+ }
87
+ } else {
88
+ options.log.warn('Could not follow symlink: ' + link);
89
+ }
90
+ });
91
+ return finder;
92
+ }
93
+
94
+ exports.scanJsFiles = function(path, options) {
95
+ return scanJsFiles(path, options);
96
+ };
97
+
98
+ exports.getNodeDependencies = function(path, limit) {
99
+ return getNodeDependencies(path, limit);
100
+ };
101
+
package/lib/retire.js ADDED
@@ -0,0 +1,184 @@
1
+ /*
2
+ * This file is used by the browser plugins and the Cli scanner and thus
3
+ * cannot have any external dependencies (no require)
4
+ */
5
+
6
+ var exports = exports || {};
7
+ exports.version = '3.0.0';
8
+
9
+ function isDefined(o) {
10
+ return typeof o !== 'undefined';
11
+ }
12
+
13
+ function uniq(results){
14
+ var keys = {};
15
+ return results.filter(function(r) {
16
+ var k = r.component + ' ' + r.version;
17
+ keys[k] = keys[k] || 0;
18
+ return keys[k]++ === 0;
19
+ });
20
+ }
21
+
22
+ function scan(data, extractor, repo, matcher) {
23
+ matcher = matcher || simpleMatch;
24
+ var detected = [];
25
+ for (var component in repo) {
26
+ var extractors = repo[component].extractors[extractor];
27
+ if (!isDefined(extractors)) continue;
28
+ for (var i in extractors) {
29
+ var match = matcher(extractors[i], data);
30
+ if (match) {
31
+ match = match.replace(/(\.|-)min$/, "");
32
+ detected.push({ version: match, component: component, detection: extractor });
33
+ }
34
+ }
35
+ }
36
+ return uniq(detected);
37
+ }
38
+
39
+ function simpleMatch(regex, data) {
40
+ var re = new RegExp(regex);
41
+ var match = re.exec(data);
42
+ return match ? match[1] : null;
43
+ }
44
+ function replacementMatch(regex, data) {
45
+ var ar = /^\/(.*[^\\])\/([^\/]+)\/$/.exec(regex);
46
+ var re = new RegExp(ar[1]);
47
+ var match = re.exec(data);
48
+ var ver = null;
49
+ if (match) {
50
+ ver = match[0].replace(new RegExp(ar[1]), ar[2]);
51
+ return ver;
52
+ }
53
+ return null;
54
+ }
55
+
56
+ function splitAndMatchAll(tokenizer) {
57
+ return function(regex, data) {
58
+ var elm = data.split(tokenizer).pop();
59
+ return simpleMatch('^' + regex + '$', elm);
60
+ };
61
+ }
62
+
63
+
64
+
65
+ function scanhash(hash, repo) {
66
+ for (var component in repo) {
67
+ var hashes = repo[component].extractors.hashes;
68
+ if (!isDefined(hashes)) continue;
69
+ if (hashes.hasOwnProperty(hash)) {
70
+ return [{ version: hashes[hash], component: component, detection: 'hash' }];
71
+ }
72
+ }
73
+ return [];
74
+ }
75
+
76
+
77
+
78
+ function check(results, repo) {
79
+ for (var r in results) {
80
+ var result = results[r];
81
+ if (!isDefined(repo[result.component])) continue;
82
+ var vulns = repo[result.component].vulnerabilities;
83
+ for (var i in vulns) {
84
+ if (!isDefined(vulns[i].below) || !isAtOrAbove(result.version, vulns[i].below)) {
85
+ if (isDefined(vulns[i].atOrAbove) && !isAtOrAbove(result.version, vulns[i].atOrAbove)) {
86
+ continue;
87
+ }
88
+ var vulnerability = { info : vulns[i].info, below: vulns[i].below, atOrAbove: vulns[i].atOrAbove };
89
+ if (vulns[i].severity) {
90
+ vulnerability.severity = vulns[i].severity;
91
+ }
92
+ if (vulns[i].identifiers) {
93
+ vulnerability.identifiers = vulns[i].identifiers;
94
+ }
95
+ result.vulnerabilities = result.vulnerabilities || [];
96
+ result.vulnerabilities.push(vulnerability);
97
+ }
98
+ }
99
+ }
100
+ return results;
101
+ }
102
+
103
+ function unique(ar) {
104
+ var r = [];
105
+ ar.forEach(function(e) {
106
+ if (r.indexOf(e) == -1) r.push(e);
107
+ });
108
+ return r;
109
+ }
110
+
111
+
112
+ function isAtOrAbove(version1, version2) {
113
+ var v1 = version1.split(/[\.\-]/g);
114
+ var v2 = version2.split(/[\.\-]/g);
115
+ var l = v1.length > v2.length ? v1.length : v2.length;
116
+ for (var i = 0; i < l; i++) {
117
+ var v1_c = toComparable(v1[i]);
118
+ var v2_c = toComparable(v2[i]);
119
+ if (typeof v1_c !== typeof v2_c) return typeof v1_c === 'number';
120
+ if (v1_c > v2_c) return true;
121
+ if (v1_c < v2_c) return false;
122
+ }
123
+ return true;
124
+ }
125
+
126
+ function toComparable(n) {
127
+ if (!isDefined(n)) return 0;
128
+ if (n.match(/^[0-9]+$/)) {
129
+ return parseInt(n, 10);
130
+ }
131
+ return n;
132
+ }
133
+
134
+
135
+ //------- External API -------
136
+
137
+ exports.check = function(component, version, repo) {
138
+ return check([{component: component, version: version}], repo);
139
+ };
140
+
141
+ exports.replaceVersion = function(jsRepoJsonAsText) {
142
+ return jsRepoJsonAsText.replace(/§§version§§/g, '[0-9][0-9.a-z_\\\\-]+');
143
+ };
144
+
145
+ exports.isVulnerable = function(results) {
146
+ for (var r in results) {
147
+ if (results[r].hasOwnProperty('vulnerabilities')) return true;
148
+ }
149
+ return false;
150
+ };
151
+
152
+ exports.scanUri = function(uri, repo) {
153
+ var result = scan(uri, 'uri', repo);
154
+ return check(result, repo);
155
+ };
156
+
157
+ exports.scanFileName = function(fileName, repo) {
158
+ var result = scan(fileName, 'filename', repo, splitAndMatchAll('/'));
159
+ return check(result, repo);
160
+ };
161
+
162
+ exports.scanFileContent = function(content, repo, hasher) {
163
+ var normalizedContent = content.toString().replace(/(\r\n|\r)/g, "\n");
164
+ var result = scan(normalizedContent, 'filecontent', repo);
165
+ if (result.length === 0) {
166
+ result = scan(normalizedContent, 'filecontentreplace', repo, replacementMatch);
167
+ }
168
+ if (result.length === 0) {
169
+ result = scanhash(hasher.sha1(normalizedContent), repo);
170
+ }
171
+ return check(result, repo);
172
+ };
173
+
174
+ exports.scanNodeDependency = function(dependency, npmrepo, options) {
175
+ if (!isDefined(dependency.version)) {
176
+ if (options.log) options.log.warn('Missing version for ' + dependency.component + '. Need to run npm install ?');
177
+ return [];
178
+ }
179
+ if (!isDefined(npmrepo[dependency.component])) return [{component: dependency.component, version: dependency.version, file: dependency.file}];
180
+ return check([dependency], npmrepo);
181
+ };
182
+
183
+
184
+
package/lib/scanner.js ADDED
@@ -0,0 +1,138 @@
1
+ /*jshint esversion: 6 */
2
+ var retire = require('./retire'),
3
+ fs = require('fs'),
4
+ crypto = require('crypto'),
5
+ path = require('path'),
6
+ utils = require('./utils'),
7
+ emitter = new require('events').EventEmitter;
8
+
9
+ var events = new emitter();
10
+
11
+ var hash = {
12
+ 'sha1' : function(data) {
13
+ shasum = crypto.createHash('sha1');
14
+ shasum.update(data);
15
+ return shasum.digest('hex');
16
+ }
17
+ };
18
+
19
+ function emitResults(finding, options) {
20
+ removeIgnored(finding.results, options.ignore);
21
+ if (!options.verbose) finding.results = finding.results.filter(f => retire.isVulnerable([f]));
22
+ if (finding.results.length == 0) return;
23
+ if (retire.isVulnerable(finding.results)) {
24
+ events.emit('vulnerable-dependency-found', finding);
25
+ } else {
26
+ events.emit('dependency-found', finding);
27
+ }
28
+
29
+ }
30
+
31
+ function shouldIgnorePath(fileSpecs, ignores) {
32
+ return utils.detect(ignores.paths, function(i) {
33
+ return utils.detect(fileSpecs, function(j) {
34
+ return i.test(j) || i.test(path.resolve(j));
35
+ });
36
+ });
37
+ }
38
+
39
+ function removeIgnored(results, ignores) {
40
+ if (!ignores.hasOwnProperty('descriptors')) return;
41
+ results.forEach(function(r) {
42
+ if (!r.hasOwnProperty('vulnerabilities')) return;
43
+ ignores.descriptors.forEach(function(i) {
44
+ if (r.component !== i.component) return;
45
+ if (i.version && r.version !== i.version) return;
46
+ if (i.severity && r.severity !== i.severity) return;
47
+ if (i.identifiers) {
48
+ removeIgnoredVulnerabilitiesByIdentifier(i.identifiers, r);
49
+ return;
50
+ }
51
+ r.vulnerabilities = [];
52
+ });
53
+ if (r.vulnerabilities.length === 0) delete r.vulnerabilities;
54
+ });
55
+ }
56
+
57
+ function removeIgnoredVulnerabilitiesByIdentifier(identifiers, result) {
58
+ result.vulnerabilities = result.vulnerabilities.filter(function(v) {
59
+ if (!v.hasOwnProperty("identifiers")) return true;
60
+ return !utils.every(identifiers, function(key, value) { return hasIdentifier(v, key, value); });
61
+ });
62
+ }
63
+ function hasIdentifier(vulnerability, key, value) {
64
+ if (!vulnerability.identifiers.hasOwnProperty(key)) return false;
65
+ var identifier = vulnerability.identifiers[key];
66
+ return Array.isArray(identifier) ? identifier.some(function(x) { return x === value; }) : identifier === value;
67
+ }
68
+
69
+
70
+ function scanJsFile(file, repo, options) {
71
+ if (options.ignore && shouldIgnorePath([file], options.ignore)) {
72
+ return;
73
+ }
74
+ var results = retire.scanFileName(file, repo);
75
+ if (!results || results.length === 0) {
76
+ results = retire.scanFileContent(fs.readFileSync(file), repo, hash);
77
+ }
78
+ emitResults({file: file, results: results}, options);
79
+ }
80
+
81
+
82
+ function scanDependencies(dependencies, nodeRepo, options) {
83
+ for (var i in dependencies) {
84
+ var dependency = dependencies[i];
85
+ var fileSpecs = [toModulePath(dependency)];
86
+ if (dependency.component) {
87
+ fileSpecs.push(dependency.component);
88
+ }
89
+
90
+ if (options.ignore && shouldIgnorePath(fileSpecs, options.ignore)) {
91
+ continue;
92
+ }
93
+ results = retire.scanNodeDependency(dependencies[i].module, nodeRepo, options);
94
+ emitResults({file: dependencies[i].file, results: results}, options);
95
+ }
96
+ }
97
+
98
+ function toModulePath(dep) {
99
+ function f(d) {
100
+ if (d.parent) return f(d.parent) + '/node_modules/' + d.component;
101
+ return '';
102
+ }
103
+ return path.resolve(f(dep).substring(1));
104
+ }
105
+
106
+
107
+
108
+ function scanBowerFile(file, repo, options) {
109
+ if (options.ignore && shouldIgnorePath([file], options.ignore)) {
110
+ return;
111
+ }
112
+ try {
113
+ var bower = JSON.parse(fs.readFileSync(file));
114
+ if (bower.version) {
115
+ var results = retire.check(bower.name, bower.version, repo);
116
+ emitResults({file: file, results: results}, options);
117
+ }
118
+ } catch (e) {
119
+ options.log.warn('Could not parse file: ' + file);
120
+ }
121
+ }
122
+
123
+
124
+
125
+ exports.scanDependencies = function(dependencies, nodeRepo, options) {
126
+ return scanDependencies(dependencies, nodeRepo, options);
127
+ };
128
+ exports.scanJsFile = function(file, repo, options) {
129
+ return scanJsFile(file, repo, options);
130
+ };
131
+ exports.scanBowerFile = function(file, repo, options) {
132
+ return scanBowerFile(file, repo, options);
133
+ };
134
+ exports.on = function(name, listener) {
135
+ events.on(name, listener);
136
+ };
137
+
138
+
package/lib/utils.js ADDED
@@ -0,0 +1,57 @@
1
+
2
+ function info(options) {
3
+ return function(message) {
4
+ (options.logger || console.log)(message);
5
+ };
6
+ }
7
+
8
+ function warn(options) {
9
+ return function(message) {
10
+ (options.warnlogger || options.logger || console.warn)(message);
11
+ };
12
+ }
13
+
14
+ exports.pick = function(p, keys) {
15
+ var result = {};
16
+ keys.forEach(function(k) {
17
+ if (p.hasOwnProperty(k)) {
18
+ result[k] = p[k];
19
+ }
20
+ });
21
+ return result;
22
+ };
23
+
24
+ exports.extend = function(o, a) {
25
+ var result = exports.pick(o, Object.keys(o));
26
+ exports.map(a, function(v,k){ result[k] = v; });
27
+ return result;
28
+ };
29
+
30
+ exports.map = function(o, fn) {
31
+ return Object.keys(o).map(function(k) { return fn(o[k], k); });
32
+ };
33
+
34
+ exports.find = function(ar, fn) {
35
+ for(var i in ar) {
36
+ if (fn(ar[i])) return ar[i];
37
+ }
38
+ return undefined;
39
+ };
40
+
41
+ exports.detect = exports.find;
42
+
43
+ exports.flatten = function(e) {
44
+ return e.reduce(function(x,y) { return x.concat(y); }, []);
45
+ };
46
+
47
+ exports.forwardEvent = function(emitter, evt) {
48
+ return function() {
49
+ emitter.emit(evt, arguments[0]);
50
+ };
51
+ };
52
+
53
+ exports.every = function(things, predicate){
54
+ return Object.keys(things)
55
+ .map(function(k) { return predicate(k, things[k]); })
56
+ .reduce(function(x,y) { return x && y; }, true);
57
+ };
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "author": "Erlend Oftedal <erlend@oftedal.no>",
3
+ "name": "retire",
4
+ "description": "Retire is a tool for detecting use of vulnerable libraries",
5
+ "version": "3.0.2",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/RetireJS/retire.js.git"
10
+ },
11
+ "bin": {
12
+ "retire": "./bin/retire"
13
+ },
14
+ "main": "./lib/retire.js",
15
+ "dependencies": {
16
+ "colors": "^1.1.2",
17
+ "commander": "2.5.x",
18
+ "https-proxy-agent": "^5.0.0",
19
+ "read-installed": "^4.0.3",
20
+ "walkdir": "0.4.1"
21
+ },
22
+ "devDependencies": {
23
+ "chai": "^4.2.0",
24
+ "jshint": "^2.12.0",
25
+ "mocha": "^8.1.3"
26
+ },
27
+ "scripts": {
28
+ "test": "./test"
29
+ },
30
+ "engines": {
31
+ "node": ">= 6.0.0"
32
+ }
33
+ }