retire 4.3.3 → 4.4.1
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 +19 -1
- package/lib/cli.js +13 -13
- package/lib/depsdev.js +39 -55
- package/lib/repo.d.ts +2 -0
- package/lib/repo.js +206 -89
- package/lib/reporters/console.js +1 -2
- package/lib/reporting.d.ts +2 -0
- package/lib/reporting.js +1 -2
- package/lib/retire.js +1 -1
- package/lib/scanner.js +15 -18
- package/lib/types.d.ts +1 -1
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [4.
|
|
3
|
+
## [4.4.1]
|
|
4
|
+
|
|
5
|
+
### Chore
|
|
6
|
+
|
|
7
|
+
- Dependency upgrades
|
|
8
|
+
|
|
9
|
+
## [4.4.0]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- Adding proper repository validation to ensure it's on the expected format
|
|
14
|
+
|
|
15
|
+
## [4.3.4]
|
|
16
|
+
|
|
17
|
+
### Bugfix
|
|
18
|
+
|
|
19
|
+
- Bug: `--jspath` is not being honoured
|
|
20
|
+
|
|
21
|
+
## [4.3.3]
|
|
4
22
|
|
|
5
23
|
### Bugfix
|
|
6
24
|
|
package/lib/cli.js
CHANGED
|
@@ -26,7 +26,6 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
26
26
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
30
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
30
|
const utils = __importStar(require("./utils"));
|
|
32
31
|
const commander_1 = require("commander");
|
|
@@ -56,8 +55,8 @@ const prg = commander_1.program
|
|
|
56
55
|
.version(retire.version)
|
|
57
56
|
.option('-v, --verbose', 'Show identified files (by default only vulnerable files are shown)')
|
|
58
57
|
.option('-c, --nocache', "Don't use local cache")
|
|
59
|
-
.option('--jspath <path>', 'Folder to scan for javascript files')
|
|
60
|
-
.option('--path <path>', 'Folder to scan for
|
|
58
|
+
.option('--jspath <path>', 'Folder to scan for javascript files (deprecated)')
|
|
59
|
+
.option('--path <path>', 'Folder to scan for javascript files')
|
|
61
60
|
.option('--jsrepo <path|url>', "Local or internal version of repo. Can be multiple comma separated. Default: 'central')")
|
|
62
61
|
.option('--cachedir <path>', 'Path to use for local cache instead of /tmp/.retire-cache')
|
|
63
62
|
.option('--proxy <url>', 'Proxy url (http://some.host:8080)')
|
|
@@ -75,11 +74,11 @@ const prg = commander_1.program
|
|
|
75
74
|
.parse()
|
|
76
75
|
.opts();
|
|
77
76
|
const colorwarn = prg.colors ? ansi_colors_1.default.red : (x) => x;
|
|
78
|
-
const jsrepolocation = (
|
|
77
|
+
const jsrepolocation = (prg.jsrepo ?? "'central'")
|
|
79
78
|
.split(',')
|
|
80
79
|
.map((x) => x === "'central'" ? 'https://raw.githubusercontent.com/RetireJS/retire.js/master/repository/jsrepository.json' : x);
|
|
81
|
-
const ignorefile =
|
|
82
|
-
const scanpath =
|
|
80
|
+
const ignorefile = prg.ignoreFile ?? defaultIgnoreFiles.filter((x) => fs_1.default.existsSync(x))[0];
|
|
81
|
+
const scanpath = prg.path ?? prg.jspath ?? '.';
|
|
83
82
|
const log = reporting.open({
|
|
84
83
|
colors: !!prg.colors,
|
|
85
84
|
colorwarn,
|
|
@@ -89,7 +88,7 @@ const log = reporting.open({
|
|
|
89
88
|
path: scanpath,
|
|
90
89
|
verbose: !!prg.verbose,
|
|
91
90
|
});
|
|
92
|
-
const severity =
|
|
91
|
+
const severity = prg.severity ?? 'none';
|
|
93
92
|
if (!(severity in types_1.severityLevels)) {
|
|
94
93
|
exitWithError(`Error: Invalid severity level (${severity}). Valid levels are: ${Object.keys(types_1.severityLevels).join(', ')}`);
|
|
95
94
|
}
|
|
@@ -97,15 +96,15 @@ const config = {
|
|
|
97
96
|
path: scanpath,
|
|
98
97
|
ignore: {
|
|
99
98
|
paths: [],
|
|
100
|
-
pathsAsString:
|
|
99
|
+
pathsAsString: prg.ignore?.split(',')?.map((x) => path_1.default.resolve(x)) ?? [],
|
|
101
100
|
descriptors: [],
|
|
102
101
|
},
|
|
103
102
|
colorwarn,
|
|
104
103
|
nocache: prg.nocache ? true : false,
|
|
105
|
-
cachedir:
|
|
104
|
+
cachedir: prg.cachedir ?? path_1.default.resolve(os_1.default.tmpdir(), '.retire-cache/'),
|
|
106
105
|
log: log,
|
|
107
106
|
severity: severity,
|
|
108
|
-
exitwith:
|
|
107
|
+
exitwith: prg.exitwith ?? 13,
|
|
109
108
|
includeOsv: !!prg.includeOsv,
|
|
110
109
|
verbose: !!prg.verbose,
|
|
111
110
|
proxy: prg.proxy,
|
|
@@ -146,7 +145,9 @@ if (ignorefile) {
|
|
|
146
145
|
catch (e) {
|
|
147
146
|
exitWithError(`Error: Invalid ignore file: ${ignorefile}`);
|
|
148
147
|
}
|
|
149
|
-
const ignoredPaths =
|
|
148
|
+
const ignoredPaths = config.ignore.descriptors
|
|
149
|
+
?.map((x) => ('path' in x ? x.path : undefined))
|
|
150
|
+
?.filter((x) => x != undefined) ?? [];
|
|
150
151
|
config.ignore.pathsAsString = config.ignore.pathsAsString.concat(ignoredPaths);
|
|
151
152
|
}
|
|
152
153
|
else {
|
|
@@ -168,8 +169,7 @@ scanner.on('vulnerable-dependency-found', (result) => {
|
|
|
168
169
|
const levels = result.results.map((r) => {
|
|
169
170
|
return r.vulnerabilities
|
|
170
171
|
? r.vulnerabilities.map((v) => {
|
|
171
|
-
|
|
172
|
-
return types_1.severityLevels[(_a = v.severity) !== null && _a !== void 0 ? _a : 'critical'];
|
|
172
|
+
return types_1.severityLevels[v.severity ?? 'critical'];
|
|
173
173
|
})
|
|
174
174
|
: [];
|
|
175
175
|
});
|
package/lib/depsdev.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
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
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
4
|
};
|
|
@@ -47,53 +38,46 @@ function scoreToSeverity(score) {
|
|
|
47
38
|
return 'medium';
|
|
48
39
|
return 'low';
|
|
49
40
|
}
|
|
50
|
-
function loadAdvisory(packageName, version, id, options) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
extractors: {},
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
return (0, retire_1.check)(packageName, version, simplifiedRepo);
|
|
79
|
-
});
|
|
41
|
+
async function loadAdvisory(packageName, version, id, options) {
|
|
42
|
+
const osvAdvisory = await loadJson(`https://api.osv.dev/v1/vulns/${id}`, options);
|
|
43
|
+
const advisory = await loadJson(`https://api.deps.dev/v3alpha/advisories/${id}`, options);
|
|
44
|
+
if (!advisory || !osvAdvisory)
|
|
45
|
+
return [];
|
|
46
|
+
const simplifiedRepo = {
|
|
47
|
+
[packageName]: {
|
|
48
|
+
vulnerabilities: osvAdvisory.affected
|
|
49
|
+
.map(({ ranges }) => ranges.map(({ events }) => ({
|
|
50
|
+
atOrAbove: events[0].introduced,
|
|
51
|
+
below: events[0].fixed,
|
|
52
|
+
severity: scoreToSeverity(advisory.cvss3Score),
|
|
53
|
+
cwe: osvAdvisory.database_specific?.cwe_ids ?? [],
|
|
54
|
+
identifiers: {
|
|
55
|
+
githubID: id,
|
|
56
|
+
CVE: osvAdvisory.aliases.filter((x) => x.startsWith('CVE-')),
|
|
57
|
+
summary: advisory.title,
|
|
58
|
+
},
|
|
59
|
+
info: osvAdvisory.references.map(({ url }) => url),
|
|
60
|
+
})))
|
|
61
|
+
.reduce((a, b) => a.concat(b), []),
|
|
62
|
+
extractors: {},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
return (0, retire_1.check)(packageName, version, simplifiedRepo);
|
|
80
66
|
}
|
|
81
|
-
function checkOSV(packageName, version, options) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (!versionInfo)
|
|
86
|
-
return [];
|
|
87
|
-
if (versionInfo.advisoryKeys.length == 0)
|
|
88
|
-
return [];
|
|
89
|
-
const comps = yield Promise.all(versionInfo.advisoryKeys.map(({ id }) => loadAdvisory(packageName, version, id, options)));
|
|
90
|
-
const flattened = comps.reduce((a, b) => a.concat(b), []);
|
|
91
|
-
return flattened.map((x) => { var _a; return (_a = x.vulnerabilities) !== null && _a !== void 0 ? _a : []; }).reduce((a, b) => a.concat(b), []);
|
|
92
|
-
}
|
|
93
|
-
catch (e) {
|
|
94
|
-
options.log.warn('Error checking OSV: ' + e);
|
|
67
|
+
async function checkOSV(packageName, version, options) {
|
|
68
|
+
try {
|
|
69
|
+
const versionInfo = await getVulnerabilities(packageName, version, options);
|
|
70
|
+
if (!versionInfo)
|
|
95
71
|
return [];
|
|
96
|
-
|
|
97
|
-
|
|
72
|
+
if (versionInfo.advisoryKeys.length == 0)
|
|
73
|
+
return [];
|
|
74
|
+
const comps = await Promise.all(versionInfo.advisoryKeys.map(({ id }) => loadAdvisory(packageName, version, id, options)));
|
|
75
|
+
const flattened = comps.reduce((a, b) => a.concat(b), []);
|
|
76
|
+
return flattened.map((x) => x.vulnerabilities ?? []).reduce((a, b) => a.concat(b), []);
|
|
77
|
+
}
|
|
78
|
+
catch (e) {
|
|
79
|
+
options.log.warn('Error checking OSV: ' + e);
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
98
82
|
}
|
|
99
83
|
exports.checkOSV = checkOSV;
|
package/lib/repo.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Options, Repository } from './types';
|
|
2
|
+
import * as z from 'zod';
|
|
3
|
+
export declare function validateRepository(repo: Repository, replacer?: Options['process']): z.SafeParseReturnType<unknown, Repository>;
|
|
2
4
|
export declare function asbowerrepo(jsRepo: Repository): Repository;
|
|
3
5
|
export declare function loadrepository(repoUrl: string, options: Options): Promise<Repository>;
|
|
4
6
|
export declare function loadrepositoryFromFile(filepath: string, options: Options): Promise<Repository>;
|
package/lib/repo.js
CHANGED
|
@@ -22,17 +22,8 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
22
22
|
__setModuleDefault(result, mod);
|
|
23
23
|
return result;
|
|
24
24
|
};
|
|
25
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
-
});
|
|
33
|
-
};
|
|
34
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
-
exports.loadrepositoryFromFile = exports.loadrepository = exports.asbowerrepo = void 0;
|
|
26
|
+
exports.loadrepositoryFromFile = exports.loadrepository = exports.asbowerrepo = exports.validateRepository = void 0;
|
|
36
27
|
const fs = __importStar(require("fs"));
|
|
37
28
|
const path = __importStar(require("path"));
|
|
38
29
|
const http = __importStar(require("http"));
|
|
@@ -40,87 +31,218 @@ const https = __importStar(require("https"));
|
|
|
40
31
|
const retire = __importStar(require("./retire"));
|
|
41
32
|
const URL = __importStar(require("url"));
|
|
42
33
|
const proxy_agent_1 = require("proxy-agent");
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
34
|
+
const z = __importStar(require("zod"));
|
|
35
|
+
const types_1 = require("./types");
|
|
36
|
+
function validateRepository(repo, replacer) {
|
|
37
|
+
const keys = Object.keys(types_1.severityLevels);
|
|
38
|
+
const versionValidator = z.string().regex(/^[\d.]+([a-zA-Z\d.-]+)?$/);
|
|
39
|
+
const numericString = z.string().regex(/^[\d]+$/);
|
|
40
|
+
const vulnValidator = z
|
|
41
|
+
.object({
|
|
42
|
+
below: versionValidator,
|
|
43
|
+
atOrAbove: versionValidator.optional(),
|
|
44
|
+
severity: z.enum(keys),
|
|
45
|
+
cwe: z.array(z.string().regex(/^CWE-[0-9]+$/)).min(1),
|
|
46
|
+
identifiers: z
|
|
47
|
+
.object({
|
|
48
|
+
CVE: z.array(z.string().regex(/^CVE-[0-9X-]+$/)).optional(),
|
|
49
|
+
bug: z
|
|
50
|
+
.string()
|
|
51
|
+
.regex(/^[a-z0-9-]+$/i)
|
|
52
|
+
.optional(),
|
|
53
|
+
issue: numericString.optional(),
|
|
54
|
+
summary: z.string().min(3).optional(),
|
|
55
|
+
githubID: z
|
|
56
|
+
.string()
|
|
57
|
+
.regex(/^GHSA[A-Z0-9-]+$/i)
|
|
58
|
+
.optional(),
|
|
59
|
+
osvdb: z.array(numericString).optional(),
|
|
60
|
+
gist: z
|
|
61
|
+
.string()
|
|
62
|
+
.regex(/^[a-z0-9-]+\/[a-f0-9]+$/i)
|
|
63
|
+
.optional(),
|
|
64
|
+
tenable: numericString.optional(),
|
|
65
|
+
blog: z
|
|
66
|
+
.string()
|
|
67
|
+
.min(10)
|
|
68
|
+
.regex(/^[:a-z0-9/-]+$/)
|
|
69
|
+
.optional(),
|
|
70
|
+
release: z.string().min(5).optional(),
|
|
71
|
+
PR: numericString.optional(),
|
|
72
|
+
retid: z
|
|
73
|
+
.string()
|
|
74
|
+
.regex(/^[\d]+$/)
|
|
75
|
+
.optional(),
|
|
76
|
+
})
|
|
77
|
+
.strict()
|
|
78
|
+
.superRefine((o, ctx) => {
|
|
79
|
+
if (Object.keys(o).filter((k) => k != 'summary').length == 0)
|
|
80
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Must have at least one identifier' });
|
|
81
|
+
const ids = Object.values(o)
|
|
82
|
+
.map((x) => (Array.isArray(x) ? x : [x]))
|
|
83
|
+
.reduce((a, b) => a.concat(b), []).length;
|
|
84
|
+
if (ids == 0)
|
|
85
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Must have at least one identifier' });
|
|
86
|
+
}),
|
|
87
|
+
info: z.array(z.string().regex(/^https?:\/\/.+/)),
|
|
88
|
+
})
|
|
89
|
+
.strict();
|
|
90
|
+
const regexValidator = z.string().superRefine((s, ctx) => {
|
|
91
|
+
if (ctx.path[0] == 'dont check')
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
new RegExp(s);
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
ctx.addIssue({
|
|
98
|
+
code: z.ZodIssueCode.custom,
|
|
99
|
+
message: 'Invalid regex: ' + s,
|
|
70
100
|
});
|
|
71
|
-
|
|
72
|
-
|
|
101
|
+
}
|
|
102
|
+
if (s.includes('[]'))
|
|
103
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Regex must not contain []: ' + s });
|
|
104
|
+
if (s.includes('{}'))
|
|
105
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Regex must not contain {}: ' + s });
|
|
106
|
+
[/.*[^\\]\{[^0-9,\\]\}.*/, /[^,0-9\\]\}/, /[^\\]\{[^,0-9]/].forEach((r) => {
|
|
107
|
+
if (r.test(s))
|
|
108
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'There is something odd with this regex: ' + s });
|
|
73
109
|
});
|
|
110
|
+
let versionMatcher = '§§version§§';
|
|
111
|
+
if (replacer)
|
|
112
|
+
versionMatcher = JSON.parse(`"${replacer(versionMatcher)}"`);
|
|
113
|
+
const versionIndex = s.indexOf(versionMatcher);
|
|
114
|
+
if (versionIndex == -1 || (versionIndex > 0 && s.substring(versionIndex - 1, versionIndex) == '\\')) {
|
|
115
|
+
ctx.addIssue({ code: z.ZodIssueCode.custom, message: 'Regex must contain (§§version§§): ' + s });
|
|
116
|
+
}
|
|
117
|
+
else if (s.replace(/\(\?:/g, '').replace(/\\\(/g, '').split(/\(/)[1].indexOf(versionMatcher) != 0) {
|
|
118
|
+
ctx.addIssue({
|
|
119
|
+
code: z.ZodIssueCode.custom,
|
|
120
|
+
message: 'Regex must contain (§§version§§) as first capture group: ' + s.replace(/\(\?:/g, '').replace(/\\\(/g, ''),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
74
123
|
});
|
|
124
|
+
const replaceValidator = z
|
|
125
|
+
.string()
|
|
126
|
+
.regex(/^\/(.*[^\\])\/([^/]+)\/$/, 'RegExp error - should be on format "/search/replacement/"');
|
|
127
|
+
const validator = z.record(z
|
|
128
|
+
.object({
|
|
129
|
+
bowername: z.array(z.string().regex(/^[a-z0-9.-]+$/i)).optional(),
|
|
130
|
+
basePurl: z
|
|
131
|
+
.string()
|
|
132
|
+
.regex(/^pkg:[a-z0-9/]+$/i)
|
|
133
|
+
.optional(),
|
|
134
|
+
npmname: z
|
|
135
|
+
.string()
|
|
136
|
+
.regex(/^[a-z0-9.-]+$/i)
|
|
137
|
+
.optional(),
|
|
138
|
+
vulnerabilities: z.array(vulnValidator),
|
|
139
|
+
extractors: z
|
|
140
|
+
.object({
|
|
141
|
+
func: z.array(z.string().min(5)).optional(),
|
|
142
|
+
uri: z.array(regexValidator).optional(),
|
|
143
|
+
filename: z.array(regexValidator).optional(),
|
|
144
|
+
filecontent: z.array(regexValidator).optional(),
|
|
145
|
+
filecontentreplace: z.array(replaceValidator).optional(),
|
|
146
|
+
hashes: z.record(z.string().regex(/^[a-f0-9]+$/i), versionValidator).optional(),
|
|
147
|
+
})
|
|
148
|
+
.strict(),
|
|
149
|
+
})
|
|
150
|
+
.strict());
|
|
151
|
+
return validator.safeParse(repo);
|
|
75
152
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
153
|
+
exports.validateRepository = validateRepository;
|
|
154
|
+
function formatValidationError(error) {
|
|
155
|
+
return JSON.stringify(error.format(), (key, value) => (Array.isArray(value) && value.length === 0 ? undefined : value), 2);
|
|
156
|
+
}
|
|
157
|
+
async function loadJson(url, options) {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
options.log.info('Downloading ' + url + ' ...');
|
|
160
|
+
const reqOptions = { ...URL.parse(url), method: 'GET' };
|
|
161
|
+
const proxyUri = options.proxy || process.env.http_proxy;
|
|
162
|
+
if (proxyUri) {
|
|
163
|
+
reqOptions.agent = new proxy_agent_1.ProxyAgent({
|
|
164
|
+
getProxyForUrl: () => proxyUri,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (options.insecure) {
|
|
168
|
+
reqOptions.rejectUnauthorized = false;
|
|
169
|
+
}
|
|
170
|
+
if (options.cacertbuf) {
|
|
171
|
+
reqOptions.ca = [options.cacertbuf];
|
|
172
|
+
}
|
|
173
|
+
const req = (url.startsWith('http:') ? http : https).get(reqOptions, (res) => {
|
|
174
|
+
if (res.statusCode != 200)
|
|
175
|
+
return reject(`Error downloading: ${url}: HTTP ${res.statusCode} ${res.statusMessage}`);
|
|
176
|
+
const data = [];
|
|
177
|
+
res.on('data', (c) => data.push(c));
|
|
178
|
+
res.on('end', () => {
|
|
179
|
+
let d = Buffer.concat(data).toString();
|
|
180
|
+
d = options.process ? options.process(d) : d;
|
|
181
|
+
const json = JSON.parse(d);
|
|
182
|
+
const vresult = validateRepository(json, options.process);
|
|
183
|
+
if (vresult.success) {
|
|
184
|
+
resolve(json);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
reject(`Invalid repository from ${url}: ${formatValidationError(vresult.error)}`);
|
|
83
188
|
}
|
|
84
|
-
data = options.process ? options.process(data) : data;
|
|
85
|
-
resolve(JSON.parse(data));
|
|
86
189
|
});
|
|
87
190
|
});
|
|
191
|
+
req.on('error', (e) => reject(`Error downloading: ${url}: ${e}`));
|
|
192
|
+
req.end();
|
|
88
193
|
});
|
|
89
194
|
}
|
|
90
|
-
function
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
195
|
+
async function loadJsonFromFile(file, options) {
|
|
196
|
+
options.log.debug('Reading ' + file + ' ...');
|
|
197
|
+
return new Promise((resolve, reject) => {
|
|
198
|
+
fs.readFile(file, { encoding: 'utf8' }, (err, data) => {
|
|
199
|
+
if (err) {
|
|
200
|
+
return reject(err.toString());
|
|
201
|
+
}
|
|
202
|
+
data = options.process ? options.process(data) : data;
|
|
203
|
+
const json = JSON.parse(data);
|
|
204
|
+
const vresult = validateRepository(json, options.process);
|
|
205
|
+
if (vresult.success) {
|
|
206
|
+
resolve(json);
|
|
101
207
|
}
|
|
102
208
|
else {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
209
|
+
reject(`Invalid repository from ${file}: ${formatValidationError(vresult.error)}`);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async function loadFromCache(url, cachedir, options) {
|
|
215
|
+
const cacheIndex = path.resolve(cachedir, 'index.json');
|
|
216
|
+
if (!fs.existsSync(cachedir))
|
|
217
|
+
fs.mkdirSync(cachedir);
|
|
218
|
+
const cache = fs.existsSync(cacheIndex) ? JSON.parse(fs.readFileSync(cacheIndex, 'utf-8')) : {};
|
|
219
|
+
const now = new Date().getTime();
|
|
220
|
+
if (cache[url]) {
|
|
221
|
+
if (now - cache[url].date < 60 * 60 * 1000) {
|
|
222
|
+
options.log.info('Loading from cache: ' + url);
|
|
223
|
+
return loadJsonFromFile(path.resolve(cachedir, cache[url].file), options);
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
if (fs.existsSync(path.resolve(cachedir, cache[url].date + '.json'))) {
|
|
227
|
+
try {
|
|
228
|
+
fs.unlinkSync(path.resolve(cachedir, cache[url].date + '.json'));
|
|
229
|
+
}
|
|
230
|
+
catch (error) {
|
|
231
|
+
if (error != null && typeof error == 'object' && 'code' in error && error.code !== 'ENOENT') {
|
|
232
|
+
throw error;
|
|
106
233
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
throw error;
|
|
110
|
-
}
|
|
111
|
-
else {
|
|
112
|
-
console.warn('Could not delete cache. Ignore this error if you are running multiple retire.js in parallel');
|
|
113
|
-
}
|
|
234
|
+
else {
|
|
235
|
+
console.warn('Could not delete cache. Ignore this error if you are running multiple retire.js in parallel');
|
|
114
236
|
}
|
|
115
237
|
}
|
|
116
238
|
}
|
|
117
239
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
240
|
+
}
|
|
241
|
+
const data = await loadJson(url, options);
|
|
242
|
+
cache[url] = { date: now, file: now + '.json' };
|
|
243
|
+
fs.writeFileSync(path.resolve(cachedir, cache[url].file), JSON.stringify(data), { encoding: 'utf8' });
|
|
244
|
+
fs.writeFileSync(cacheIndex, JSON.stringify(cache), { encoding: 'utf8' });
|
|
245
|
+
return data;
|
|
124
246
|
}
|
|
125
247
|
function asbowerrepo(jsRepo) {
|
|
126
248
|
const result = {};
|
|
@@ -133,21 +255,16 @@ function asbowerrepo(jsRepo) {
|
|
|
133
255
|
return result;
|
|
134
256
|
}
|
|
135
257
|
exports.asbowerrepo = asbowerrepo;
|
|
136
|
-
function loadrepository(repoUrl, options) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
return yield loadFromCache(repoUrl, options.cachedir, options);
|
|
144
|
-
});
|
|
258
|
+
async function loadrepository(repoUrl, options) {
|
|
259
|
+
options = { ...options, process: retire.replaceVersion };
|
|
260
|
+
if (options.nocache) {
|
|
261
|
+
return await loadJson(repoUrl, options);
|
|
262
|
+
}
|
|
263
|
+
return await loadFromCache(repoUrl, options.cachedir, options);
|
|
145
264
|
}
|
|
146
265
|
exports.loadrepository = loadrepository;
|
|
147
|
-
function loadrepositoryFromFile(filepath, options) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
return yield loadJsonFromFile(filepath, options);
|
|
151
|
-
});
|
|
266
|
+
async function loadrepositoryFromFile(filepath, options) {
|
|
267
|
+
options = { ...options, process: retire.replaceVersion };
|
|
268
|
+
return await loadJsonFromFile(filepath, options);
|
|
152
269
|
}
|
|
153
270
|
exports.loadrepositoryFromFile = loadrepositoryFromFile;
|
package/lib/reporters/console.js
CHANGED
|
@@ -45,9 +45,8 @@ function printResults(logger, finding, config) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
function printVulnerability(component, config) {
|
|
48
|
-
var _a;
|
|
49
48
|
let string = '';
|
|
50
|
-
|
|
49
|
+
component.vulnerabilities?.forEach((vulnerability) => {
|
|
51
50
|
string += config.outputformat === 'clean' ? '\n ' : ' ';
|
|
52
51
|
if (vulnerability.severity) {
|
|
53
52
|
string += `severity: ${vulnerability.severity}; `;
|
package/lib/reporting.d.ts
CHANGED
package/lib/reporting.js
CHANGED
|
@@ -115,8 +115,7 @@ function configureFileWriter(config) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
function open(config) {
|
|
118
|
-
|
|
119
|
-
verbose = (_a = config.verbose) !== null && _a !== void 0 ? _a : false;
|
|
118
|
+
verbose = config.verbose ?? false;
|
|
120
119
|
if (config.colors)
|
|
121
120
|
colorwarn = config.colorwarn;
|
|
122
121
|
const format = config.outputformat || 'console';
|
package/lib/retire.js
CHANGED
package/lib/scanner.js
CHANGED
|
@@ -40,18 +40,17 @@ const hash = {
|
|
|
40
40
|
};
|
|
41
41
|
function emitResults(finding, options) {
|
|
42
42
|
if (options.includeOsv === true) {
|
|
43
|
-
Promise.all(finding.results.map((r) => (0, depsdev_1.checkOSV)(r.component, r.version, options).then((v) =>
|
|
43
|
+
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));
|
|
44
44
|
}
|
|
45
45
|
else {
|
|
46
46
|
filterAndEmitResults(finding, options);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
function getIdentifiers(v) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
.concat(
|
|
53
|
-
.concat(
|
|
54
|
-
.concat((_h = (_g = v.identifiers) === null || _g === void 0 ? void 0 : _g.githubID) !== null && _h !== void 0 ? _h : []);
|
|
50
|
+
return (v.identifiers?.CVE ?? [])
|
|
51
|
+
.concat(v.identifiers?.bug ?? [])
|
|
52
|
+
.concat(v.identifiers?.issue ?? [])
|
|
53
|
+
.concat(v.identifiers?.githubID ?? []);
|
|
55
54
|
}
|
|
56
55
|
function uniqueVulnerabilities(vulnerabilities) {
|
|
57
56
|
if (!vulnerabilities)
|
|
@@ -78,45 +77,43 @@ function filterAndEmitResults(finding, options) {
|
|
|
78
77
|
}
|
|
79
78
|
}
|
|
80
79
|
function shouldIgnorePath(fileSpecs, ignores) {
|
|
81
|
-
|
|
82
|
-
return ((_b = (_a = ignores.paths) === null || _a === void 0 ? void 0 : _a.some((i) => {
|
|
80
|
+
return (ignores.paths?.some((i) => {
|
|
83
81
|
return fileSpecs.some((j) => i.test(j) || i.test(path.resolve(j)));
|
|
84
|
-
})
|
|
82
|
+
}) ?? false);
|
|
85
83
|
}
|
|
86
84
|
function removeIgnored(results, ignores) {
|
|
87
85
|
if (!('descriptors' in ignores))
|
|
88
86
|
return;
|
|
89
87
|
results.forEach((r) => {
|
|
90
|
-
var _a, _b;
|
|
91
88
|
if (!('vulnerabilities' in r))
|
|
92
89
|
return;
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
ignores.descriptors
|
|
91
|
+
?.filter((d) => 'component' in d)
|
|
92
|
+
.forEach((i) => {
|
|
95
93
|
if (r.component !== i.component)
|
|
96
94
|
return;
|
|
97
95
|
if (i.version && r.version !== i.version)
|
|
98
96
|
return;
|
|
99
97
|
if (i.severity) {
|
|
100
98
|
//Remove vulnerabilities with the severity we want to drop
|
|
101
|
-
r.vulnerabilities =
|
|
99
|
+
r.vulnerabilities = r.vulnerabilities?.filter((v) => v.severity != i.severity);
|
|
102
100
|
return;
|
|
103
101
|
}
|
|
104
102
|
if (i.identifiers) {
|
|
105
|
-
removeIgnoredVulnerabilitiesByIdentifier(
|
|
103
|
+
removeIgnoredVulnerabilitiesByIdentifier({ ...i.identifiers }, r);
|
|
106
104
|
return;
|
|
107
105
|
}
|
|
108
106
|
r.vulnerabilities = [];
|
|
109
107
|
});
|
|
110
|
-
if (
|
|
108
|
+
if (r.vulnerabilities?.length === 0)
|
|
111
109
|
delete r.vulnerabilities;
|
|
112
110
|
});
|
|
113
111
|
}
|
|
114
112
|
function removeIgnoredVulnerabilitiesByIdentifier(identifiers, result) {
|
|
115
|
-
|
|
116
|
-
result.vulnerabilities = (_a = result.vulnerabilities) === null || _a === void 0 ? void 0 : _a.filter((v) => {
|
|
113
|
+
result.vulnerabilities = result.vulnerabilities?.filter((v) => {
|
|
117
114
|
if (!('identifiers' in v))
|
|
118
115
|
return true;
|
|
119
|
-
return !Object.entries(identifiers || {}).every(([key, value]) => hasIdentifier(
|
|
116
|
+
return !Object.entries(identifiers || {}).every(([key, value]) => hasIdentifier({ ...v.identifiers }, key, value));
|
|
120
117
|
});
|
|
121
118
|
}
|
|
122
119
|
function hasIdentifier(identifiers, key, value) {
|
package/lib/types.d.ts
CHANGED
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.
|
|
5
|
+
"version": "4.4.1",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,24 +14,24 @@
|
|
|
14
14
|
"main": "./lib/retire.js",
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"ansi-colors": "^4.1.1",
|
|
17
|
-
"commander": "^10.0.
|
|
18
|
-
"proxy-agent": "^6.
|
|
19
|
-
"uuid": "^9.0.
|
|
17
|
+
"commander": "^10.0.1",
|
|
18
|
+
"proxy-agent": "^6.4.0",
|
|
19
|
+
"uuid": "^9.0.1",
|
|
20
20
|
"walkdir": "0.4.1",
|
|
21
|
-
"zod": "^3.
|
|
21
|
+
"zod": "^3.22.4"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^18.13.0",
|
|
25
25
|
"@types/uuid": "^9.0.0",
|
|
26
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
27
|
-
"@typescript-eslint/parser": "^
|
|
26
|
+
"@typescript-eslint/eslint-plugin": "^6.11.0",
|
|
27
|
+
"@typescript-eslint/parser": "^6.11.0",
|
|
28
28
|
"chai": "^4.3.4",
|
|
29
29
|
"eslint": "^8.34.0",
|
|
30
|
-
"eslint-config-prettier": "^
|
|
30
|
+
"eslint-config-prettier": "^9.0.0",
|
|
31
31
|
"jsonschema": "^1.4.1",
|
|
32
32
|
"libxmljs": "^1.0.8",
|
|
33
33
|
"mocha": "^10.2.0",
|
|
34
|
-
"prettier": "^
|
|
34
|
+
"prettier": "^3.1.0",
|
|
35
35
|
"typescript": "^5.0.4"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"check": "npm run lint && npm run typecheck"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
|
-
"node": ">=
|
|
46
|
+
"node": ">= 18.0.0"
|
|
47
47
|
},
|
|
48
48
|
"keywords": [
|
|
49
49
|
"sbom",
|