verdaccio-security-filter 1.3.11 → 1.3.12
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/README.md +6 -2
- package/build/index.d.ts +1 -0
- package/build/index.js +51 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -422,7 +422,7 @@ middlewares:
|
|
|
422
422
|
| `cveCheck.updateInterval` | `number` | `24` | How long a package's vulnerability data is cached (hours) |
|
|
423
423
|
| `cveCheck.cacheDir` | `string` | `./.security-cache` | Cache directory path |
|
|
424
424
|
|
|
425
|
-
> **Note on
|
|
425
|
+
> **Note on removed versions and dist-tags:** whenever a check removes versions (CVE `autoBlock`, license, author, age, version rules, `whitelist.versions`), the other versions stay installable. The plugin drops every dist-tag that points to a removed version. Verdaccio then points `latest` at the highest version that is left (verified with Verdaccio 6.2), so `npm install <pkg>` keeps working. Other dist-tags that pointed to a removed version are gone.
|
|
426
426
|
|
|
427
427
|
> **When OSV is unreachable:** a lookup gets one retry with a 5 second timeout each. After a failure the same package is not looked up again for 60 seconds. Previously cached data, even if older than `updateInterval`, keeps being used. With no cached data, `errorHandling.onCveCheckError` decides: `fail-open` serves the package unchecked, `fail-closed` blocks it.
|
|
428
428
|
|
|
@@ -432,7 +432,9 @@ middlewares:
|
|
|
432
432
|
|--------|------|---------|-------------|
|
|
433
433
|
| `licenses.allowed` | `string[]` | `[]` | Allowed license list |
|
|
434
434
|
| `licenses.blocked` | `string[]` | `[]` | Blocked license list |
|
|
435
|
-
| `licenses.requireLicense` | `boolean` | `
|
|
435
|
+
| `licenses.requireLicense` | `boolean` | `true` | Reject versions without a license field |
|
|
436
|
+
|
|
437
|
+
The license check runs when `licenses.allowed` is not empty. Every version is checked: versions with a rejected license are removed from the metadata, and the package is blocked as a whole only when no version is left.
|
|
436
438
|
|
|
437
439
|
### Whitelist Options
|
|
438
440
|
|
|
@@ -481,6 +483,8 @@ middlewares:
|
|
|
481
483
|
| `authorFilter.requireVerifiedEmail` | `boolean` | `false` | Require author email information |
|
|
482
484
|
| `authorFilter.warnOnly` | `boolean` | `false` | Only warn, don't block |
|
|
483
485
|
|
|
486
|
+
Every version is checked against the author rules: versions published by a blocked author are removed from the metadata, and the package is blocked as a whole only when no version is left.
|
|
487
|
+
|
|
484
488
|
### Error Handling Options
|
|
485
489
|
|
|
486
490
|
| Option | Type | Default | Description |
|
package/build/index.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
|
|
|
23
23
|
register_middlewares(app: Application, _auth: IBasicAuth<SecurityConfig>, _storage: IStorageManager<SecurityConfig>): void;
|
|
24
24
|
private _blockedPackage;
|
|
25
25
|
private _removeVersions;
|
|
26
|
+
private _rejectedVersions;
|
|
26
27
|
filter_metadata(packageInfo: Package): Promise<Package>;
|
|
27
28
|
private _versionBlockReason;
|
|
28
29
|
private _checkPackageBlock;
|
package/build/index.js
CHANGED
|
@@ -306,9 +306,23 @@ class SecurityFilterPlugin {
|
|
|
306
306
|
}
|
|
307
307
|
return Object.assign(Object.assign({}, pkg), { versions, 'dist-tags': distTags });
|
|
308
308
|
}
|
|
309
|
+
_rejectedVersions(pkg, check) {
|
|
310
|
+
var _a;
|
|
311
|
+
const all = Object.entries(pkg.versions || {});
|
|
312
|
+
const results = all
|
|
313
|
+
.map(([version, data]) => [version, check(data)])
|
|
314
|
+
.filter(([, result]) => !result.allowed);
|
|
315
|
+
const latest = results.find(([version]) => { var _a; return version === ((_a = pkg['dist-tags']) === null || _a === void 0 ? void 0 : _a.latest); });
|
|
316
|
+
return {
|
|
317
|
+
results,
|
|
318
|
+
versions: results.map(([version]) => version),
|
|
319
|
+
first: (_a = (latest || results[0])) === null || _a === void 0 ? void 0 : _a[1],
|
|
320
|
+
all: all.length > 0 && results.length === all.length,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
309
323
|
filter_metadata(packageInfo) {
|
|
310
324
|
return __awaiter(this, void 0, void 0, function* () {
|
|
311
|
-
var _a, _b, _c, _d, _e
|
|
325
|
+
var _a, _b, _c, _d, _e;
|
|
312
326
|
let pkg = packageInfo;
|
|
313
327
|
const packageName = pkg.name;
|
|
314
328
|
this.logger.debug(`[filter_metadata] --> Processing: ${packageName}`);
|
|
@@ -361,19 +375,22 @@ class SecurityFilterPlugin {
|
|
|
361
375
|
}
|
|
362
376
|
}
|
|
363
377
|
if (this.config.licenses && this.config.licenses.allowed && this.config.licenses.allowed.length > 0) {
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
378
|
+
const rejected = this._rejectedVersions(pkg, data => this.licenseChecker.checkLicense(data));
|
|
379
|
+
if (rejected.all) {
|
|
380
|
+
const reason = rejected.first.reason || 'License not allowed';
|
|
381
|
+
this.logger.warn(`[filter_metadata] LICENSE BLOCKED: ${packageName} - ${reason}`);
|
|
382
|
+
this.metrics.recordBlock(packageName, '*', reason);
|
|
383
|
+
return this._blockedPackage(pkg, reason, { license: rejected.first.license });
|
|
384
|
+
}
|
|
385
|
+
if (rejected.versions.length > 0) {
|
|
386
|
+
this.logger.warn(`[filter_metadata] LICENSE FILTERED: ${packageName}: ${rejected.versions.join(', ')}`);
|
|
387
|
+
for (const [version, result] of rejected.results) {
|
|
388
|
+
this.metrics.recordBlock(packageName, version, result.reason || 'License not allowed');
|
|
373
389
|
}
|
|
390
|
+
pkg = this._removeVersions(pkg, rejected.versions);
|
|
374
391
|
}
|
|
375
392
|
}
|
|
376
|
-
if ((
|
|
393
|
+
if ((_c = this.config.packageAge) === null || _c === void 0 ? void 0 : _c.enabled) {
|
|
377
394
|
const ageResult = this.packageAgeChecker.checkPackageAge(pkg);
|
|
378
395
|
if (!ageResult.allowed && !ageResult.warnOnly) {
|
|
379
396
|
const reason = ageResult.reason || 'Package too new';
|
|
@@ -400,33 +417,38 @@ class SecurityFilterPlugin {
|
|
|
400
417
|
pkg = this._removeVersions(pkg, removedVersions);
|
|
401
418
|
}
|
|
402
419
|
}
|
|
403
|
-
if ((
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
this.logger.warn(`[filter_metadata] AUTHOR BLOCKED: ${packageName} - ${reason}`);
|
|
420
|
+
if ((_d = this.config.authorFilter) === null || _d === void 0 ? void 0 : _d.enabled) {
|
|
421
|
+
const rejected = this._rejectedVersions(pkg, data => this.authorChecker.checkAuthor(data));
|
|
422
|
+
if (rejected.versions.length > 0 && this.config.authorFilter.warnOnly) {
|
|
423
|
+
this.logger.warn(`[filter_metadata] AUTHOR WARNING: ${packageName}: ${rejected.versions.join(', ')} - ${rejected.first.reason} (warn-only mode)`);
|
|
424
|
+
}
|
|
425
|
+
else if (rejected.versions.length > 0) {
|
|
426
|
+
for (const [version, result] of rejected.results) {
|
|
411
427
|
this.metrics.record({
|
|
412
428
|
timestamp: new Date().toISOString(),
|
|
413
429
|
event: 'author_blocked',
|
|
414
430
|
packageName,
|
|
415
|
-
version:
|
|
416
|
-
reason,
|
|
431
|
+
version: rejected.all ? '*' : version,
|
|
432
|
+
reason: result.reason || 'Author not allowed',
|
|
417
433
|
metadata: {
|
|
418
|
-
blockedBy:
|
|
419
|
-
authorInfo:
|
|
434
|
+
blockedBy: result.blockedBy,
|
|
435
|
+
authorInfo: result.authorInfo,
|
|
420
436
|
},
|
|
421
437
|
});
|
|
438
|
+
if (rejected.all) {
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (rejected.all) {
|
|
443
|
+
const reason = rejected.first.reason || 'Author not allowed';
|
|
444
|
+
this.logger.warn(`[filter_metadata] AUTHOR BLOCKED: ${packageName} - ${reason}`);
|
|
422
445
|
return this._blockedPackage(pkg, reason, {
|
|
423
|
-
blockedBy:
|
|
424
|
-
authorInfo:
|
|
446
|
+
blockedBy: rejected.first.blockedBy,
|
|
447
|
+
authorInfo: rejected.first.authorInfo,
|
|
425
448
|
});
|
|
426
449
|
}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
450
|
+
this.logger.warn(`[filter_metadata] AUTHOR FILTERED: ${packageName}: ${rejected.versions.join(', ')}`);
|
|
451
|
+
pkg = this._removeVersions(pkg, rejected.versions);
|
|
430
452
|
}
|
|
431
453
|
}
|
|
432
454
|
this.logger.debug(`[filter_metadata] [OK] ALLOWED: ${packageName}`);
|
|
@@ -434,7 +456,7 @@ class SecurityFilterPlugin {
|
|
|
434
456
|
}
|
|
435
457
|
catch (error) {
|
|
436
458
|
this.logger.error(`[filter_metadata] Error processing ${packageName}: ${error.message}`);
|
|
437
|
-
const errorStrategy = ((
|
|
459
|
+
const errorStrategy = ((_e = this.config.errorHandling) === null || _e === void 0 ? void 0 : _e.onFilterError) || 'fail-open';
|
|
438
460
|
if (errorStrategy === 'fail-closed') {
|
|
439
461
|
this.logger.warn(`[filter_metadata] Error handling: fail-closed - BLOCKING ${packageName}`);
|
|
440
462
|
this.metrics.recordBlock(packageName, '*', `Error during processing: ${error.message}`);
|
package/package.json
CHANGED