retire 4.0.1 → 4.1.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/CHANGELOG.md +10 -0
- package/README.md +4 -3
- package/lib/cli.js +19 -13
- package/lib/depsdev.d.ts +3 -0
- package/lib/depsdev.js +78 -0
- package/lib/reporters/console.js +2 -2
- package/lib/retire.js +2 -11
- package/lib/scanner.js +30 -0
- package/lib/types.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## [4.1.0]
|
|
5
|
+
|
|
6
|
+
### Additions
|
|
7
|
+
- Option to check results for the component from OSV
|
|
8
|
+
- Option to use more than one JS repository
|
|
9
|
+
|
|
10
|
+
### Bugfixes
|
|
11
|
+
- Remove dropexternal as it never worked
|
|
12
|
+
|
|
3
13
|
## [4.0.1]
|
|
4
14
|
|
|
5
15
|
### Breaking changes
|
package/README.md
CHANGED
|
@@ -15,23 +15,24 @@ Usage: retire [options]
|
|
|
15
15
|
Options:
|
|
16
16
|
-V, --version output the version number
|
|
17
17
|
-v, --verbose Show identified files (by default only vulnerable files are shown)
|
|
18
|
-
-x, --dropexternal Don't include project provided vulnerability repository
|
|
19
18
|
-c, --nocache Don't use local cache
|
|
20
19
|
--jspath <path> Folder to scan for javascript files
|
|
21
20
|
--path <path> Folder to scan for both
|
|
22
|
-
--jsrepo <path|url> Local or internal version of repo
|
|
21
|
+
--jsrepo <path|url> Local or internal version of repo. Can be multiple comma separated. Default: 'central')
|
|
23
22
|
--cachedir <path> Path to use for local cache instead of /tmp/.retire-cache
|
|
24
23
|
--proxy <url> Proxy url (http://some.host:8080)
|
|
25
24
|
--outputformat <format> Valid formats: text, json, jsonsimple, depcheck (experimental), cyclonedx and cyclonedxJSON
|
|
26
25
|
--outputpath <path> File to which output should be written
|
|
27
26
|
--ignore <paths> Comma delimited list of paths to ignore
|
|
28
27
|
--ignorefile <path> Custom ignore file, defaults to .retireignore / .retireignore.json
|
|
29
|
-
--severity <level> Specify the bug severity level from which the process fails. Allowed levels none, low, medium, high, critical.
|
|
28
|
+
--severity <level> Specify the bug severity level from which the process fails. Allowed levels none, low, medium, high, critical.
|
|
29
|
+
Default: none
|
|
30
30
|
--exitwith <code> Custom exit code (default: 13) when vulnerabilities are found
|
|
31
31
|
--colors Enable color output (console output only)
|
|
32
32
|
--insecure Enable fetching remote jsrepo/noderepo files from hosts using an insecure or self-signed SSL (TLS) certificate
|
|
33
33
|
--ext <extensions> Comman separated list of file extensions for javascript files. The default is "js"
|
|
34
34
|
--cacert <path> Use the specified certificate file to verify the peer used for fetching remote jsrepo/noderepo files
|
|
35
|
+
--includeOsv Include OSV advisories in the output
|
|
35
36
|
-h, --help display help for command
|
|
36
37
|
````
|
|
37
38
|
|
package/lib/cli.js
CHANGED
|
@@ -55,11 +55,10 @@ if (process.argv.includes("--node") || process.argv.includes("-n")) {
|
|
|
55
55
|
const prg = commander_1.program
|
|
56
56
|
.version(retire.version)
|
|
57
57
|
.option('-v, --verbose', 'Show identified files (by default only vulnerable files are shown)')
|
|
58
|
-
.option('-x, --dropexternal', "Don't include project provided vulnerability repository")
|
|
59
58
|
.option('-c, --nocache', "Don't use local cache")
|
|
60
59
|
.option('--jspath <path>', 'Folder to scan for javascript files')
|
|
61
60
|
.option('--path <path>', 'Folder to scan for both')
|
|
62
|
-
.option('--jsrepo <path|url>', 'Local or internal version of repo')
|
|
61
|
+
.option('--jsrepo <path|url>', 'Local or internal version of repo. Can be multiple comma separated. Default: \'central\')')
|
|
63
62
|
.option('--cachedir <path>', 'Path to use for local cache instead of /tmp/.retire-cache')
|
|
64
63
|
.option('--proxy <url>', 'Proxy url (http://some.host:8080)')
|
|
65
64
|
.option('--outputformat <format>', 'Valid formats: text, json, jsonsimple, depcheck (experimental), cyclonedx and cyclonedxJSON')
|
|
@@ -72,20 +71,22 @@ const prg = commander_1.program
|
|
|
72
71
|
.option('--insecure', 'Enable fetching remote jsrepo/noderepo files from hosts using an insecure or self-signed SSL (TLS) certificate')
|
|
73
72
|
.option('--ext <extensions>', 'Comman separated list of file extensions for javascript files. The default is "js"')
|
|
74
73
|
.option('--cacert <path>', 'Use the specified certificate file to verify the peer used for fetching remote jsrepo/noderepo files')
|
|
74
|
+
.option('--includeOsv', 'Include OSV advisories in the output')
|
|
75
75
|
.parse()
|
|
76
76
|
.opts();
|
|
77
77
|
const colorwarn = prg.colors ? ansi_colors_1.default.red : (x) => x;
|
|
78
|
-
const jsrepolocation = (_a = prg.jsrepo) !== null && _a !== void 0 ? _a : "
|
|
78
|
+
const jsrepolocation = ((_a = prg.jsrepo) !== null && _a !== void 0 ? _a : "'central'").split(",")
|
|
79
|
+
.map((x) => x === "'central'" ? "https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository.json" : x);
|
|
79
80
|
const ignorefile = (_b = prg.ignoreFile) !== null && _b !== void 0 ? _b : defaultIgnoreFiles.filter((x) => fs_1.default.existsSync(x))[0];
|
|
80
81
|
const scanpath = (_c = prg.path) !== null && _c !== void 0 ? _c : ".";
|
|
81
82
|
const log = reporting.open({
|
|
82
83
|
colors: !!prg.colors,
|
|
83
84
|
colorwarn,
|
|
84
|
-
jsRepo: jsrepolocation,
|
|
85
|
+
jsRepo: jsrepolocation.join(", "),
|
|
85
86
|
outputformat: prg.outputformat,
|
|
86
87
|
outputpath: prg.outputpath,
|
|
87
88
|
path: scanpath,
|
|
88
|
-
verbose: !!prg.verbose
|
|
89
|
+
verbose: !!prg.verbose,
|
|
89
90
|
});
|
|
90
91
|
const severity = (_d = prg.severity) !== null && _d !== void 0 ? _d : "none";
|
|
91
92
|
if (!(severity in types_1.severityLevels)) {
|
|
@@ -103,7 +104,9 @@ const config = {
|
|
|
103
104
|
cachedir: (_h = prg.cachedir) !== null && _h !== void 0 ? _h : path_1.default.resolve(os_1.default.tmpdir(), '.retire-cache/'),
|
|
104
105
|
log: log,
|
|
105
106
|
severity: severity,
|
|
106
|
-
exitwith: (_j = prg.exitwith) !== null && _j !== void 0 ? _j : 13
|
|
107
|
+
exitwith: (_j = prg.exitwith) !== null && _j !== void 0 ? _j : 13,
|
|
108
|
+
includeOsv: !!prg.includeOsv,
|
|
109
|
+
verbose: !!prg.verbose,
|
|
107
110
|
};
|
|
108
111
|
log.info(`retire.js v${retire.version}`);
|
|
109
112
|
function exitWithError(msg) {
|
|
@@ -177,17 +180,20 @@ process.on('uncaughtException', (err, ...rest) => {
|
|
|
177
180
|
events.on('stop', (err) => {
|
|
178
181
|
exitWithError(err);
|
|
179
182
|
});
|
|
180
|
-
(jsrepolocation.match(/^https?:\/\//)
|
|
181
|
-
? repo.loadrepository(
|
|
182
|
-
: repo.loadrepositoryFromFile(
|
|
183
|
-
.then((jsRepo) => {
|
|
183
|
+
Promise.all(jsrepolocation.map((jsr) => (jsr.match(/^https?:\/\//)
|
|
184
|
+
? repo.loadrepository(jsr, config)
|
|
185
|
+
: repo.loadrepositoryFromFile(jsr, config)))).then((jsRepos) => {
|
|
184
186
|
resolve.scanJsFiles(config.path, config)
|
|
185
187
|
.on('jsfile', (file) => {
|
|
186
|
-
|
|
188
|
+
jsRepos.forEach((jsRepo) => {
|
|
189
|
+
scanner.scanJsFile(file, jsRepo, config);
|
|
190
|
+
});
|
|
187
191
|
})
|
|
188
192
|
.on('bowerfile', (bowerfile) => {
|
|
189
|
-
|
|
190
|
-
|
|
193
|
+
jsRepos.forEach((jsRepo) => {
|
|
194
|
+
const bowerRepo = repo.asbowerrepo(jsRepo);
|
|
195
|
+
scanner.scanBowerFile(bowerfile, bowerRepo, config);
|
|
196
|
+
});
|
|
191
197
|
})
|
|
192
198
|
.on('end', () => {
|
|
193
199
|
events.emit('scan-done');
|
package/lib/depsdev.d.ts
ADDED
package/lib/depsdev.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
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.checkOSV = void 0;
|
|
16
|
+
const https_1 = __importDefault(require("https"));
|
|
17
|
+
const retire_1 = require("./retire");
|
|
18
|
+
function loadJson(url, options) {
|
|
19
|
+
options.log.debug("Downloading " + url + " ...");
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const req = https_1.default.request(url, res => {
|
|
22
|
+
const data = [];
|
|
23
|
+
res.on('data', c => data.push(c));
|
|
24
|
+
res.on('end', () => {
|
|
25
|
+
const result = Buffer.concat(data).toString();
|
|
26
|
+
resolve(JSON.parse(result));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
req.on('error', err => {
|
|
30
|
+
reject(err);
|
|
31
|
+
});
|
|
32
|
+
req.end();
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function getVulnerabilities(packageName, version, options) {
|
|
36
|
+
return loadJson(`https://api.deps.dev/v3alpha/systems/npm/packages/${packageName}/versions/${version}`, options);
|
|
37
|
+
}
|
|
38
|
+
function scoreToSeverity(score) {
|
|
39
|
+
if (score > 7)
|
|
40
|
+
return "high";
|
|
41
|
+
if (score > 4)
|
|
42
|
+
return "medium";
|
|
43
|
+
return "low";
|
|
44
|
+
}
|
|
45
|
+
function loadAdvisory(packageName, version, id, options) {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const osvAdvisory = yield loadJson(`https://api.osv.dev/v1/vulns/${id}`, options);
|
|
48
|
+
const advisory = yield loadJson(`https://api.deps.dev/v3alpha/advisories/${id}`, options);
|
|
49
|
+
const simplifiedRepo = {
|
|
50
|
+
[packageName]: {
|
|
51
|
+
vulnerabilities: osvAdvisory.affected.map(({ ranges }) => ranges.map(({ events }) => ({
|
|
52
|
+
atOrAbove: events[0].introduced,
|
|
53
|
+
below: events[0].fixed,
|
|
54
|
+
severity: scoreToSeverity(advisory.cvss3Score),
|
|
55
|
+
identifiers: {
|
|
56
|
+
githubID: id,
|
|
57
|
+
CVE: osvAdvisory.aliases.filter(x => x.startsWith("CVE-")),
|
|
58
|
+
summary: advisory.title
|
|
59
|
+
},
|
|
60
|
+
info: osvAdvisory.references.map(({ url }) => url)
|
|
61
|
+
}))).reduce((a, b) => a.concat(b), []),
|
|
62
|
+
extractors: {}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
return (0, retire_1.check)(packageName, version, simplifiedRepo);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function checkOSV(packageName, version, options) {
|
|
69
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
70
|
+
const versionInfo = yield getVulnerabilities(packageName, version, options);
|
|
71
|
+
if (versionInfo.advisoryKeys.length == 0)
|
|
72
|
+
return [];
|
|
73
|
+
const comps = yield Promise.all(versionInfo.advisoryKeys.map(({ id }) => loadAdvisory(packageName, version, id, options)));
|
|
74
|
+
const flattened = comps.reduce((a, b) => a.concat(b), []);
|
|
75
|
+
return flattened.map(x => { var _a; return (_a = x.vulnerabilities) !== null && _a !== void 0 ? _a : []; }).reduce((a, b) => a.concat(b), []);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
exports.checkOSV = checkOSV;
|
package/lib/reporters/console.js
CHANGED
|
@@ -53,8 +53,8 @@ function printVulnerability(component, config) {
|
|
|
53
53
|
string += `severity: ${vulnerability.severity}; `;
|
|
54
54
|
}
|
|
55
55
|
if (vulnerability.identifiers) {
|
|
56
|
-
string += Object.entries(vulnerability.identifiers).map(([
|
|
57
|
-
return `${name}: ${utils.flatten([[id]]).join(' ')}`;
|
|
56
|
+
string += Object.entries(vulnerability.identifiers).map(([name, id]) => {
|
|
57
|
+
return `${name}: ${utils.flatten([Array.isArray(id) ? id : [id]]).join(' ')}`;
|
|
58
58
|
}).join(', ') + '; ';
|
|
59
59
|
}
|
|
60
60
|
string += vulnerability.info.join(config.outputformat === 'clean' ? '\n' : ' ');
|
package/lib/retire.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
var exports = exports || {};
|
|
7
|
-
exports.version = '4.0
|
|
7
|
+
exports.version = '4.1.0';
|
|
8
8
|
|
|
9
9
|
function isDefined(o) {
|
|
10
10
|
return typeof o !== 'undefined';
|
|
@@ -100,15 +100,6 @@ function check(results, repo) {
|
|
|
100
100
|
return results;
|
|
101
101
|
}
|
|
102
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
103
|
function isAtOrAbove(version1, version2) {
|
|
113
104
|
var v1 = version1.split(/[\.\-]/g);
|
|
114
105
|
var v2 = version2.split(/[\.\-]/g);
|
|
@@ -144,7 +135,7 @@ exports.replaceVersion = function(jsRepoJsonAsText) {
|
|
|
144
135
|
|
|
145
136
|
exports.isVulnerable = function(results) {
|
|
146
137
|
for (var r in results) {
|
|
147
|
-
if (results[r].hasOwnProperty('vulnerabilities')) return true;
|
|
138
|
+
if (results[r].hasOwnProperty('vulnerabilities') && results[r].vulnerabilities != undefined && results[r].vulnerabilities.length > 0) return true;
|
|
148
139
|
}
|
|
149
140
|
return false;
|
|
150
141
|
};
|
package/lib/scanner.js
CHANGED
|
@@ -29,6 +29,7 @@ const retire = __importStar(require("./retire"));
|
|
|
29
29
|
const fs = __importStar(require("fs"));
|
|
30
30
|
const crypto = __importStar(require("crypto"));
|
|
31
31
|
const path = __importStar(require("path"));
|
|
32
|
+
const depsdev_1 = require("./depsdev");
|
|
32
33
|
const events = new events_1.EventEmitter();
|
|
33
34
|
const hash = {
|
|
34
35
|
'sha1': (data) => {
|
|
@@ -38,6 +39,35 @@ const hash = {
|
|
|
38
39
|
}
|
|
39
40
|
};
|
|
40
41
|
function emitResults(finding, options) {
|
|
42
|
+
if (options.includeOsv === true) {
|
|
43
|
+
Promise
|
|
44
|
+
.all(finding.results.map((r) => (0, depsdev_1.checkOSV)(r.component, r.version, options).then(v => { var _a; return r.vulnerabilities = ((_a = r.vulnerabilities) !== null && _a !== void 0 ? _a : []).concat(v); })))
|
|
45
|
+
.then(() => filterAndEmitResults(finding, options));
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
filterAndEmitResults(finding, options);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getIdentifiers(v) {
|
|
52
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
53
|
+
return ((_b = (_a = v.identifiers) === null || _a === void 0 ? void 0 : _a.CVE) !== null && _b !== void 0 ? _b : [])
|
|
54
|
+
.concat((_d = (_c = v.identifiers) === null || _c === void 0 ? void 0 : _c.bug) !== null && _d !== void 0 ? _d : [])
|
|
55
|
+
.concat((_f = (_e = v.identifiers) === null || _e === void 0 ? void 0 : _e.issue) !== null && _f !== void 0 ? _f : [])
|
|
56
|
+
.concat((_h = (_g = v.identifiers) === null || _g === void 0 ? void 0 : _g.githubID) !== null && _h !== void 0 ? _h : []);
|
|
57
|
+
}
|
|
58
|
+
function uniqueVulnerabilities(vulnerabilities) {
|
|
59
|
+
if (!vulnerabilities)
|
|
60
|
+
return undefined;
|
|
61
|
+
const unique = [];
|
|
62
|
+
for (const v of vulnerabilities) {
|
|
63
|
+
if (!unique.some(u => getIdentifiers(u).some(i => getIdentifiers(v).includes(i)))) {
|
|
64
|
+
unique.push(v);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return unique;
|
|
68
|
+
}
|
|
69
|
+
function filterAndEmitResults(finding, options) {
|
|
70
|
+
finding.results.forEach(r => r.vulnerabilities = uniqueVulnerabilities(r.vulnerabilities));
|
|
41
71
|
if (options.ignore)
|
|
42
72
|
removeIgnored(finding.results, options.ignore);
|
|
43
73
|
if (!options.verbose)
|
package/lib/types.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type Vulnerability = {
|
|
|
21
21
|
bug?: string;
|
|
22
22
|
issue?: string;
|
|
23
23
|
summary?: string;
|
|
24
|
+
githubID?: string;
|
|
24
25
|
};
|
|
25
26
|
info: string[];
|
|
26
27
|
};
|
|
@@ -79,5 +80,6 @@ export type Options = {
|
|
|
79
80
|
jsRepo?: string;
|
|
80
81
|
path: string;
|
|
81
82
|
exitwith: number;
|
|
83
|
+
includeOsv?: boolean;
|
|
82
84
|
};
|
|
83
85
|
export {};
|
package/package.json
CHANGED