retire 4.0.0 → 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 CHANGED
@@ -1,5 +1,20 @@
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
+
13
+ ## [4.0.1]
14
+
15
+ ### Breaking changes
16
+ - Not really a breaking change as the change was introduced in 4.0.0, but node.js >= 14.0.0 is required to run retire.js >= 4.0.0 going forward
17
+
3
18
  ## [4.0.0]
4
19
 
5
20
  ### 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. Default: none
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 : "https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository.json";
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(jsrepolocation, config)
182
- : repo.loadrepositoryFromFile(jsrepolocation, config))
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
- scanner.scanJsFile(file, jsRepo, config);
188
+ jsRepos.forEach((jsRepo) => {
189
+ scanner.scanJsFile(file, jsRepo, config);
190
+ });
187
191
  })
188
192
  .on('bowerfile', (bowerfile) => {
189
- const bowerRepo = repo.asbowerrepo(jsRepo);
190
- scanner.scanBowerFile(bowerfile, bowerRepo, config);
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');
@@ -0,0 +1,3 @@
1
+ import { Options } from "./types";
2
+ import { Vulnerability } from "./types";
3
+ export declare function checkOSV(packageName: string, version: string, options: Options): Promise<Vulnerability[]>;
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;
@@ -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(([id, name]) => {
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.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
@@ -2,7 +2,7 @@
2
2
  "author": "Erlend Oftedal <erlend@oftedal.no>",
3
3
  "name": "retire",
4
4
  "description": "Retire is a tool for detecting use of vulnerable libraries",
5
- "version": "4.0.0",
5
+ "version": "4.1.0",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
@@ -42,7 +42,7 @@
42
42
  "check": "npm run lint && npm run typecheck"
43
43
  },
44
44
  "engines": {
45
- "node": ">= 6.0.0"
45
+ "node": ">= 14.0.0"
46
46
  },
47
47
  "keywords": [
48
48
  "sbom",