retire 5.1.3 → 5.2.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,17 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.2.0]
4
+
5
+ ### Enhancement
6
+
7
+ - Add license information to SBOM
8
+
9
+ ## [5.1.4]
10
+
11
+ ### Bugfix
12
+
13
+ - Fix bug where deepScan would sometimes not report vulnerabilities
14
+
3
15
  ## [5.1.3]
4
16
 
5
17
  ### Bugfix
package/lib/cli.js CHANGED
@@ -78,7 +78,7 @@ const colorwarn = prg.colors ? ansi_colors_1.default.red : (x) => x;
78
78
  const jsrepolocation = (prg.jsrepo ?? "'central'")
79
79
  .split(',')
80
80
  .map((x) => x === "'central'"
81
- ? 'https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository-v2.json'
81
+ ? 'https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository-v4.json'
82
82
  : x);
83
83
  const ignorefile = prg.ignoreFile ?? defaultIgnoreFiles.filter((x) => fs_1.default.existsSync(x))[0];
84
84
  const scanpath = prg.path ?? prg.jspath ?? '.';
package/lib/deepscan.js CHANGED
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deepScan = deepScan;
4
4
  const astronomical_1 = require("astronomical");
5
+ const retire_1 = require("./retire");
5
6
  function deepScan(content, repo) {
6
7
  const astQueries = {};
7
8
  const backMap = {};
@@ -30,7 +31,6 @@ function deepScan(content, repo) {
30
31
  return detected.reduce((acc, cur) => {
31
32
  if (acc.some((c) => c.component === cur.component && c.version === cur.version))
32
33
  return acc;
33
- acc.push(cur);
34
- return acc;
34
+ return acc.concat((0, retire_1.check)(cur.component, cur.version, repo));
35
35
  }, []);
36
36
  }
@@ -0,0 +1 @@
1
+ export declare function evaluateLicense(licenses: string[], version: string): string[];
package/lib/license.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateLicense = evaluateLicense;
4
+ const retire_1 = require("./retire");
5
+ function evaluateLicense(licenses, version) {
6
+ const parsedLicenses = licenses.map((license) => {
7
+ let splitIndex = license.indexOf(' ');
8
+ let licenseName = license.slice(0, splitIndex);
9
+ if (license.startsWith('(')) {
10
+ splitIndex = license.slice(1).indexOf(')') + 1;
11
+ licenseName = license.slice(1, splitIndex);
12
+ }
13
+ //console.log(license, '|', splitIndex, '|', licenseName, '|', license.slice(splitIndex + 1));
14
+ const ranges = license
15
+ .slice(splitIndex + 1)
16
+ .trim()
17
+ .split(';')
18
+ .map((range) => {
19
+ const [from, to] = range.split(' ').map((v) => v.trim());
20
+ if (!from.startsWith('>='))
21
+ throw new Error("Invalid license range: 'from' must start with '>=': " + range);
22
+ if (to && !to.startsWith('<'))
23
+ throw new Error("Invalid license range: 'to' must start with '<': " + range);
24
+ if (to && to.startsWith('<='))
25
+ throw new Error("Invalid license range: 'to' must start with '<': " + range);
26
+ return { from: from.replace('>=', ''), to: to ? to.replace('<', '') : undefined };
27
+ });
28
+ return { licenseName, ranges };
29
+ });
30
+ return parsedLicenses
31
+ .filter((parsedLicense) => {
32
+ return parsedLicense.ranges.some((range) => {
33
+ if (range.from && range.to) {
34
+ return (0, retire_1.isAtOrAbove)(version, range.from) && (0, retire_1.isAtOrAbove)(range.to, version);
35
+ }
36
+ if (range.from) {
37
+ return (0, retire_1.isAtOrAbove)(version, range.from);
38
+ }
39
+ return false;
40
+ });
41
+ })
42
+ .map((parsedLicense) => parsedLicense.licenseName);
43
+ }
package/lib/repo.js CHANGED
@@ -150,6 +150,11 @@ function validateRepository(repo, replacer) {
150
150
  ast: z.array(z.string()).optional(),
151
151
  })
152
152
  .strict(),
153
+ licenses: z
154
+ .array(z
155
+ .string()
156
+ .regex(/(\([A-Za-z\-0-9.]+( OR [A-Za-z\-0-9.]+)+\)|[A-Za-z\-0-9.]+) >=[0-9.]+( <[0-9.]+)?(;>=[0-9.]+( <[0-9.]+)?)*/))
157
+ .optional(),
153
158
  })
154
159
  .strict());
