verdaccio-security-filter 1.3.9 → 1.3.10
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.d.ts +4 -0
- package/build/index.js +94 -130
- package/build/lib/metrics.d.ts +2 -0
- package/build/lib/metrics.js +10 -0
- package/build/lib/whitelist-checker.d.ts +2 -0
- package/build/lib/whitelist-checker.js +11 -5
- package/package.json +1 -1
package/build/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
|
|
|
6
6
|
logger: SecurityLogger;
|
|
7
7
|
config: SecurityConfig;
|
|
8
8
|
private readonly securityRules;
|
|
9
|
+
private readonly blockedPatternRegexes;
|
|
9
10
|
private readonly metrics;
|
|
10
11
|
private readonly whitelistChecker;
|
|
11
12
|
private readonly cveChecker;
|
|
@@ -20,7 +21,10 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
|
|
|
20
21
|
private _isScopeAllowed;
|
|
21
22
|
private _getVersionRangeRule;
|
|
22
23
|
register_middlewares(app: Application, _auth: IBasicAuth<SecurityConfig>, _storage: IStorageManager<SecurityConfig>): void;
|
|
24
|
+
private _blockedPackage;
|
|
25
|
+
private _removeVersions;
|
|
23
26
|
filter_metadata(packageInfo: Package): Promise<Package>;
|
|
27
|
+
private _versionBlockReason;
|
|
24
28
|
private _checkPackageBlock;
|
|
25
29
|
private _getAppliedRules;
|
|
26
30
|
destroy(): void;
|
package/build/index.js
CHANGED
|
@@ -47,6 +47,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
47
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
48
48
|
exports.AuthorChecker = exports.PackageAgeChecker = exports.LicenseChecker = exports.CVEChecker = exports.WhitelistChecker = exports.MetricsCollector = exports.SecurityLogger = void 0;
|
|
49
49
|
const semver = __importStar(require("semver"));
|
|
50
|
+
const fs_1 = require("fs");
|
|
51
|
+
const path_1 = require("path");
|
|
50
52
|
const logger_1 = require("./lib/logger");
|
|
51
53
|
const metrics_1 = require("./lib/metrics");
|
|
52
54
|
const whitelist_checker_1 = require("./lib/whitelist-checker");
|
|
@@ -54,6 +56,7 @@ const cve_checker_1 = require("./lib/cve-checker");
|
|
|
54
56
|
const license_checker_1 = require("./lib/license-checker");
|
|
55
57
|
const package_age_checker_1 = require("./lib/package-age-checker");
|
|
56
58
|
const author_checker_1 = require("./lib/author-checker");
|
|
59
|
+
const PLUGIN_VERSION = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', 'package.json'), 'utf8')).version;
|
|
57
60
|
class SecurityFilterPlugin {
|
|
58
61
|
constructor(config, options) {
|
|
59
62
|
this.config = config || {};
|
|
@@ -81,6 +84,15 @@ class SecurityFilterPlugin {
|
|
|
81
84
|
enforceChecksum: this.config.enforceChecksum !== false,
|
|
82
85
|
versionRangeRules: this._parseVersionRangeRules(this.config.versionRangeRules || [])
|
|
83
86
|
};
|
|
87
|
+
this.blockedPatternRegexes = this.securityRules.blockedPatterns.flatMap(pattern => {
|
|
88
|
+
try {
|
|
89
|
+
return [new RegExp(pattern)];
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
this.logger.warn(`Invalid blocked pattern "${pattern}", skipping: ${error.message}`);
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
});
|
|
84
96
|
this._logInitialization();
|
|
85
97
|
}
|
|
86
98
|
_parseVersionRangeRules(rules) {
|
|
@@ -89,9 +101,8 @@ class SecurityFilterPlugin {
|
|
|
89
101
|
this.logger.warn(`Invalid version range rule at index ${index}, skipping`);
|
|
90
102
|
return null;
|
|
91
103
|
}
|
|
92
|
-
if (rule.strategy === 'fallback'
|
|
93
|
-
this.logger.warn(`
|
|
94
|
-
return null;
|
|
104
|
+
if (rule.strategy === 'fallback') {
|
|
105
|
+
this.logger.warn(`Strategy "fallback" for ${rule.package} is deprecated and works like "block": matching versions are hidden, so npm resolves to the highest allowed one`);
|
|
95
106
|
}
|
|
96
107
|
try {
|
|
97
108
|
const validRange = semver.validRange(rule.range);
|
|
@@ -156,10 +167,7 @@ class SecurityFilterPlugin {
|
|
|
156
167
|
return features;
|
|
157
168
|
}
|
|
158
169
|
_isBlockedByPattern(packageName) {
|
|
159
|
-
return this.
|
|
160
|
-
const regex = new RegExp(pattern);
|
|
161
|
-
return regex.test(packageName);
|
|
162
|
-
});
|
|
170
|
+
return this.blockedPatternRegexes.some(regex => regex.test(packageName));
|
|
163
171
|
}
|
|
164
172
|
_isScopeAllowed(packageName) {
|
|
165
173
|
if (!packageName.startsWith('@')) {
|
|
@@ -212,9 +220,11 @@ class SecurityFilterPlugin {
|
|
|
212
220
|
const tarballFile = urlMatch[3];
|
|
213
221
|
let version;
|
|
214
222
|
if (tarballFile) {
|
|
215
|
-
const
|
|
216
|
-
version =
|
|
217
|
-
|
|
223
|
+
const filePrefix = `${urlMatch[2]}-`;
|
|
224
|
+
version = tarballFile.startsWith(filePrefix)
|
|
225
|
+
? tarballFile.slice(filePrefix.length, -'.tgz'.length)
|
|
226
|
+
: undefined;
|
|
227
|
+
this.logger.debug(`[Middleware] TARBALL REQUEST: ${packageName}@${version || 'unknown'}`);
|
|
218
228
|
const blockResult = yield this._checkPackageBlock(packageName, version);
|
|
219
229
|
if (blockResult.blocked) {
|
|
220
230
|
this.logger.warn(`[Middleware] [X] BLOCKED TARBALL: ${packageName}@${version || '*'} - ${blockResult.reason}`);
|
|
@@ -228,10 +238,10 @@ class SecurityFilterPlugin {
|
|
|
228
238
|
});
|
|
229
239
|
return;
|
|
230
240
|
}
|
|
231
|
-
this.logger.
|
|
241
|
+
this.logger.debug(`[Middleware] [OK] ALLOWED TARBALL: ${packageName}@${version || 'unknown'}`);
|
|
232
242
|
}
|
|
233
243
|
else {
|
|
234
|
-
this.logger.
|
|
244
|
+
this.logger.debug(`[Middleware] METADATA REQUEST: ${packageName} - will intercept response`);
|
|
235
245
|
const blockResult = yield this._checkPackageBlock(packageName);
|
|
236
246
|
if (blockResult.blocked) {
|
|
237
247
|
this.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
|
|
@@ -244,7 +254,7 @@ class SecurityFilterPlugin {
|
|
|
244
254
|
reason: blockResult.reason,
|
|
245
255
|
plugin: {
|
|
246
256
|
name: 'verdaccio-security-filter',
|
|
247
|
-
version:
|
|
257
|
+
version: PLUGIN_VERSION,
|
|
248
258
|
mode: this.config.mode || 'blacklist',
|
|
249
259
|
},
|
|
250
260
|
blockedAt: new Date().toISOString(),
|
|
@@ -263,7 +273,7 @@ class SecurityFilterPlugin {
|
|
|
263
273
|
return;
|
|
264
274
|
}
|
|
265
275
|
else {
|
|
266
|
-
this.logger.
|
|
276
|
+
this.logger.debug(`[Middleware] [OK] ALLOWED METADATA: ${packageName}`);
|
|
267
277
|
}
|
|
268
278
|
}
|
|
269
279
|
next();
|
|
@@ -275,28 +285,51 @@ class SecurityFilterPlugin {
|
|
|
275
285
|
}));
|
|
276
286
|
this.logger.info('[Middleware] Security filter middleware registered successfully');
|
|
277
287
|
}
|
|
288
|
+
_blockedPackage(pkg, reason, extra = {}) {
|
|
289
|
+
return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: Object.assign(Object.assign({ blocked: true, reason }, extra), { plugin: {
|
|
290
|
+
name: 'verdaccio-security-filter',
|
|
291
|
+
version: PLUGIN_VERSION,
|
|
292
|
+
mode: this.config.mode || 'blacklist',
|
|
293
|
+
}, blockedAt: new Date().toISOString() }) });
|
|
294
|
+
}
|
|
295
|
+
_removeVersions(pkg, removed) {
|
|
296
|
+
if (removed.length === 0) {
|
|
297
|
+
return pkg;
|
|
298
|
+
}
|
|
299
|
+
const gone = new Set(removed);
|
|
300
|
+
const versions = Object.assign({}, pkg.versions);
|
|
301
|
+
for (const version of gone) {
|
|
302
|
+
delete versions[version];
|
|
303
|
+
}
|
|
304
|
+
const distTags = Object.assign({}, pkg['dist-tags']);
|
|
305
|
+
for (const [tag, tagVersion] of Object.entries(distTags)) {
|
|
306
|
+
if (gone.has(tagVersion)) {
|
|
307
|
+
delete distTags[tag];
|
|
308
|
+
this.logger.debug(`[filter_metadata] Removed dist-tag "${tag}" pointing to filtered version ${tagVersion}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return Object.assign(Object.assign({}, pkg), { versions, 'dist-tags': distTags });
|
|
312
|
+
}
|
|
278
313
|
filter_metadata(packageInfo) {
|
|
279
314
|
return __awaiter(this, void 0, void 0, function* () {
|
|
280
315
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
281
316
|
let pkg = packageInfo;
|
|
282
317
|
const packageName = pkg.name;
|
|
283
|
-
this.logger.
|
|
318
|
+
this.logger.debug(`[filter_metadata] --> Processing: ${packageName}`);
|
|
284
319
|
try {
|
|
285
320
|
const blockResult = yield this._checkPackageBlock(packageName);
|
|
286
321
|
if (blockResult.blocked) {
|
|
287
322
|
this.logger.warn(`[filter_metadata] BLOCKED: ${packageName}@* - ${blockResult.reason}`);
|
|
288
323
|
this.metrics.recordBlock(packageName, '*', blockResult.reason);
|
|
289
|
-
return
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
rules: this._getAppliedRules(packageName),
|
|
299
|
-
} });
|
|
324
|
+
return this._blockedPackage(pkg, blockResult.reason, { rules: this._getAppliedRules(packageName) });
|
|
325
|
+
}
|
|
326
|
+
if (this.securityRules.blockedVersions.length > 0 || this.securityRules.versionRangeRules.length > 0) {
|
|
327
|
+
const ruleBlocked = Object.keys(pkg.versions || {})
|
|
328
|
+
.filter(version => this._versionBlockReason(packageName, version) !== null);
|
|
329
|
+
if (ruleBlocked.length > 0) {
|
|
330
|
+
this.logger.warn(`[filter_metadata] RULE FILTERED: ${packageName}: ${ruleBlocked.join(', ')}`);
|
|
331
|
+
pkg = this._removeVersions(pkg, ruleBlocked);
|
|
332
|
+
}
|
|
300
333
|
}
|
|
301
334
|
if ((_a = this.config.cveCheck) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
302
335
|
const versions = Object.keys(pkg.versions || {});
|
|
@@ -310,15 +343,7 @@ class SecurityFilterPlugin {
|
|
|
310
343
|
if (((_b = this.config.errorHandling) === null || _b === void 0 ? void 0 : _b.onCveCheckError) === 'fail-closed') {
|
|
311
344
|
const reason = `CVE check failed (fail-closed mode): ${error.message}`;
|
|
312
345
|
this.metrics.recordBlock(packageName, '*', reason);
|
|
313
|
-
return
|
|
314
|
-
blocked: true,
|
|
315
|
-
reason,
|
|
316
|
-
plugin: {
|
|
317
|
-
name: 'verdaccio-security-filter',
|
|
318
|
-
version: '2.0.0',
|
|
319
|
-
},
|
|
320
|
-
blockedAt: new Date().toISOString(),
|
|
321
|
-
} });
|
|
346
|
+
return this._blockedPackage(pkg, reason);
|
|
322
347
|
}
|
|
323
348
|
}
|
|
324
349
|
if (vulnerableVersions.length > 0 && !this.config.cveCheck.autoBlock) {
|
|
@@ -329,31 +354,13 @@ class SecurityFilterPlugin {
|
|
|
329
354
|
if (vulnerableVersions.length === versions.length) {
|
|
330
355
|
this.logger.warn(`[filter_metadata] CVE BLOCKED: ${packageName} - ${reason}`);
|
|
331
356
|
this.metrics.recordBlock(packageName, '*', reason);
|
|
332
|
-
return
|
|
333
|
-
blocked: true,
|
|
334
|
-
reason,
|
|
335
|
-
vulnerableVersions,
|
|
336
|
-
plugin: {
|
|
337
|
-
name: 'verdaccio-security-filter',
|
|
338
|
-
version: '2.0.0',
|
|
339
|
-
},
|
|
340
|
-
blockedAt: new Date().toISOString(),
|
|
341
|
-
} });
|
|
357
|
+
return this._blockedPackage(pkg, reason, { vulnerableVersions });
|
|
342
358
|
}
|
|
343
359
|
this.logger.warn(`[filter_metadata] CVE FILTERED: ${packageName} - ${reason}: ${vulnerableVersions.join(', ')}`);
|
|
344
|
-
const filteredVersions = Object.assign({}, pkg.versions);
|
|
345
360
|
for (const version of vulnerableVersions) {
|
|
346
|
-
delete filteredVersions[version];
|
|
347
361
|
this.metrics.recordBlock(packageName, version, reason);
|
|
348
362
|
}
|
|
349
|
-
|
|
350
|
-
for (const [tag, tagVersion] of Object.entries(filteredDistTags)) {
|
|
351
|
-
if (vulnerableVersions.includes(tagVersion)) {
|
|
352
|
-
delete filteredDistTags[tag];
|
|
353
|
-
this.logger.debug(`[filter_metadata] Removed dist-tag "${tag}" pointing to vulnerable version ${tagVersion}`);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
pkg = Object.assign(Object.assign({}, pkg), { versions: filteredVersions, 'dist-tags': filteredDistTags });
|
|
363
|
+
pkg = this._removeVersions(pkg, vulnerableVersions);
|
|
357
364
|
}
|
|
358
365
|
}
|
|
359
366
|
if (this.config.licenses && this.config.licenses.allowed && this.config.licenses.allowed.length > 0) {
|
|
@@ -362,66 +369,38 @@ class SecurityFilterPlugin {
|
|
|
362
369
|
const versionData = pkg.versions[latestVersion];
|
|
363
370
|
const licenseResult = this.licenseChecker.checkLicense(versionData);
|
|
364
371
|
if (!licenseResult.allowed) {
|
|
365
|
-
|
|
366
|
-
this.
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
reason: licenseResult.reason || 'License not allowed',
|
|
370
|
-
license: licenseResult.license,
|
|
371
|
-
plugin: {
|
|
372
|
-
name: 'verdaccio-security-filter',
|
|
373
|
-
version: '2.0.0',
|
|
374
|
-
},
|
|
375
|
-
blockedAt: new Date().toISOString(),
|
|
376
|
-
} });
|
|
372
|
+
const reason = licenseResult.reason || 'License not allowed';
|
|
373
|
+
this.logger.warn(`[filter_metadata] LICENSE BLOCKED: ${packageName} - ${reason}`);
|
|
374
|
+
this.metrics.recordBlock(packageName, '*', reason);
|
|
375
|
+
return this._blockedPackage(pkg, reason, { license: licenseResult.license });
|
|
377
376
|
}
|
|
378
377
|
}
|
|
379
378
|
}
|
|
380
379
|
if ((_e = this.config.packageAge) === null || _e === void 0 ? void 0 : _e.enabled) {
|
|
381
380
|
const ageResult = this.packageAgeChecker.checkPackageAge(pkg);
|
|
382
381
|
if (!ageResult.allowed && !ageResult.warnOnly) {
|
|
383
|
-
|
|
384
|
-
this.
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
reason: ageResult.reason || 'Package too new',
|
|
388
|
-
ageDays: ageResult.ageDays,
|
|
389
|
-
plugin: {
|
|
390
|
-
name: 'verdaccio-security-filter',
|
|
391
|
-
version: '2.0.0',
|
|
392
|
-
},
|
|
393
|
-
blockedAt: new Date().toISOString(),
|
|
394
|
-
} });
|
|
382
|
+
const reason = ageResult.reason || 'Package too new';
|
|
383
|
+
this.logger.warn(`[filter_metadata] AGE BLOCKED: ${packageName} - ${reason}`);
|
|
384
|
+
this.metrics.recordBlock(packageName, '*', reason);
|
|
385
|
+
return this._blockedPackage(pkg, reason, { ageDays: ageResult.ageDays });
|
|
395
386
|
}
|
|
396
387
|
else if (!ageResult.allowed && ageResult.warnOnly) {
|
|
397
388
|
this.logger.warn(`[filter_metadata] AGE WARNING: ${packageName} - ${ageResult.reason} (warn-only mode)`);
|
|
398
389
|
}
|
|
399
390
|
if (this.config.packageAge.minVersionAgeDays) {
|
|
400
|
-
const filteredVersions = Object.assign({}, pkg.versions);
|
|
401
391
|
const removedVersions = [];
|
|
402
|
-
for (const version of Object.keys(
|
|
392
|
+
for (const version of Object.keys(pkg.versions || {})) {
|
|
403
393
|
const versionAgeResult = this.packageAgeChecker.checkVersionAge(pkg, version);
|
|
404
394
|
if (!versionAgeResult.allowed && !versionAgeResult.warnOnly) {
|
|
405
395
|
this.logger.warn(`[filter_metadata] VERSION AGE FILTERED: ${packageName}@${version} - ${versionAgeResult.reason}`);
|
|
406
396
|
this.metrics.recordBlock(packageName, version, versionAgeResult.reason || 'Version too new');
|
|
407
|
-
delete filteredVersions[version];
|
|
408
397
|
removedVersions.push(version);
|
|
409
398
|
}
|
|
410
399
|
else if (!versionAgeResult.allowed && versionAgeResult.warnOnly) {
|
|
411
400
|
this.logger.warn(`[filter_metadata] VERSION AGE WARNING: ${packageName}@${version} - ${versionAgeResult.reason} (warn-only mode)`);
|
|
412
401
|
}
|
|
413
402
|
}
|
|
414
|
-
|
|
415
|
-
this.logger.info(`[filter_metadata] Removed ${removedVersions.length} too-new version(s) from ${packageName}: ${removedVersions.join(', ')}`);
|
|
416
|
-
const filteredDistTags = Object.assign({}, pkg['dist-tags']);
|
|
417
|
-
for (const [tag, tagVersion] of Object.entries(filteredDistTags)) {
|
|
418
|
-
if (removedVersions.includes(tagVersion)) {
|
|
419
|
-
delete filteredDistTags[tag];
|
|
420
|
-
this.logger.debug(`[filter_metadata] Removed dist-tag "${tag}" pointing to filtered version ${tagVersion}`);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
pkg = Object.assign(Object.assign({}, pkg), { versions: filteredVersions, 'dist-tags': filteredDistTags });
|
|
424
|
-
}
|
|
403
|
+
pkg = this._removeVersions(pkg, removedVersions);
|
|
425
404
|
}
|
|
426
405
|
}
|
|
427
406
|
if ((_f = this.config.authorFilter) === null || _f === void 0 ? void 0 : _f.enabled) {
|
|
@@ -430,36 +409,30 @@ class SecurityFilterPlugin {
|
|
|
430
409
|
const versionData = pkg.versions[latestVersion];
|
|
431
410
|
const authorResult = this.authorChecker.checkAuthor(versionData);
|
|
432
411
|
if (!authorResult.allowed && !this.config.authorFilter.warnOnly) {
|
|
433
|
-
|
|
412
|
+
const reason = authorResult.reason || 'Author not allowed';
|
|
413
|
+
this.logger.warn(`[filter_metadata] AUTHOR BLOCKED: ${packageName} - ${reason}`);
|
|
434
414
|
this.metrics.record({
|
|
435
415
|
timestamp: new Date().toISOString(),
|
|
436
416
|
event: 'author_blocked',
|
|
437
417
|
packageName,
|
|
438
418
|
version: latestVersion,
|
|
439
|
-
reason
|
|
419
|
+
reason,
|
|
440
420
|
metadata: {
|
|
441
421
|
blockedBy: authorResult.blockedBy,
|
|
442
422
|
authorInfo: authorResult.authorInfo,
|
|
443
423
|
},
|
|
444
424
|
});
|
|
445
|
-
return
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
authorInfo: authorResult.authorInfo,
|
|
450
|
-
plugin: {
|
|
451
|
-
name: 'verdaccio-security-filter',
|
|
452
|
-
version: '2.0.0',
|
|
453
|
-
},
|
|
454
|
-
blockedAt: new Date().toISOString(),
|
|
455
|
-
} });
|
|
425
|
+
return this._blockedPackage(pkg, reason, {
|
|
426
|
+
blockedBy: authorResult.blockedBy,
|
|
427
|
+
authorInfo: authorResult.authorInfo,
|
|
428
|
+
});
|
|
456
429
|
}
|
|
457
430
|
else if (!authorResult.allowed && this.config.authorFilter.warnOnly) {
|
|
458
431
|
this.logger.warn(`[filter_metadata] AUTHOR WARNING: ${packageName} - ${authorResult.reason} (warn-only mode)`);
|
|
459
432
|
}
|
|
460
433
|
}
|
|
461
434
|
}
|
|
462
|
-
this.logger.
|
|
435
|
+
this.logger.debug(`[filter_metadata] [OK] ALLOWED: ${packageName}`);
|
|
463
436
|
return pkg;
|
|
464
437
|
}
|
|
465
438
|
catch (error) {
|
|
@@ -468,21 +441,20 @@ class SecurityFilterPlugin {
|
|
|
468
441
|
if (errorStrategy === 'fail-closed') {
|
|
469
442
|
this.logger.warn(`[filter_metadata] Error handling: fail-closed - BLOCKING ${packageName}`);
|
|
470
443
|
this.metrics.recordBlock(packageName, '*', `Error during processing: ${error.message}`);
|
|
471
|
-
return
|
|
472
|
-
blocked: true,
|
|
473
|
-
reason: `Error during security processing (fail-closed mode): ${error.message}`,
|
|
474
|
-
plugin: {
|
|
475
|
-
name: 'verdaccio-security-filter',
|
|
476
|
-
version: '2.0.0',
|
|
477
|
-
},
|
|
478
|
-
blockedAt: new Date().toISOString(),
|
|
479
|
-
} });
|
|
444
|
+
return this._blockedPackage(packageInfo, `Error during security processing (fail-closed mode): ${error.message}`);
|
|
480
445
|
}
|
|
481
446
|
this.logger.warn(`[filter_metadata] Error handling: fail-open - ALLOWING ${packageName}`);
|
|
482
447
|
return packageInfo;
|
|
483
448
|
}
|
|
484
449
|
});
|
|
485
450
|
}
|
|
451
|
+
_versionBlockReason(packageName, version) {
|
|
452
|
+
if (this.securityRules.blockedVersions.includes(`${packageName}@${version}`)) {
|
|
453
|
+
return 'Exact version match in blocklist';
|
|
454
|
+
}
|
|
455
|
+
const rangeRule = this._getVersionRangeRule(packageName, version);
|
|
456
|
+
return rangeRule ? `Version matches blocked range: ${rangeRule.range}` : null;
|
|
457
|
+
}
|
|
486
458
|
_checkPackageBlock(packageName, version) {
|
|
487
459
|
return __awaiter(this, void 0, void 0, function* () {
|
|
488
460
|
if (this.config.mode === 'whitelist') {
|
|
@@ -497,15 +469,9 @@ class SecurityFilterPlugin {
|
|
|
497
469
|
if (!this._isScopeAllowed(packageName)) {
|
|
498
470
|
return { blocked: true, reason: 'Package scope not allowed' };
|
|
499
471
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
return { blocked: true, reason: 'Exact version match in blocklist' };
|
|
504
|
-
}
|
|
505
|
-
const rangeRule = this._getVersionRangeRule(packageName, version);
|
|
506
|
-
if (rangeRule && rangeRule.strategy === 'block') {
|
|
507
|
-
return { blocked: true, reason: `Version matches blocked range: ${rangeRule.range}` };
|
|
508
|
-
}
|
|
472
|
+
const versionReason = version ? this._versionBlockReason(packageName, version) : null;
|
|
473
|
+
if (versionReason) {
|
|
474
|
+
return { blocked: true, reason: versionReason };
|
|
509
475
|
}
|
|
510
476
|
return { blocked: false, reason: '' };
|
|
511
477
|
});
|
|
@@ -516,11 +482,9 @@ class SecurityFilterPlugin {
|
|
|
516
482
|
rules.mode = 'whitelist';
|
|
517
483
|
rules.whitelisted = false;
|
|
518
484
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
return regex.test(packageName);
|
|
523
|
-
});
|
|
485
|
+
const blockedPattern = this.blockedPatternRegexes.find(regex => regex.test(packageName));
|
|
486
|
+
if (blockedPattern) {
|
|
487
|
+
rules.blockedPattern = blockedPattern.source;
|
|
524
488
|
}
|
|
525
489
|
if (packageName.startsWith('@')) {
|
|
526
490
|
const scope = packageName.split('/')[0];
|
package/build/lib/metrics.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export declare class MetricsCollector {
|
|
|
4
4
|
private metricsBuffer;
|
|
5
5
|
private readonly bufferSize;
|
|
6
6
|
private readonly maxBufferSize;
|
|
7
|
+
private readonly flushIntervalMs;
|
|
8
|
+
private flushTimer;
|
|
7
9
|
constructor(config?: MetricsConfig);
|
|
8
10
|
record(event: MetricsData): void;
|
|
9
11
|
recordBlock(packageName: string, version: string, reason: string, metadata?: Record<string, any>): void;
|
package/build/lib/metrics.js
CHANGED
|
@@ -41,11 +41,17 @@ class MetricsCollector {
|
|
|
41
41
|
this.metricsBuffer = [];
|
|
42
42
|
this.bufferSize = 100;
|
|
43
43
|
this.maxBufferSize = 500;
|
|
44
|
+
this.flushIntervalMs = 10000;
|
|
45
|
+
this.flushTimer = null;
|
|
44
46
|
this.config = {
|
|
45
47
|
enabled: (config === null || config === void 0 ? void 0 : config.enabled) || false,
|
|
46
48
|
output: (config === null || config === void 0 ? void 0 : config.output) || 'stdout',
|
|
47
49
|
filePath: (config === null || config === void 0 ? void 0 : config.filePath) || './security-metrics.json',
|
|
48
50
|
};
|
|
51
|
+
if (this.config.enabled) {
|
|
52
|
+
this.flushTimer = setInterval(() => this.flush(), this.flushIntervalMs);
|
|
53
|
+
this.flushTimer.unref();
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
record(event) {
|
|
51
57
|
if (!this.config.enabled) {
|
|
@@ -163,6 +169,10 @@ class MetricsCollector {
|
|
|
163
169
|
return summary;
|
|
164
170
|
}
|
|
165
171
|
cleanup() {
|
|
172
|
+
if (this.flushTimer) {
|
|
173
|
+
clearInterval(this.flushTimer);
|
|
174
|
+
this.flushTimer = null;
|
|
175
|
+
}
|
|
166
176
|
this.flush();
|
|
167
177
|
}
|
|
168
178
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { WhitelistConfig } from '../types';
|
|
2
2
|
export declare class WhitelistChecker {
|
|
3
3
|
private config;
|
|
4
|
+
private regexes;
|
|
4
5
|
constructor(config?: WhitelistConfig);
|
|
5
6
|
isWhitelisted(packageName: string, version?: string): {
|
|
6
7
|
allowed: boolean;
|
|
7
8
|
reason?: string;
|
|
8
9
|
};
|
|
9
10
|
private matchesPattern;
|
|
11
|
+
private compile;
|
|
10
12
|
addPackage(packageName: string, versionRange?: string): void;
|
|
11
13
|
removePackage(packageName: string): void;
|
|
12
14
|
addPattern(pattern: string): void;
|
|
@@ -46,6 +46,7 @@ exports.WhitelistChecker = void 0;
|
|
|
46
46
|
const semver = __importStar(require("semver"));
|
|
47
47
|
class WhitelistChecker {
|
|
48
48
|
constructor(config) {
|
|
49
|
+
this.regexes = new Map();
|
|
49
50
|
this.config = {
|
|
50
51
|
packages: (config === null || config === void 0 ? void 0 : config.packages) || [],
|
|
51
52
|
patterns: (config === null || config === void 0 ? void 0 : config.patterns) || [],
|
|
@@ -75,16 +76,21 @@ class WhitelistChecker {
|
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
78
|
matchesPattern(packageName) {
|
|
78
|
-
return this.config.patterns.some(pattern => {
|
|
79
|
+
return this.config.patterns.some(pattern => { var _a; return (_a = this.compile(pattern)) === null || _a === void 0 ? void 0 : _a.test(packageName); });
|
|
80
|
+
}
|
|
81
|
+
compile(pattern) {
|
|
82
|
+
let regex = this.regexes.get(pattern);
|
|
83
|
+
if (regex === undefined) {
|
|
79
84
|
try {
|
|
80
|
-
|
|
81
|
-
return regex.test(packageName);
|
|
85
|
+
regex = new RegExp(pattern);
|
|
82
86
|
}
|
|
83
87
|
catch (_a) {
|
|
84
88
|
console.error(`[Whitelist] Invalid pattern: ${pattern}`);
|
|
85
|
-
|
|
89
|
+
regex = null;
|
|
86
90
|
}
|
|
87
|
-
|
|
91
|
+
this.regexes.set(pattern, regex);
|
|
92
|
+
}
|
|
93
|
+
return regex;
|
|
88
94
|
}
|
|
89
95
|
addPackage(packageName, versionRange) {
|
|
90
96
|
if (!this.config.packages.includes(packageName)) {
|
package/package.json
CHANGED