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 +12 -0
- package/lib/cli.js +1 -1
- package/lib/deepscan.js +2 -2
- package/lib/license.d.ts +1 -0
- package/lib/license.js +43 -0
- package/lib/repo.js +5 -0
- package/lib/reporters/cyclonedx-1_6-json.js +10 -0
- package/lib/reporters/cyclonedx-json.js +10 -0
- package/lib/reporters/cyclonedx.js +11 -1
- package/lib/retire.d.ts +2 -0
- package/lib/retire.js +3 -1
- package/lib/scanner.js +15 -6
- package/lib/types.d.ts +2 -0
- package/package.json +3 -1
package/CHANGELOG.md
CHANGED
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-
|
|
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.
|
|
34
|
-
return acc;
|
|
34
|
+
return acc.concat((0, retire_1.check)(cur.component, cur.version, repo));
|
|
35
35
|
}, []);
|
|
36
36
|
}
|
package/lib/license.d.ts
ADDED
|
@@ -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.
|
|
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
|
|
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.
|
|
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"
|