verdaccio-security-filter 1.3.8 → 1.3.9
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/build/index.js +31 -23
- package/build/lib/cve-checker.d.ts +11 -4
- package/build/lib/cve-checker.js +202 -124
- package/package.json +1 -1
package/build/index.js
CHANGED
|
@@ -277,7 +277,7 @@ class SecurityFilterPlugin {
|
|
|
277
277
|
}
|
|
278
278
|
filter_metadata(packageInfo) {
|
|
279
279
|
return __awaiter(this, void 0, void 0, function* () {
|
|
280
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
280
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
281
281
|
let pkg = packageInfo;
|
|
282
282
|
const packageName = pkg.name;
|
|
283
283
|
this.logger.info(`[filter_metadata] --> Processing: ${packageName}`);
|
|
@@ -300,21 +300,29 @@ class SecurityFilterPlugin {
|
|
|
300
300
|
}
|
|
301
301
|
if ((_a = this.config.cveCheck) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
302
302
|
const versions = Object.keys(pkg.versions || {});
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
303
|
+
let vulnerableVersions = [];
|
|
304
|
+
try {
|
|
305
|
+
const results = yield this.cveChecker.checkVersions(packageName, versions);
|
|
306
|
+
vulnerableVersions = results.filter(result => result.isVulnerable).map(result => result.version);
|
|
307
|
+
}
|
|
308
|
+
catch (error) {
|
|
309
|
+
this.logger.error(`[filter_metadata] CVE check failed for ${packageName}: ${error.message}`);
|
|
310
|
+
if (((_b = this.config.errorHandling) === null || _b === void 0 ? void 0 : _b.onCveCheckError) === 'fail-closed') {
|
|
311
|
+
const reason = `CVE check failed (fail-closed mode): ${error.message}`;
|
|
312
|
+
this.metrics.recordBlock(packageName, '*', reason);
|
|
313
|
+
return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: {
|
|
314
|
+
blocked: true,
|
|
315
|
+
reason,
|
|
316
|
+
plugin: {
|
|
317
|
+
name: 'verdaccio-security-filter',
|
|
318
|
+
version: '2.0.0',
|
|
319
|
+
},
|
|
320
|
+
blockedAt: new Date().toISOString(),
|
|
321
|
+
} });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (vulnerableVersions.length > 0 && !this.config.cveCheck.autoBlock) {
|
|
325
|
+
this.logger.warn(`[filter_metadata] CVE found in ${packageName}: ${vulnerableVersions.length} vulnerable version(s), autoBlock is off`);
|
|
318
326
|
}
|
|
319
327
|
if (this.config.cveCheck.autoBlock && vulnerableVersions.length > 0) {
|
|
320
328
|
const reason = `Package has ${vulnerableVersions.length} vulnerable version(s)`;
|
|
@@ -349,8 +357,8 @@ class SecurityFilterPlugin {
|
|
|
349
357
|
}
|
|
350
358
|
}
|
|
351
359
|
if (this.config.licenses && this.config.licenses.allowed && this.config.licenses.allowed.length > 0) {
|
|
352
|
-
const latestVersion = (
|
|
353
|
-
if (latestVersion && ((
|
|
360
|
+
const latestVersion = (_c = pkg['dist-tags']) === null || _c === void 0 ? void 0 : _c.latest;
|
|
361
|
+
if (latestVersion && ((_d = pkg.versions) === null || _d === void 0 ? void 0 : _d[latestVersion])) {
|
|
354
362
|
const versionData = pkg.versions[latestVersion];
|
|
355
363
|
const licenseResult = this.licenseChecker.checkLicense(versionData);
|
|
356
364
|
if (!licenseResult.allowed) {
|
|
@@ -369,7 +377,7 @@ class SecurityFilterPlugin {
|
|
|
369
377
|
}
|
|
370
378
|
}
|
|
371
379
|
}
|
|
372
|
-
if ((
|
|
380
|
+
if ((_e = this.config.packageAge) === null || _e === void 0 ? void 0 : _e.enabled) {
|
|
373
381
|
const ageResult = this.packageAgeChecker.checkPackageAge(pkg);
|
|
374
382
|
if (!ageResult.allowed && !ageResult.warnOnly) {
|
|
375
383
|
this.logger.warn(`[filter_metadata] AGE BLOCKED: ${packageName} - ${ageResult.reason}`);
|
|
@@ -416,9 +424,9 @@ class SecurityFilterPlugin {
|
|
|
416
424
|
}
|
|
417
425
|
}
|
|
418
426
|
}
|
|
419
|
-
if ((
|
|
420
|
-
const latestVersion = (
|
|
421
|
-
if (latestVersion && ((
|
|
427
|
+
if ((_f = this.config.authorFilter) === null || _f === void 0 ? void 0 : _f.enabled) {
|
|
428
|
+
const latestVersion = (_g = pkg['dist-tags']) === null || _g === void 0 ? void 0 : _g.latest;
|
|
429
|
+
if (latestVersion && ((_h = pkg.versions) === null || _h === void 0 ? void 0 : _h[latestVersion])) {
|
|
422
430
|
const versionData = pkg.versions[latestVersion];
|
|
423
431
|
const authorResult = this.authorChecker.checkAuthor(versionData);
|
|
424
432
|
if (!authorResult.allowed && !this.config.authorFilter.warnOnly) {
|
|
@@ -456,7 +464,7 @@ class SecurityFilterPlugin {
|
|
|
456
464
|
}
|
|
457
465
|
catch (error) {
|
|
458
466
|
this.logger.error(`[filter_metadata] Error processing ${packageName}: ${error.message}`);
|
|
459
|
-
const errorStrategy = ((
|
|
467
|
+
const errorStrategy = ((_j = this.config.errorHandling) === null || _j === void 0 ? void 0 : _j.onFilterError) || 'fail-open';
|
|
460
468
|
if (errorStrategy === 'fail-closed') {
|
|
461
469
|
this.logger.warn(`[filter_metadata] Error handling: fail-closed - BLOCKING ${packageName}`);
|
|
462
470
|
this.metrics.recordBlock(packageName, '*', `Error during processing: ${error.message}`);
|
|
@@ -2,22 +2,29 @@ import { CVECheckConfig, CVECheckResult } from '../types';
|
|
|
2
2
|
export declare class CVEChecker {
|
|
3
3
|
private config;
|
|
4
4
|
private cache;
|
|
5
|
-
private
|
|
5
|
+
private failedUntil;
|
|
6
|
+
private inFlight;
|
|
7
|
+
private matches;
|
|
6
8
|
private readonly MAX_CACHE_SIZE;
|
|
7
9
|
private saveTimer;
|
|
8
10
|
private readonly SAVE_DEBOUNCE_MS;
|
|
11
|
+
private dirty;
|
|
9
12
|
constructor(config?: CVECheckConfig);
|
|
10
13
|
checkPackage(packageName: string, version: string): Promise<CVECheckResult>;
|
|
11
|
-
|
|
14
|
+
checkVersions(packageName: string, versions: string[]): Promise<CVECheckResult[]>;
|
|
15
|
+
private getPackageEntry;
|
|
16
|
+
private refresh;
|
|
12
17
|
private fetchFromOSV;
|
|
13
|
-
private
|
|
18
|
+
private queryOSV;
|
|
19
|
+
private toCachedVulnerability;
|
|
14
20
|
private mapOSVSeverity;
|
|
15
21
|
private mapDatabaseSpecificSeverity;
|
|
16
22
|
private scoreToSeverity;
|
|
17
23
|
private computeCVSSv3BaseScore;
|
|
18
|
-
private
|
|
24
|
+
private get cacheFile();
|
|
19
25
|
private loadCache;
|
|
20
26
|
private debouncedSaveCache;
|
|
27
|
+
private serializeCache;
|
|
21
28
|
private saveCache;
|
|
22
29
|
clearCache(): void;
|
|
23
30
|
destroy(): void;
|
package/build/lib/cve-checker.js
CHANGED
|
@@ -45,6 +45,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
45
45
|
exports.CVEChecker = void 0;
|
|
46
46
|
const fs = __importStar(require("fs"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
|
+
const semver = __importStar(require("semver"));
|
|
48
49
|
const lru_cache_1 = require("./lru-cache");
|
|
49
50
|
const CVSS_V3_WEIGHTS = {
|
|
50
51
|
AV: { N: 0.85, A: 0.62, L: 0.55, P: 0.2 },
|
|
@@ -61,12 +62,56 @@ function roundUpCvssScore(value) {
|
|
|
61
62
|
}
|
|
62
63
|
return (Math.floor(intValue / 10000) + 1) / 10;
|
|
63
64
|
}
|
|
65
|
+
const OSV_QUERY_URL = 'https://api.osv.dev/v1/query';
|
|
66
|
+
const OSV_TIMEOUT_MS = 5000;
|
|
67
|
+
const OSV_ATTEMPTS = 2;
|
|
68
|
+
const OSV_RETRY_DELAY_MS = 500;
|
|
69
|
+
const FAILURE_TTL_MS = 60000;
|
|
70
|
+
function compareVersions(a, b) {
|
|
71
|
+
try {
|
|
72
|
+
return semver.compare(a, b);
|
|
73
|
+
}
|
|
74
|
+
catch (_a) {
|
|
75
|
+
return NaN;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function isInRange(events, version) {
|
|
79
|
+
let affected = false;
|
|
80
|
+
for (const event of events) {
|
|
81
|
+
if (event.introduced !== undefined) {
|
|
82
|
+
if (event.introduced === '0' || compareVersions(version, event.introduced) >= 0)
|
|
83
|
+
affected = true;
|
|
84
|
+
}
|
|
85
|
+
else if (event.fixed !== undefined) {
|
|
86
|
+
if (compareVersions(version, event.fixed) >= 0)
|
|
87
|
+
affected = false;
|
|
88
|
+
}
|
|
89
|
+
else if (event.last_affected !== undefined) {
|
|
90
|
+
if (compareVersions(version, event.last_affected) > 0)
|
|
91
|
+
affected = false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return affected;
|
|
95
|
+
}
|
|
96
|
+
function isAffected(vuln, version, parsed) {
|
|
97
|
+
const informative = vuln.affected.filter(a => a.versions || a.ranges.length > 0);
|
|
98
|
+
if (informative.length === 0) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
return informative.some(a => {
|
|
102
|
+
var _a;
|
|
103
|
+
return ((_a = a.versions) === null || _a === void 0 ? void 0 : _a.includes(version)) ||
|
|
104
|
+
(parsed !== null && a.ranges.some(events => isInRange(events, parsed)));
|
|
105
|
+
});
|
|
106
|
+
}
|
|
64
107
|
class CVEChecker {
|
|
65
108
|
constructor(config) {
|
|
66
|
-
this.
|
|
109
|
+
this.inFlight = new Map();
|
|
110
|
+
this.matches = new WeakMap();
|
|
67
111
|
this.MAX_CACHE_SIZE = 1000;
|
|
68
112
|
this.saveTimer = null;
|
|
69
113
|
this.SAVE_DEBOUNCE_MS = 5000;
|
|
114
|
+
this.dirty = false;
|
|
70
115
|
this.config = {
|
|
71
116
|
enabled: (config === null || config === void 0 ? void 0 : config.enabled) || false,
|
|
72
117
|
databases: (config === null || config === void 0 ? void 0 : config.databases) || ['osv'],
|
|
@@ -76,131 +121,156 @@ class CVEChecker {
|
|
|
76
121
|
cacheDir: (config === null || config === void 0 ? void 0 : config.cacheDir) || './.security-cache',
|
|
77
122
|
};
|
|
78
123
|
this.cache = new lru_cache_1.LRUCache(this.MAX_CACHE_SIZE);
|
|
124
|
+
this.failedUntil = new lru_cache_1.LRUCache(this.MAX_CACHE_SIZE);
|
|
79
125
|
if (this.config.enabled) {
|
|
80
126
|
this.loadCache();
|
|
81
127
|
}
|
|
82
128
|
}
|
|
83
129
|
checkPackage(packageName, version) {
|
|
84
130
|
return __awaiter(this, void 0, void 0, function* () {
|
|
85
|
-
|
|
86
|
-
|
|
131
|
+
return (yield this.checkVersions(packageName, [version]))[0];
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
checkVersions(packageName, versions) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
const checkedAt = new Date().toISOString();
|
|
137
|
+
if (!this.config.enabled || !this.config.databases.includes('osv')) {
|
|
138
|
+
return versions.map(version => ({
|
|
87
139
|
package: packageName,
|
|
88
140
|
version,
|
|
89
141
|
vulnerabilities: [],
|
|
90
142
|
isVulnerable: false,
|
|
91
|
-
checkedAt
|
|
92
|
-
};
|
|
143
|
+
checkedAt,
|
|
144
|
+
}));
|
|
93
145
|
}
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
146
|
+
const entry = yield this.getPackageEntry(packageName);
|
|
147
|
+
let memo = this.matches.get(entry);
|
|
148
|
+
if (!memo) {
|
|
149
|
+
memo = new Map();
|
|
150
|
+
this.matches.set(entry, memo);
|
|
97
151
|
}
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
152
|
+
const relevant = entry.vulns.filter(vuln => this.config.severity.includes(vuln.severity));
|
|
153
|
+
return versions.map(version => {
|
|
154
|
+
let vulnerabilities = memo.get(version);
|
|
155
|
+
if (!vulnerabilities) {
|
|
156
|
+
const parsed = semver.parse(version);
|
|
157
|
+
vulnerabilities = relevant.filter(vuln => isAffected(vuln, version, parsed));
|
|
158
|
+
memo.set(version, vulnerabilities);
|
|
159
|
+
}
|
|
160
|
+
return {
|
|
161
|
+
package: packageName,
|
|
162
|
+
version,
|
|
163
|
+
vulnerabilities,
|
|
164
|
+
isVulnerable: vulnerabilities.length > 0,
|
|
165
|
+
checkedAt,
|
|
166
|
+
};
|
|
167
|
+
});
|
|
110
168
|
});
|
|
111
169
|
}
|
|
112
|
-
|
|
170
|
+
getPackageEntry(packageName) {
|
|
171
|
+
const cached = this.cache.get(packageName);
|
|
172
|
+
const ttlMs = this.config.updateInterval * 60 * 60 * 1000;
|
|
173
|
+
if (cached && Date.now() - cached.checkedAt <= ttlMs) {
|
|
174
|
+
return Promise.resolve(cached);
|
|
175
|
+
}
|
|
176
|
+
let pending = this.inFlight.get(packageName);
|
|
177
|
+
if (!pending) {
|
|
178
|
+
pending = this.refresh(packageName, cached).finally(() => this.inFlight.delete(packageName));
|
|
179
|
+
this.inFlight.set(packageName, pending);
|
|
180
|
+
}
|
|
181
|
+
return pending;
|
|
182
|
+
}
|
|
183
|
+
refresh(packageName, stale) {
|
|
113
184
|
return __awaiter(this, void 0, void 0, function* () {
|
|
114
|
-
|
|
115
|
-
|
|
185
|
+
var _a;
|
|
186
|
+
let failure;
|
|
187
|
+
if (((_a = this.failedUntil.get(packageName)) !== null && _a !== void 0 ? _a : 0) > Date.now()) {
|
|
188
|
+
failure = new Error('OSV lookup failed recently, not retrying yet');
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
116
191
|
try {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
break;
|
|
123
|
-
case 'github':
|
|
124
|
-
break;
|
|
125
|
-
}
|
|
192
|
+
const entry = { checkedAt: Date.now(), vulns: yield this.fetchFromOSV(packageName) };
|
|
193
|
+
this.cache.set(packageName, entry);
|
|
194
|
+
this.failedUntil.delete(packageName);
|
|
195
|
+
this.debouncedSaveCache();
|
|
196
|
+
return entry;
|
|
126
197
|
}
|
|
127
198
|
catch (error) {
|
|
128
|
-
|
|
129
|
-
|
|
199
|
+
failure = error;
|
|
200
|
+
this.failedUntil.set(packageName, Date.now() + FAILURE_TTL_MS);
|
|
130
201
|
}
|
|
131
202
|
}
|
|
132
|
-
|
|
203
|
+
if (stale) {
|
|
204
|
+
const message = failure instanceof Error ? failure.message : String(failure);
|
|
205
|
+
console.warn(`[CVE Checker] Using stale data for ${packageName}: ${message}`);
|
|
206
|
+
return stale;
|
|
207
|
+
}
|
|
208
|
+
throw failure;
|
|
133
209
|
});
|
|
134
210
|
}
|
|
135
|
-
fetchFromOSV(packageName
|
|
211
|
+
fetchFromOSV(packageName) {
|
|
136
212
|
return __awaiter(this, void 0, void 0, function* () {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
213
|
+
const vulns = [];
|
|
214
|
+
let pageToken;
|
|
215
|
+
do {
|
|
216
|
+
const data = yield this.queryOSV({
|
|
217
|
+
package: { name: packageName, ecosystem: 'npm' },
|
|
218
|
+
page_token: pageToken,
|
|
219
|
+
});
|
|
220
|
+
for (const vuln of data.vulns || []) {
|
|
221
|
+
vulns.push(this.toCachedVulnerability(vuln, packageName));
|
|
222
|
+
}
|
|
223
|
+
pageToken = data.next_page_token;
|
|
224
|
+
} while (pageToken);
|
|
225
|
+
return vulns;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
queryOSV(body) {
|
|
229
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
230
|
+
for (let attempt = 1;; attempt++) {
|
|
140
231
|
try {
|
|
141
|
-
const
|
|
142
|
-
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
143
|
-
const response = yield fetch('https://api.osv.dev/v1/query', {
|
|
232
|
+
const response = yield fetch(OSV_QUERY_URL, {
|
|
144
233
|
method: 'POST',
|
|
145
|
-
headers: {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
body: JSON.stringify({
|
|
149
|
-
package: {
|
|
150
|
-
name: packageName,
|
|
151
|
-
ecosystem: 'npm',
|
|
152
|
-
},
|
|
153
|
-
version: version,
|
|
154
|
-
}),
|
|
155
|
-
signal: controller.signal,
|
|
234
|
+
headers: { 'Content-Type': 'application/json' },
|
|
235
|
+
body: JSON.stringify(body),
|
|
236
|
+
signal: AbortSignal.timeout(OSV_TIMEOUT_MS),
|
|
156
237
|
});
|
|
157
|
-
clearTimeout(timeoutId);
|
|
158
238
|
if (!response.ok) {
|
|
159
|
-
if (response.status === 429 && attempt < maxRetries) {
|
|
160
|
-
const waitTime = retryDelay * Math.pow(2, attempt - 1);
|
|
161
|
-
console.warn(`[CVE Checker] Rate limited, retrying in ${waitTime}ms (attempt ${attempt}/${maxRetries})`);
|
|
162
|
-
yield this.sleep(waitTime);
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
239
|
throw new Error(`OSV API returned ${response.status}`);
|
|
166
240
|
}
|
|
167
|
-
|
|
168
|
-
if (!data.vulns || data.vulns.length === 0) {
|
|
169
|
-
return [];
|
|
170
|
-
}
|
|
171
|
-
return data.vulns.map((vuln) => {
|
|
172
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
173
|
-
return ({
|
|
174
|
-
id: vuln.id,
|
|
175
|
-
severity: this.mapOSVSeverity(vuln),
|
|
176
|
-
summary: vuln.summary || vuln.details || 'No description available',
|
|
177
|
-
affectedVersions: ((_a = vuln.affected) === null || _a === void 0 ? void 0 : _a.map((a) => { var _a; return (_a = a.ranges) === null || _a === void 0 ? void 0 : _a.map((r) => { var _a; return (_a = r.events) === null || _a === void 0 ? void 0 : _a.map((e) => e.introduced || e.fixed).filter(Boolean); }).flat(); }).flat()) || [],
|
|
178
|
-
fixedVersion: (_g = (_f = (_e = (_d = (_c = (_b = vuln.affected) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.ranges) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.events) === null || _f === void 0 ? void 0 : _f.find((e) => e.fixed)) === null || _g === void 0 ? void 0 : _g.fixed,
|
|
179
|
-
publishedDate: vuln.published || new Date().toISOString(),
|
|
180
|
-
source: 'osv',
|
|
181
|
-
});
|
|
182
|
-
});
|
|
241
|
+
return yield response.json();
|
|
183
242
|
}
|
|
184
243
|
catch (error) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
console.error(`[CVE Checker] OSV API error after ${maxRetries} attempts:`, message);
|
|
188
|
-
return [];
|
|
244
|
+
if (attempt >= OSV_ATTEMPTS) {
|
|
245
|
+
throw error;
|
|
189
246
|
}
|
|
190
|
-
|
|
191
|
-
console.warn(`[CVE Checker] Request timeout, retrying (attempt ${attempt}/${maxRetries})`);
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
console.warn(`[CVE Checker] Request failed, retrying (attempt ${attempt}/${maxRetries}):`, message);
|
|
195
|
-
}
|
|
196
|
-
yield this.sleep(retryDelay * attempt);
|
|
247
|
+
yield new Promise(resolve => setTimeout(resolve, OSV_RETRY_DELAY_MS));
|
|
197
248
|
}
|
|
198
249
|
}
|
|
199
|
-
return [];
|
|
200
250
|
});
|
|
201
251
|
}
|
|
202
|
-
|
|
203
|
-
|
|
252
|
+
toCachedVulnerability(vuln, packageName) {
|
|
253
|
+
var _a;
|
|
254
|
+
const affected = (vuln.affected || [])
|
|
255
|
+
.filter(a => { var _a; return !((_a = a.package) === null || _a === void 0 ? void 0 : _a.name) || a.package.name === packageName; })
|
|
256
|
+
.map(a => {
|
|
257
|
+
var _a;
|
|
258
|
+
return ({
|
|
259
|
+
versions: ((_a = a.versions) === null || _a === void 0 ? void 0 : _a.length) ? a.versions : undefined,
|
|
260
|
+
ranges: (a.ranges || []).filter(r => r.type !== 'GIT').map(r => r.events || []),
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
const events = affected.flatMap(a => a.ranges.flat());
|
|
264
|
+
return {
|
|
265
|
+
id: vuln.id || 'unknown',
|
|
266
|
+
severity: this.mapOSVSeverity(vuln),
|
|
267
|
+
summary: vuln.summary || 'No description available',
|
|
268
|
+
affectedVersions: events.map(e => e.introduced || e.fixed).filter((v) => !!v),
|
|
269
|
+
fixedVersion: (_a = events.find(e => e.fixed)) === null || _a === void 0 ? void 0 : _a.fixed,
|
|
270
|
+
publishedDate: vuln.published || new Date().toISOString(),
|
|
271
|
+
source: 'osv',
|
|
272
|
+
affected,
|
|
273
|
+
};
|
|
204
274
|
}
|
|
205
275
|
mapOSVSeverity(vuln) {
|
|
206
276
|
var _a, _b, _c, _d;
|
|
@@ -279,23 +349,21 @@ class CVEChecker {
|
|
|
279
349
|
? Math.ceil(cappedScore * 10) / 10
|
|
280
350
|
: roundUpCvssScore(cappedScore);
|
|
281
351
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
const updateIntervalMs = this.config.updateInterval * 60 * 60 * 1000;
|
|
285
|
-
return now - this.lastUpdate > updateIntervalMs;
|
|
352
|
+
get cacheFile() {
|
|
353
|
+
return path.join(this.config.cacheDir, 'cve-cache.json');
|
|
286
354
|
}
|
|
287
355
|
loadCache() {
|
|
288
356
|
try {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
357
|
+
if (!fs.existsSync(this.cacheFile)) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
const parsed = JSON.parse(fs.readFileSync(this.cacheFile, 'utf8'));
|
|
361
|
+
for (const [key, value] of Object.entries((parsed === null || parsed === void 0 ? void 0 : parsed.cache) || {}).slice(-this.MAX_CACHE_SIZE)) {
|
|
362
|
+
const entry = value;
|
|
363
|
+
if (typeof (entry === null || entry === void 0 ? void 0 : entry.checkedAt) === 'number' && Array.isArray(entry.vulns)
|
|
364
|
+
&& entry.vulns.every(vuln => Array.isArray(vuln === null || vuln === void 0 ? void 0 : vuln.affected))) {
|
|
365
|
+
this.cache.set(key, entry);
|
|
366
|
+
}
|
|
299
367
|
}
|
|
300
368
|
}
|
|
301
369
|
catch (error) {
|
|
@@ -304,44 +372,54 @@ class CVEChecker {
|
|
|
304
372
|
}
|
|
305
373
|
}
|
|
306
374
|
debouncedSaveCache() {
|
|
375
|
+
this.dirty = true;
|
|
307
376
|
if (this.saveTimer) {
|
|
308
377
|
clearTimeout(this.saveTimer);
|
|
309
378
|
}
|
|
310
379
|
this.saveTimer = setTimeout(() => {
|
|
311
|
-
this.saveCache();
|
|
312
380
|
this.saveTimer = null;
|
|
381
|
+
void this.saveCache();
|
|
313
382
|
}, this.SAVE_DEBOUNCE_MS);
|
|
383
|
+
this.saveTimer.unref();
|
|
384
|
+
}
|
|
385
|
+
serializeCache() {
|
|
386
|
+
return JSON.stringify({ cache: Object.fromEntries(this.cache.entries()) });
|
|
314
387
|
}
|
|
315
388
|
saveCache() {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
fs.
|
|
389
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
390
|
+
try {
|
|
391
|
+
this.dirty = false;
|
|
392
|
+
const tmpFile = `${this.cacheFile}.${process.pid}.tmp`;
|
|
393
|
+
yield fs.promises.mkdir(path.dirname(this.cacheFile), { recursive: true });
|
|
394
|
+
yield fs.promises.writeFile(tmpFile, this.serializeCache(), 'utf8');
|
|
395
|
+
yield fs.promises.rename(tmpFile, this.cacheFile);
|
|
321
396
|
}
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
this.lastUpdate = Date.now();
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
331
|
-
console.error('[CVE Checker] Failed to save cache:', message);
|
|
332
|
-
}
|
|
397
|
+
catch (error) {
|
|
398
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
399
|
+
console.error('[CVE Checker] Failed to save cache:', message);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
333
402
|
}
|
|
334
403
|
clearCache() {
|
|
335
404
|
this.cache.clear();
|
|
336
|
-
this.
|
|
337
|
-
this.
|
|
405
|
+
this.failedUntil.clear();
|
|
406
|
+
this.debouncedSaveCache();
|
|
338
407
|
}
|
|
339
408
|
destroy() {
|
|
340
409
|
if (this.saveTimer) {
|
|
341
410
|
clearTimeout(this.saveTimer);
|
|
342
411
|
this.saveTimer = null;
|
|
343
412
|
}
|
|
344
|
-
this.
|
|
413
|
+
if (this.dirty) {
|
|
414
|
+
try {
|
|
415
|
+
fs.mkdirSync(path.dirname(this.cacheFile), { recursive: true });
|
|
416
|
+
fs.writeFileSync(this.cacheFile, this.serializeCache(), 'utf8');
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
420
|
+
console.error('[CVE Checker] Failed to save cache:', message);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
345
423
|
this.cache.clear();
|
|
346
424
|
}
|
|
347
425
|
}
|
package/package.json
CHANGED