retire 6.0.0 → 6.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 +17 -1
- package/lib/cli.js +5 -0
- package/lib/repo.d.ts +2 -1
- package/lib/repo.js +22 -19
- package/lib/retire.js +1 -1
- package/lib/types.d.ts +10 -20
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [6.1.0]
|
|
4
|
+
|
|
5
|
+
### Improvements
|
|
6
|
+
|
|
7
|
+
- Repository validation errors (for an invalid `--jsrepo`, and from `repository/validate`) are now listed one per line with the location of each problem, instead of as a nested JSON tree:
|
|
8
|
+
```
|
|
9
|
+
✖ Invalid option: expected one of "none"|"low"|"medium"|"high"|"critical"
|
|
10
|
+
→ at jquery.vulnerabilities[0].severity
|
|
11
|
+
```
|
|
12
|
+
- Positional arguments (e.g. `retire .` or `retire src/`) are still ignored, but now print a warning. Use `--path <path>` to choose what to scan.
|
|
13
|
+
- Upgraded `commander` to 14 and `zod` to 4.
|
|
14
|
+
|
|
15
|
+
### Bugfixes
|
|
16
|
+
|
|
17
|
+
- Repository validation crashed with a `TypeError` on extractor regexes that contain `§§version§§` without a capture group (e.g. `jquery-§§version§§\.js`), instead of reporting the invalid regex. It now reports `Regex must contain (§§version§§) as first capture group`.
|
|
18
|
+
|
|
3
19
|
## [6.0.0]
|
|
4
20
|
|
|
5
21
|
### Breaking changes
|
|
6
22
|
|
|
7
23
|
- Requires Node.js 20.19+ or 22.12+ (previously 18+).
|
|
8
|
-
- `--proxy` (and the `http_proxy` environment variable) now only supports `http://` and `https://` proxy URLs. SOCKS (`socks://`, `socks5://`, ...) and PAC (`pac+http://`, ...) proxies are no longer supported and fail with an `Unsupported proxy protocol` error.
|
|
24
|
+
- `--proxy` (and the `http_proxy` environment variable) now only supports `http://` and `https://` proxy URLs. SOCKS (`socks://`, `socks5://`, ...) and PAC (`pac+http://`, ...) proxies are no longer supported and fail with an `Unsupported proxy protocol` error (let us know if socks or pac are needed and we can re-add).
|
|
9
25
|
|
|
10
26
|
### Improvements
|
|
11
27
|
|
package/lib/cli.js
CHANGED
|
@@ -79,6 +79,8 @@ const prg = commander_1.program
|
|
|
79
79
|
.option('--cacert <path>', 'Use the specified certificate file to verify the peer used for fetching remote jsrepo/noderepo files')
|
|
80
80
|
.option('--includeOsv', 'Include OSV advisories in the output')
|
|
81
81
|
.option('--deep', 'Deep scan (slower and experimental)')
|
|
82
|
+
// Positional arguments were never used, but were silently accepted before commander 13.
|
|
83
|
+
.allowExcessArguments()
|
|
82
84
|
.parse()
|
|
83
85
|
.opts();
|
|
84
86
|
const red = (x) => `\u001b[31m${x}\u001b[39m`;
|
|
@@ -180,6 +182,9 @@ try {
|
|
|
180
182
|
throw new Error(`Invalid severity level (${severity}). Valid levels are: ${Object.keys(types_1.severityLevels).join(', ')}`);
|
|
181
183
|
}
|
|
182
184
|
log.info(`retire.js v${retire.version}`);
|
|
185
|
+
if (commander_1.program.args.length > 0) {
|
|
186
|
+
log.warn(colorwarn(`Warning: Ignoring unexpected argument(s) ${commander_1.program.args.map((arg) => `"${arg}"`).join(' ')}. Use --path <path> to choose what to scan (scanning ${scanpath}).`));
|
|
187
|
+
}
|
|
183
188
|
if (prg.cacert) {
|
|
184
189
|
if (!fs_1.default.existsSync(prg.cacert)) {
|
|
185
190
|
throw new Error(`Could not read cacert file: ${prg.cacert}`);
|
package/lib/repo.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Options, Repository } from './types';
|
|
2
2
|
import * as z from 'zod';
|
|
3
|
-
export declare function validateRepository(repo: Repository, replacer?: Options['process']): z.
|
|
3
|
+
export declare function validateRepository(repo: Repository, replacer?: Options['process']): z.ZodSafeParseResult<Repository>;
|
|
4
|
+
export declare function formatValidationError(error: z.ZodError): string;
|
|
4
5
|
export declare function asbowerrepo(jsRepo: Repository): Repository;
|
|
5
6
|
export declare function loadrepository(repoUrl: string, options: Options): Promise<Repository>;
|
|
6
7
|
export declare function loadrepositoryFromFile(filepath: string, options: Options): Promise<Repository>;
|
package/lib/repo.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.validateRepository = validateRepository;
|
|
37
|
+
exports.formatValidationError = formatValidationError;
|
|
37
38
|
exports.asbowerrepo = asbowerrepo;
|
|
38
39
|
exports.loadrepository = loadrepository;
|
|
39
40
|
exports.loadrepositoryFromFile = loadrepositoryFromFile;
|
|
@@ -88,47 +89,45 @@ function validateRepository(repo, replacer) {
|
|
|
88
89
|
.strict()
|
|
89
90
|
.superRefine((o, ctx) => {
|
|
90
91
|
if (Object.keys(o).filter((k) => k != 'summary').length == 0)
|
|
91
|
-
ctx.addIssue({ code:
|
|
92
|
+
ctx.addIssue({ code: 'custom', message: 'Must have at least one identifier' });
|
|
92
93
|
const ids = Object.values(o)
|
|
93
94
|
.map((x) => (Array.isArray(x) ? x : [x]))
|
|
94
95
|
.reduce((a, b) => a.concat(b), []).length;
|
|
95
96
|
if (ids == 0)
|
|
96
|
-
ctx.addIssue({ code:
|
|
97
|
+
ctx.addIssue({ code: 'custom', message: 'Must have at least one identifier' });
|
|
97
98
|
}),
|
|
98
99
|
details: z.string().optional(),
|
|
99
100
|
info: z.array(z.string().regex(/^https?:\/\/.+/)),
|
|
100
101
|
})
|
|
101
102
|
.strict();
|
|
102
103
|
const regexValidator = z.string().superRefine((s, ctx) => {
|
|
103
|
-
if (ctx.path[0] == 'dont check')
|
|
104
|
-
return;
|
|
105
104
|
try {
|
|
106
105
|
new RegExp(s);
|
|
107
106
|
}
|
|
108
107
|
catch {
|
|
109
108
|
ctx.addIssue({
|
|
110
|
-
code:
|
|
109
|
+
code: 'custom',
|
|
111
110
|
message: 'Invalid regex: ' + s,
|
|
112
111
|
});
|
|
113
112
|
}
|
|
114
113
|
if (s.includes('[]'))
|
|
115
|
-
ctx.addIssue({ code:
|
|
114
|
+
ctx.addIssue({ code: 'custom', message: 'Regex must not contain []: ' + s });
|
|
116
115
|
if (s.includes('{}'))
|
|
117
|
-
ctx.addIssue({ code:
|
|
116
|
+
ctx.addIssue({ code: 'custom', message: 'Regex must not contain {}: ' + s });
|
|
118
117
|
[/.*[^\\]\{[^0-9,\\]\}.*/, /[^,0-9\\]\}/, /[^\\]\{[^,0-9]/].forEach((r) => {
|
|
119
118
|
if (r.test(s))
|
|
120
|
-
ctx.addIssue({ code:
|
|
119
|
+
ctx.addIssue({ code: 'custom', message: 'There is something odd with this regex: ' + s });
|
|
121
120
|
});
|
|
122
121
|
let versionMatcher = '§§version§§';
|
|
123
122
|
if (replacer)
|
|
124
123
|
versionMatcher = JSON.parse(`"${replacer(versionMatcher)}"`);
|
|
125
124
|
const versionIndex = s.indexOf(versionMatcher);
|
|
126
125
|
if (versionIndex == -1 || (versionIndex > 0 && s.substring(versionIndex - 1, versionIndex) == '\\')) {
|
|
127
|
-
ctx.addIssue({ code:
|
|
126
|
+
ctx.addIssue({ code: 'custom', message: 'Regex must contain (§§version§§): ' + s });
|
|
128
127
|
}
|
|
129
|
-
else if (s.replace(/\(\?:/g, '').replace(/\\\(/g, '').split(/\(/)[1]
|
|
128
|
+
else if (s.replace(/\(\?:/g, '').replace(/\\\(/g, '').split(/\(/)[1]?.indexOf(versionMatcher) != 0) {
|
|
130
129
|
ctx.addIssue({
|
|
131
|
-
code:
|
|
130
|
+
code: 'custom',
|
|
132
131
|
message: 'Regex must contain (§§version§§) as first capture group: ' + s.replace(/\(\?:/g, '').replace(/\\\(/g, ''),
|
|
133
132
|
});
|
|
134
133
|
}
|
|
@@ -136,7 +135,7 @@ function validateRepository(repo, replacer) {
|
|
|
136
135
|
const replaceValidator = z
|
|
137
136
|
.string()
|
|
138
137
|
.regex(/^\/(.*[^\\])\/([^/]+)\/$/, 'RegExp error - should be on format "/search/replacement/"');
|
|
139
|
-
const
|
|
138
|
+
const entryValidator = (extractorValidator) => z
|
|
140
139
|
.object({
|
|
141
140
|
bowername: z.array(z.string().regex(/^[a-z0-9.-]+$/i)).optional(),
|
|
142
141
|
basePurl: z
|
|
@@ -151,9 +150,9 @@ function validateRepository(repo, replacer) {
|
|
|
151
150
|
extractors: z
|
|
152
151
|
.object({
|
|
153
152
|
func: z.array(z.string().min(5)).optional(),
|
|
154
|
-
uri: z.array(
|
|
155
|
-
filename: z.array(
|
|
156
|
-
filecontent: z.array(
|
|
153
|
+
uri: z.array(extractorValidator).optional(),
|
|
154
|
+
filename: z.array(extractorValidator).optional(),
|
|
155
|
+
filecontent: z.array(extractorValidator).optional(),
|
|
157
156
|
filecontentreplace: z.array(replaceValidator).optional(),
|
|
158
157
|
hashes: z.record(z.string().regex(/^[a-f0-9]+$/i), versionValidator).optional(),
|
|
159
158
|
ast: z.array(z.string()).optional(),
|
|
@@ -165,11 +164,15 @@ function validateRepository(repo, replacer) {
|
|
|
165
164
|
.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.]+)?)*/))
|
|
166
165
|
.optional(),
|
|
167
166
|
})
|
|
168
|
-
.strict()
|
|
167
|
+
.strict();
|
|
168
|
+
// "dont check" lists URIs that are deliberately not version extractors, so its regexes are not validated.
|
|
169
|
+
const validator = z
|
|
170
|
+
.object({ 'dont check': entryValidator(z.string()).optional() })
|
|
171
|
+
.catchall(entryValidator(regexValidator));
|
|
169
172
|
return validator.safeParse(repo);
|
|
170
173
|
}
|
|
171
174
|
function formatValidationError(error) {
|
|
172
|
-
return
|
|
175
|
+
return z.prettifyError(error);
|
|
173
176
|
}
|
|
174
177
|
async function loadJson(url, options) {
|
|
175
178
|
options.log.info('Downloading ' + url + ' ...');
|
|
@@ -198,7 +201,7 @@ async function loadJson(url, options) {
|
|
|
198
201
|
resolve(json);
|
|
199
202
|
}
|
|
200
203
|
else {
|
|
201
|
-
reject(`Invalid repository from ${url}
|
|
204
|
+
reject(`Invalid repository from ${url}:\n${formatValidationError(vresult.error)}`);
|
|
202
205
|
}
|
|
203
206
|
}
|
|
204
207
|
catch (error) {
|
|
@@ -222,7 +225,7 @@ async function loadJsonFromFile(file, options) {
|
|
|
222
225
|
resolve(json);
|
|
223
226
|
}
|
|
224
227
|
else {
|
|
225
|
-
reject(`Invalid repository from ${file}
|
|
228
|
+
reject(`Invalid repository from ${file}:\n${formatValidationError(vresult.error)}`);
|
|
226
229
|
}
|
|
227
230
|
}
|
|
228
231
|
catch (error) {
|
package/lib/retire.js
CHANGED
package/lib/types.d.ts
CHANGED
|
@@ -56,7 +56,13 @@ export declare const severityLevels: {
|
|
|
56
56
|
readonly critical: 4;
|
|
57
57
|
};
|
|
58
58
|
export type SeverityLevel = keyof typeof severityLevels;
|
|
59
|
-
export declare const severityParser: z.ZodEnum<
|
|
59
|
+
export declare const severityParser: z.ZodEnum<{
|
|
60
|
+
none: "none";
|
|
61
|
+
low: "low";
|
|
62
|
+
medium: "medium";
|
|
63
|
+
high: "high";
|
|
64
|
+
critical: "critical";
|
|
65
|
+
}>;
|
|
60
66
|
export type PathDescriptor = {
|
|
61
67
|
path: string;
|
|
62
68
|
justification?: string;
|
|
@@ -97,27 +103,11 @@ export type Options = {
|
|
|
97
103
|
};
|
|
98
104
|
export declare const ignoreFileParser: z.ZodArray<z.ZodIntersection<z.ZodObject<{
|
|
99
105
|
justification: z.ZodString;
|
|
100
|
-
},
|
|
101
|
-
justification: string;
|
|
102
|
-
}, {
|
|
103
|
-
justification: string;
|
|
104
|
-
}>, z.ZodUnion<[z.ZodObject<{
|
|
106
|
+
}, z.core.$strip>, z.ZodUnion<[z.ZodObject<{
|
|
105
107
|
path: z.ZodString;
|
|
106
|
-
},
|
|
107
|
-
path: string;
|
|
108
|
-
}, {
|
|
109
|
-
path: string;
|
|
110
|
-
}>, z.ZodObject<{
|
|
108
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
111
109
|
component: z.ZodString;
|
|
112
110
|
version: z.ZodOptional<z.ZodString>;
|
|
113
111
|
identifiers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
114
|
-
},
|
|
115
|
-
component: string;
|
|
116
|
-
version?: string | undefined;
|
|
117
|
-
identifiers?: Record<string, string> | undefined;
|
|
118
|
-
}, {
|
|
119
|
-
component: string;
|
|
120
|
-
version?: string | undefined;
|
|
121
|
-
identifiers?: Record<string, string> | undefined;
|
|
122
|
-
}>]>>, "many">;
|
|
112
|
+
}, z.core.$strip>]>>>;
|
|
123
113
|
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": "6.
|
|
5
|
+
"version": "6.1.0",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
"main": "./lib/retire.js",
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"astronomical": "^3.2.0",
|
|
17
|
-
"commander": "^
|
|
17
|
+
"commander": "^14.0.3",
|
|
18
18
|
"walkdir": "0.4.1",
|
|
19
|
-
"zod": "^
|
|
19
|
+
"zod": "^4.6.5"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@eslint/js": "^10.0.1",
|