155
160
  return validator.safeParse(repo);
@@ -89,6 +89,7 @@ function configureCycloneDXJSONLogger(logger, writer, config, hash) {
89
89
  purl: purl,
90
90
  hashes: hashes,
91
91
  evidence,
92
+ licenses: mapLicenses(dep.licenses),
92
93
  };
93
94
  seen.set(purl, result);
94
95
  return result;
@@ -115,6 +116,15 @@ function configureCycloneDXJSONLogger(logger, writer, config, hash) {
115
116
  writer.close(callback);
116
117
  };
117
118
  }
119
+ function mapLicenses(licenses) {
120
+ if (!licenses)
121
+ return [];
122
+ if (licenses.length == 0)
123
+ return [];
124
+ if (licenses[0] == 'commercial')
125
+ return [{ license: { name: 'Commercial' } }];
126
+ return [{ expression: licenses[0] }];
127
+ }
118
128
  exports.default = {
119
129
  configure: configureCycloneDXJSONLogger,
120
130
  };
@@ -88,6 +88,7 @@ function configureCycloneDXJSONLogger(logger, writer, config, hash) {
88
88
  version: dep.version,
89
89
  purl: purl,
90
90
  hashes: hashes,
91
+ licenses: mapLicenses(dep.licenses),
91
92
  properties,
92
93
  };
93
94
  seen.set(purl, result);
@@ -115,6 +116,15 @@ function configureCycloneDXJSONLogger(logger, writer, config, hash) {
115
116
  writer.close(callback);
116
117
  };
117
118
  }
119
+ function mapLicenses(licenses) {
120
+ if (!licenses)
121
+ return [];
122
+ if (licenses.length == 0)
123
+ return [];
124
+ if (licenses[0] == 'commercial')
125
+ return [{ license: { name: 'Commercial' } }];
126
+ return [{ expression: licenses[0] }];
127
+ }
118
128
  exports.default = {
119
129
  configure: configureCycloneDXJSONLogger,
120
130
  };
@@ -57,7 +57,7 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
57
57
  const write = vulnsFound ? writer.err : writer.out;
58
58
  const seen = new Set();
59
59
  const components = finalResults.data
