UNPKG

6.65 kBJavaScriptView Raw
1/* jshint -W055 */
2'use strict';
3
4module.exports = function (grunt) {
5
6 var retire = require('retire/lib/retire'),
7 repo = require('retire/lib/repo'),
8 resolve = require('retire/lib/resolve'),
9 log = require('retire/lib/utils').log,
10 scanner = require('retire/lib/scanner'),
11 fs = require('fs'),
12 path = require('path'),
13 req = require('request'),
14 os = require('os'),
15 async = require('async');
16
17 grunt.registerMultiTask('retire', 'Scanner detecting the use of JavaScript libraries with known vulnerabilites.', function () {
18 var done = this.async();
19 var jsRepo = null;
20 var nodeRepo = null;
21 var vulnsFound = false;
22 var filesSrc = this.filesSrc;
23 var request = req;
24 var defaultIgnoreFile = '.retireignore';
25 var output = {};
26 var scanedFile;
27
28 function taskVulnLogger(msg) {
29 var keyValue;
30 keyValue = scanedFile.slice(scanedFile.lastIndexOf('/') + 1);
31
32 if (keyValue.indexOf('.') > -1) {
33 keyValue = 'js.' + keyValue.slice(0, keyValue.indexOf('.'));
34 }
35 else {
36 keyValue = 'node.' + keyValue;
37 }
38 if (!output.hasOwnProperty(keyValue)) {
39 output[keyValue] = 1;
40 }
41 output[keyValue] = output[keyValue] + 1;
42 return grunt.log.error(msg);
43 }
44
45 // Merge task-specific and/or target-specific options with these defaults.
46 var options = this.options({
47 verbose: true,
48 packageOnly: false,
49 jsRepository: 'https://raw.github.com/RetireJS/retire.js/master/repository/jsrepository.json',
50 nodeRepository: 'https://raw.github.com/RetireJS/retire.js/master/repository/npmrepository.json',
51 logger: grunt.log.writeln,
52 warnlogger: taskVulnLogger,
53 outputFile: false
54 });
55 var logger = log(options);
56
57 if (!options.nocache) {
58 options.cachedir = path.resolve(os.tmpdir(), '.retire-cache/');
59 }
60 var ignores = options.ignore ? options.ignore.split(',') : [];
61 options.ignore = [];
62 if (!options.ignorefile && grunt.file.exists(defaultIgnoreFile)) {
63 options.ignorefile = defaultIgnoreFile;
64 }
65
66 if(options.ignorefile) {
67 if (!grunt.file.exists(options.ignorefile)) {
68 grunt.log.error('Error: Could not read ignore file: ' + options.ignorefile);
69 process.exit(1);
70 }
71 var lines = fs.readFileSync(options.ignorefile).toString().split(/\r\n|\n/g).filter(function(e) { return e !== ''; });
72 var ignored = lines.map(function(e) { return e[0] === '@' ? e.slice(1) : path.resolve(e); });
73 options.ignore = options.ignore.concat(ignored);
74 }
75
76 ignores.forEach(function(e) { options.ignore.push(e); });
77 logger.verbose("Ignoring " + JSON.stringify(options.ignore));
78
79 // log (verbose) options before hooking in the reporter
80 grunt.verbose.writeflags(options, 'Options');
81
82 // required to throw proper grunt error
83 scanner.on('vulnerable-dependency-found', function(e) {
84 vulnsFound = true;
85 });
86 var events = [];
87 function once(name, fun) {
88 events.push(name);
89 grunt.event.once(name, fun);
90 }
91 function on(name, fun) {
92 events.push(name);
93 grunt.event.on(name, fun);
94 }
95
96
97 once('retire-js-repo', function() {
98 filesSrc.forEach(function(filepath) {
99 if (!grunt.file.exists(filepath)) {
100 grunt.log.debug('Skipping directory file:', filepath);
101 return;
102 }
103 if (!grunt.file.isFile(filepath)) {
104 grunt.log.debug('Not a file:', filepath);
105 return;
106 }
107 scanedFile = filepath;
108 if(options.verbose) {
109 grunt.log.writeln('Checking:', filepath);
110 }
111 if (filepath.match(/\.js$/)) {
112 scanner.scanJsFile(filepath, jsRepo, options);
113 } else if (filepath.match(/\/bower.json$/)) {
114 scanner.scanBowerFile(filepath, jsRepo, options);
115 } else {
116 grunt.log.debug('Unknown file type:', filepath);
117 }
118 });
119 grunt.event.emit('retire-done');
120 });
121
122 on('retire-node-scan', function(filesSrc) {
123 if (filesSrc.length === 0) {
124 grunt.event.emit('retire-done');
125 return;
126 }
127 var filepath = filesSrc[0];
128 if(grunt.file.exists(filepath + '/package.json')) {
129 scanedFile = filepath.slice( 0, filepath.lastIndexOf('/') );
130 if(options.verbose) {
131 grunt.log.writeln('Checking:', filepath);
132 }
133 resolve.getNodeDependencies(filepath, options.packageOnly).on('done', function(dependencies) {
134 scanner.scanDependencies(dependencies, nodeRepo, options);
135 grunt.event.emit('retire-node-scan', filesSrc.slice(1));
136 });
137 } else {
138 grunt.log.debug('Skipping. Could not find:', filepath + '/package.json');
139 grunt.event.emit('retire-node-scan', filesSrc.slice(1));
140 }
141 });
142
143 once('retire-load-js', function() {
144 var done = function (repo) {
145 jsRepo = repo;
146 grunt.event.emit('retire-js-repo');
147 };
148
149 if (/^http[s]?:/.test(options.jsRepository)) {
150 repo.loadrepository(options.jsRepository, options).on('done', done);
151 } else {
152 repo.loadrepositoryFromFile(options.jsRepository, options).on('done', done);
153 }
154
155 });
156
157 once('retire-load-node', function() {
158 var done = function(repo) {
159 nodeRepo = repo;
160 grunt.event.emit('retire-node-scan', filesSrc);
161 };
162
163 if (/^http[s]?:/.test(options.nodeRepository)) {
164 repo.loadrepository(options.nodeRepository, options).on('done', done);
165 } else {
166 repo.loadrepositoryFromFile(options.nodeRepository, options).on('done', done);
167 }
168 });
169
170 once('retire-done', function() {
171 if(!vulnsFound){
172 grunt.log.writeln("No vulnerabilities found.");
173 }
174 events.forEach(function(e) {
175 grunt.event.removeAllListeners(e);
176 });
177 if (options.outputFile) {
178 grunt.file.write(options.outputFile, JSON.stringify(output));
179 }
180 done(!vulnsFound);
181 });
182
183 grunt.event.emit(this.target === 'node' ? 'retire-load-node' : 'retire-load-js');
184
185 });
186
187};