60
- .filter((d) => d.results)
60
+ .filter((d) => d.results.length > 0)
61
61
  .map((r) => r.results
62
62
  .map((dep) => {
63
63
  dep.version = (dep.version.split('.').length >= 3 ? dep.version : dep.version + '.0').replace(/-/g, '.');
@@ -81,6 +81,7 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
81
81
  <component type="library">
82
82
  <name>${dep.component}</name>
83
83
  <version>${dep.version}</version>${hashes}
84
+ <licenses>${mapLicenses(dep.licenses)}</licenses>
84
85
  <purl>${purl}</purl>
85
86
  <modified>false</modified>
86
87
  </component>`;
@@ -105,6 +106,15 @@ function configureCycloneDXLogger(logger, writer, config, hash) {
105
106
  writer.close(callback);
106
107
  };
107
108
  }
109
+ function mapLicenses(licenses) {
110
+ if (!licenses)
111
+ return '';
112
+ if (licenses.length == 0)
113
+ return '';
114
+ if (licenses[0] == 'commercial')
115
+ return '<license><name>Commercial</name></license>';
116
+ return `<expression>${licenses[0]}</expression>`;
117
+ }
108
118
  exports.default = {
109
119
  configure: configureCycloneDXLogger,
110
120
  };
package/lib/retire.d.ts CHANGED
@@ -12,4 +12,6 @@ export declare function scanFileName(fileName: string, repo: Repository, include
12
12
 
13
13
  export declare function scanFileContent(content: string, repo: Repository, hasher: Hasher): Component[];
14
14
 
15
+ export declare function isAtOrAbove(versionA: string, versionB: string): boolean;
16
+
15
17
  export declare const version: string;
package/lib/retire.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  var exports = exports || {};
7
- exports.version = '5.1.3';
7
+ exports.version = '5.2.0';
8
8
 
9
9
  function isDefined(o) {
10
10
  return typeof o !== 'undefined';
@@ -186,3 +186,5 @@ exports.scanFileContent = function (content, repo, hasher) {
186
186
  }
187
187
  return check(result, repo);
188
188
  };
189
+
190
+ exports.isAtOrAbove = isAtOrAbove;
package/lib/scanner.js CHANGED
@@ -33,6 +33,7 @@ const crypto = __importStar(require("crypto"));
33
33
  const path = __importStar(require("path"));
34
34
  const depsdev_1 = require("./depsdev");
35
35
  const deepscan_1 = require("./deepscan");
36
+ const license_1 = require("./license");
36
37
  const events = new events_1.EventEmitter();
37
38
  const hash = {
38
39
  sha1: (data) => {
@@ -41,12 +42,12 @@ const hash = {
41
42
  return shasum.digest('hex');
42
43
  },
43
44
  };
44
- function emitResults(finding, options) {
45
+ function emitResults(finding, options, repo) {
45
46
  if (options.includeOsv === true) {
46
- Promise.all(finding.results.map((r) => (0, depsdev_1.checkOSV)(r.component, r.version, options).then((v) => (r.vulnerabilities = (r.vulnerabilities ?? []).concat(v))))).then(() => filterAndEmitResults(finding, options));
47
+ Promise.all(finding.results.map((r) => (0, depsdev_1.checkOSV)(r.component, r.version, options).then((v) => (r.vulnerabilities = (r.vulnerabilities ?? []).concat(v))))).then(() => filterAndEmitResults(finding, options, repo));
47
48
  }
48
49
  else {
49
- filterAndEmitResults(finding, options);
50
+ filterAndEmitResults(finding, options, repo);
50
51
  }
51
52
  }
52
53
  function getIdentifiers(v) {
@@ -66,12 +67,20 @@ function uniqueVulnerabilities(vulnerabilities) {
66
67
  }
67
68
  return unique;
68
69
  }
69
- function filterAndEmitResults(finding, options) {
70
+ function addLicenses(components, repo) {
71
+ components.forEach((c) => {
72
+ const possibleLicenses = repo[c.component]?.licenses;
73
+ if (possibleLicenses)
74
+ c.licenses = (0, license_1.evaluateLicense)(possibleLicenses, c.version);
75
+ });
76
+ }
77
+ function filterAndEmitResults(finding, options, repo) {
70
78
  finding.results.forEach((r) => (r.vulnerabilities = uniqueVulnerabilities(r.vulnerabilities)));
71
79
  if (options.ignore)
72
80
  removeIgnored(finding.results, options.ignore);
73
81
  if (finding.results.length == 0)
74
82
  return;
83
+ addLicenses(finding.results, repo);
75
84
  if (retire.isVulnerable(finding.results)) {
76
85
  events.emit('vulnerable-dependency-found', finding);
77
86
  }
@@ -137,7 +146,7 @@ function scanJsFile(file, repo, options) {
137
146
  results = results.concat((0, deepscan_1.deepScan)(content, repo));
138
147
  }
139
148
  }
140
- emitResults({ file: file, results: results }, options);
149
+ emitResults({ file: file, results: results }, options, repo);
141
150
  }
142
151
  function scanBowerFile(file, repo, options) {
143
152
  if (options.ignore && shouldIgnorePath([file], options.ignore)) {
@@ -147,7 +156,7 @@ function scanBowerFile(file, repo, options) {
147
156
  const bower = JSON.parse(fs.readFileSync(file, 'utf-8'));
148
157
  if (bower.version) {
149
158
  const results = retire.check(bower.name, bower.version, repo);
150
- emitResults({ file: file, results: results }, options);
159
+ emitResults({ file: file, results: results }, options, repo);
151
160
  }
152
161
  }
153
162
  catch (e) {
package/lib/types.d.ts CHANGED
@@ -13,6 +13,7 @@ export type Repository = Record<string, {
13
13
  hashes?: Record<string, string>;
14
14
  ast?: string[];
15
15
  };
16
+ licenses?: string[];
16
17
  }>;
17
18
  export type Vulnerability = {
18
19
  below: string;
@@ -35,6 +36,7 @@ export type Component = {
35
36
  version: string;
36
37
  vulnerabilities?: Vulnerability[];
37
38
  detection?: string;
39
+ licenses?: string[];
38
40
  };
39
41
  export type Finding = {
40
42
  results: Component[];
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": "5.1.3",
5
+ "version": "5.2.0",
6
6
  "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
@@ -32,6 +32,7 @@
32
32
  "jsonschema": "^1.4.1",
33
33
  "mocha": "^10.2.0",
34
34
  "prettier": "^3.1.0",
35
+ "ts-node": "^10.9.2",
35
36
  "typescript": "^5.0.4",
36
37
  "xsd-schema-validator": "^0.9.0"
37
38
  },
@@ -55,6 +56,7 @@
55
56
  "software-composition-analysis",
56
57
  "sca"
57
58
  ],
59
+ "type": "commonjs",
58
60
  "files": [
59
61
  "lib/**/*",
60
62
  "CHANGELOG.md"