verdaccio-security-filter 1.3.10 → 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 +26 -41
- package/build/index.d.ts +1 -0
- package/build/index.js +55 -36
- package/build/lib/metrics.d.ts +0 -2
- package/build/lib/metrics.js +0 -20
- package/build/lib/whitelist-checker.d.ts +0 -5
- package/build/lib/whitelist-checker.js +0 -22
- package/build/types.d.ts +2 -27
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -36,17 +36,15 @@ This plugin implements **two independent security layers** for complete protecti
|
|
|
36
36
|
|
|
37
37
|
#### 🔒 Version Management
|
|
38
38
|
- **Version Range Blocking** - Block vulnerable versions using semver ranges
|
|
39
|
-
-
|
|
40
|
-
- **Fallback Strategy**: Transparently redirect to safe versions
|
|
39
|
+
- Matching versions are hidden from metadata and their tarballs return 403, so npm resolves to the highest allowed version
|
|
41
40
|
- **Exact Version Blocking** - Block specific package@version combinations
|
|
42
41
|
- **Semver Support** - Full semver syntax (`^`, `~`, `>`, `>=`, `<`, `<=`, `x`, `*`)
|
|
43
42
|
|
|
44
43
|
#### 🛡️ CVE & Vulnerability Scanning
|
|
45
44
|
- **OSV Database Integration** - Automatic vulnerability scanning via [OSV API](https://osv.dev/)
|
|
46
45
|
- **Severity Filtering** - Filter by severity: `low`, `medium`, `high`, `critical`
|
|
47
|
-
- **Auto-Block Vulnerable
|
|
48
|
-
- **Caching System** -
|
|
49
|
-
- **Multiple Database Support** - Ready for OSV, Snyk, GitHub Advisory
|
|
46
|
+
- **Auto-Block Vulnerable Versions** - Remove versions with known CVEs from metadata
|
|
47
|
+
- **Caching System** - One OSV lookup per package, cached per package with a configurable refresh interval
|
|
50
48
|
|
|
51
49
|
#### ⚖️ License Compliance
|
|
52
50
|
- **License Filtering** - Enforce license policies with allowed/blocked lists
|
|
@@ -62,7 +60,7 @@ This plugin implements **two independent security layers** for complete protecti
|
|
|
62
60
|
|
|
63
61
|
#### 📊 Monitoring & Observability
|
|
64
62
|
- **Enhanced Logging** - Configurable log levels (`debug`, `info`, `warn`, `error`)
|
|
65
|
-
- **Metrics Collection** - Track security events (blocks
|
|
63
|
+
- **Metrics Collection** - Track security events (blocks with their reason, author blocks)
|
|
66
64
|
- **Security Audit Trail** - Detailed logging of all security decisions
|
|
67
65
|
- **Customizable Output** - Log to stdout or file in JSON format
|
|
68
66
|
|
|
@@ -83,9 +81,7 @@ This plugin implements **two independent security layers** for complete protecti
|
|
|
83
81
|
- Prevents blocked packages from being installed as dependencies
|
|
84
82
|
- Intercepts requests to `/:package/-/:filename.tgz` routes
|
|
85
83
|
- Returns 403 Forbidden with detailed error messages
|
|
86
|
-
- **Size Limits** - Enforce minimum and maximum package sizes
|
|
87
84
|
- **Metadata Validation** - Verify package integrity and detect dangerous characters
|
|
88
|
-
- **Checksum Enforcement** - Ensure package integrity
|
|
89
85
|
|
|
90
86
|
#### ⚙️ Reliability & Error Handling
|
|
91
87
|
- **Fail-Safe/Fail-Closed Configuration** - Choose error handling strategy
|
|
@@ -274,7 +270,6 @@ filters:
|
|
|
274
270
|
enabled: true
|
|
275
271
|
databases:
|
|
276
272
|
- osv
|
|
277
|
-
- github
|
|
278
273
|
severity:
|
|
279
274
|
- high
|
|
280
275
|
- critical
|
|
@@ -304,8 +299,7 @@ filters:
|
|
|
304
299
|
versionRangeRules:
|
|
305
300
|
- package: lodash
|
|
306
301
|
range: "<4.17.21"
|
|
307
|
-
strategy:
|
|
308
|
-
fallbackVersion: "4.17.21"
|
|
302
|
+
strategy: block
|
|
309
303
|
reason: "CVE-2021-23337: Command injection"
|
|
310
304
|
|
|
311
305
|
- package: minimist
|
|
@@ -379,7 +373,7 @@ filters:
|
|
|
379
373
|
|
|
380
374
|
cveCheck:
|
|
381
375
|
enabled: true
|
|
382
|
-
databases: [osv
|
|
376
|
+
databases: [osv]
|
|
383
377
|
severity: [low, medium, high, critical] # Block ALL
|
|
384
378
|
autoBlock: true
|
|
385
379
|
updateInterval: 6 # Check every 6 hours
|
|
@@ -389,9 +383,6 @@ filters:
|
|
|
389
383
|
blocked: [GPL-3.0, AGPL-3.0, LGPL-3.0]
|
|
390
384
|
requireLicense: true
|
|
391
385
|
|
|
392
|
-
minPackageSize: 10000 # 10KB minimum
|
|
393
|
-
maxPackageSize: 10485760 # 10MB maximum
|
|
394
|
-
|
|
395
386
|
logger:
|
|
396
387
|
level: debug # Log everything
|
|
397
388
|
enabled: true
|
|
@@ -419,22 +410,21 @@ middlewares:
|
|
|
419
410
|
| `blockedPatterns` | `string[]` | `[]` | Regex patterns for package names |
|
|
420
411
|
| `allowedScopes` | `string[]` | `[]` | Allowed package scopes |
|
|
421
412
|
| `blockedScopes` | `string[]` | `[]` | Blocked package scopes |
|
|
422
|
-
| `minPackageSize` | `number` | `0` | Minimum package size in bytes |
|
|
423
|
-
| `maxPackageSize` | `number` | `104857600` | Maximum package size (100MB) |
|
|
424
|
-
| `enforceChecksum` | `boolean` | `true` | Enforce checksum validation |
|
|
425
413
|
|
|
426
414
|
### CVE Check Options
|
|
427
415
|
|
|
428
416
|
| Option | Type | Default | Description |
|
|
429
417
|
|--------|------|---------|-------------|
|
|
430
418
|
| `cveCheck.enabled` | `boolean` | `false` | Enable CVE scanning |
|
|
431
|
-
| `cveCheck.databases` | `string[]` | `['osv']` |
|
|
419
|
+
| `cveCheck.databases` | `string[]` | `['osv']` | Vulnerability databases. Only `osv` is implemented; it already includes GitHub Security Advisories |
|
|
432
420
|
| `cveCheck.severity` | `string[]` | `['high', 'critical']` | Severity levels to check |
|
|
433
421
|
| `cveCheck.autoBlock` | `boolean` | `false` | Remove vulnerable versions from metadata (whole package blocked if every version is vulnerable) |
|
|
434
|
-
| `cveCheck.updateInterval` | `number` | `24` |
|
|
422
|
+
| `cveCheck.updateInterval` | `number` | `24` | How long a package's vulnerability data is cached (hours) |
|
|
435
423
|
| `cveCheck.cacheDir` | `string` | `./.security-cache` | Cache directory path |
|
|
436
424
|
|
|
437
|
-
> **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
|
+
|
|
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.
|
|
438
428
|
|
|
439
429
|
### License Options
|
|
440
430
|
|
|
@@ -442,7 +432,9 @@ middlewares:
|
|
|
442
432
|
|--------|------|---------|-------------|
|
|
443
433
|
| `licenses.allowed` | `string[]` | `[]` | Allowed license list |
|
|
444
434
|
| `licenses.blocked` | `string[]` | `[]` | Blocked license list |
|
|
445
|
-
| `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.
|
|
446
438
|
|
|
447
439
|
### Whitelist Options
|
|
448
440
|
|
|
@@ -491,13 +483,14 @@ middlewares:
|
|
|
491
483
|
| `authorFilter.requireVerifiedEmail` | `boolean` | `false` | Require author email information |
|
|
492
484
|
| `authorFilter.warnOnly` | `boolean` | `false` | Only warn, don't block |
|
|
493
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
|
+
|
|
494
488
|
### Error Handling Options
|
|
495
489
|
|
|
496
490
|
| Option | Type | Default | Description |
|
|
497
491
|
|--------|------|---------|-------------|
|
|
498
492
|
| `errorHandling.onFilterError` | `'fail-open' \| 'fail-closed'` | `'fail-open'` | Strategy when filter errors occur |
|
|
499
|
-
| `errorHandling.onCveCheckError` | `'fail-open' \| 'fail-closed'` | `'fail-open'` | Strategy when
|
|
500
|
-
| `errorHandling.onLicenseCheckError` | `'fail-open' \| 'fail-closed'` | `'fail-open'` | Strategy when license check fails |
|
|
493
|
+
| `errorHandling.onCveCheckError` | `'fail-open' \| 'fail-closed'` | `'fail-open'` | Strategy when the vulnerability lookup fails and no cached data exists |
|
|
501
494
|
|
|
502
495
|
**Error Handling Strategies:**
|
|
503
496
|
- **fail-open** (Default): Allow packages through on errors (prioritizes availability)
|
|
@@ -509,19 +502,16 @@ middlewares:
|
|
|
509
502
|
errorHandling:
|
|
510
503
|
onFilterError: fail-closed
|
|
511
504
|
onCveCheckError: fail-closed
|
|
512
|
-
onLicenseCheckError: fail-closed
|
|
513
505
|
|
|
514
506
|
# Development: Availability-first approach
|
|
515
507
|
errorHandling:
|
|
516
508
|
onFilterError: fail-open
|
|
517
509
|
onCveCheckError: fail-open
|
|
518
|
-
onLicenseCheckError: fail-open
|
|
519
510
|
|
|
520
|
-
# Balanced:
|
|
511
|
+
# Balanced: block only when vulnerability data is unavailable
|
|
521
512
|
errorHandling:
|
|
522
513
|
onFilterError: fail-open
|
|
523
|
-
onCveCheckError: fail-
|
|
524
|
-
onLicenseCheckError: fail-closed
|
|
514
|
+
onCveCheckError: fail-closed
|
|
525
515
|
```
|
|
526
516
|
|
|
527
517
|
### Version Range Rules
|
|
@@ -530,8 +520,7 @@ errorHandling:
|
|
|
530
520
|
{
|
|
531
521
|
package: string; // Package name
|
|
532
522
|
range: string; // Semver range
|
|
533
|
-
strategy: 'block'
|
|
534
|
-
fallbackVersion?: string; // Required for fallback strategy
|
|
523
|
+
strategy: 'block'; // 'fallback' is deprecated and behaves like 'block'
|
|
535
524
|
reason?: string; // Explanation for blocking
|
|
536
525
|
}
|
|
537
526
|
```
|
|
@@ -586,12 +575,7 @@ npm run build -- --watch
|
|
|
586
575
|
|
|
587
576
|
When metrics are enabled, the plugin tracks:
|
|
588
577
|
|
|
589
|
-
- `block` - Packages blocked by
|
|
590
|
-
- `fallback` - Versions redirected to safe alternatives
|
|
591
|
-
- `publish_rejected` - Publish attempts rejected
|
|
592
|
-
- `cve_detected` - CVE vulnerabilities found
|
|
593
|
-
- `license_blocked` - Packages blocked by license rules
|
|
594
|
-
- `package_too_new` - Packages/versions blocked due to age restrictions
|
|
578
|
+
- `block` - Packages or versions blocked by rules, CVE, license or age checks; the `reason` field says which
|
|
595
579
|
- `author_blocked` - Packages blocked due to author/region filtering
|
|
596
580
|
|
|
597
581
|
Example metrics output:
|
|
@@ -621,17 +605,18 @@ versionRangeRules:
|
|
|
621
605
|
reason: "CVE-2021-23337: Command injection vulnerability"
|
|
622
606
|
```
|
|
623
607
|
|
|
624
|
-
### 2.
|
|
608
|
+
### 2. Steer Installs to a Patched Version
|
|
625
609
|
|
|
626
610
|
```yaml
|
|
627
611
|
versionRangeRules:
|
|
628
612
|
- package: axios
|
|
629
613
|
range: "0.21.1"
|
|
630
|
-
strategy:
|
|
631
|
-
fallbackVersion: "0.21.4"
|
|
614
|
+
strategy: block
|
|
632
615
|
reason: "SSRF vulnerability fix"
|
|
633
616
|
```
|
|
634
617
|
|
|
618
|
+
The blocked version disappears from the package metadata, so a range such as `axios@^0.21.0` resolves to the newest allowed version. A lockfile pinned to the blocked version fails with 403 until it is updated.
|
|
619
|
+
|
|
635
620
|
### 3. Enforce Corporate License Policy
|
|
636
621
|
|
|
637
622
|
```yaml
|
|
@@ -665,7 +650,7 @@ whitelist:
|
|
|
665
650
|
```yaml
|
|
666
651
|
cveCheck:
|
|
667
652
|
enabled: true
|
|
668
|
-
databases: [osv
|
|
653
|
+
databases: [osv]
|
|
669
654
|
severity: [high, critical]
|
|
670
655
|
autoBlock: true
|
|
671
656
|
updateInterval: 12
|
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
|
@@ -64,7 +64,6 @@ class SecurityFilterPlugin {
|
|
|
64
64
|
this.config.errorHandling = {
|
|
65
65
|
onFilterError: 'fail-open',
|
|
66
66
|
onCveCheckError: 'fail-open',
|
|
67
|
-
onLicenseCheckError: 'fail-open',
|
|
68
67
|
};
|
|
69
68
|
}
|
|
70
69
|
this.logger = new logger_1.SecurityLogger(options.logger, this.config.logger);
|
|
@@ -77,11 +76,8 @@ class SecurityFilterPlugin {
|
|
|
77
76
|
this.securityRules = {
|
|
78
77
|
blockedVersions: this.config.blockedVersions || [],
|
|
79
78
|
blockedPatterns: this.config.blockedPatterns || [],
|
|
80
|
-
minPackageSize: this.config.minPackageSize || 0,
|
|
81
|
-
maxPackageSize: this.config.maxPackageSize || 100 * 1024 * 1024,
|
|
82
79
|
allowedScopes: this.config.allowedScopes || [],
|
|
83
80
|
blockedScopes: this.config.blockedScopes || [],
|
|
84
|
-
enforceChecksum: this.config.enforceChecksum !== false,
|
|
85
81
|
versionRangeRules: this._parseVersionRangeRules(this.config.versionRangeRules || [])
|
|
86
82
|
};
|
|
87
83
|
this.blockedPatternRegexes = this.securityRules.blockedPatterns.flatMap(pattern => {
|
|
@@ -310,9 +306,23 @@ class SecurityFilterPlugin {
|
|
|
310
306
|
}
|
|
311
307
|
return Object.assign(Object.assign({}, pkg), { versions, 'dist-tags': distTags });
|
|
312
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
|
+
}
|
|
313
323
|
filter_metadata(packageInfo) {
|
|
314
324
|
return __awaiter(this, void 0, void 0, function* () {
|
|
315
|
-
var _a, _b, _c, _d, _e
|
|
325
|
+
var _a, _b, _c, _d, _e;
|
|
316
326
|
let pkg = packageInfo;
|
|
317
327
|
const packageName = pkg.name;
|
|
318
328
|
this.logger.debug(`[filter_metadata] --> Processing: ${packageName}`);
|
|
@@ -323,9 +333,10 @@ class SecurityFilterPlugin {
|
|
|
323
333
|
this.metrics.recordBlock(packageName, '*', blockResult.reason);
|
|
324
334
|
return this._blockedPackage(pkg, blockResult.reason, { rules: this._getAppliedRules(packageName) });
|
|
325
335
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
336
|
+
const whitelistMode = this.config.mode === 'whitelist';
|
|
337
|
+
if (whitelistMode || this.securityRules.blockedVersions.length > 0 || this.securityRules.versionRangeRules.length > 0) {
|
|
338
|
+
const ruleBlocked = Object.keys(pkg.versions || {}).filter(version => this._versionBlockReason(packageName, version) !== null ||
|
|
339
|
+
(whitelistMode && !this.whitelistChecker.isWhitelisted(packageName, version).allowed));
|
|
329
340
|
if (ruleBlocked.length > 0) {
|
|
330
341
|
this.logger.warn(`[filter_metadata] RULE FILTERED: ${packageName}: ${ruleBlocked.join(', ')}`);
|
|
331
342
|
pkg = this._removeVersions(pkg, ruleBlocked);
|
|
@@ -364,19 +375,22 @@ class SecurityFilterPlugin {
|
|
|
364
375
|
}
|
|
365
376
|
}
|
|
366
377
|
if (this.config.licenses && this.config.licenses.allowed && this.config.licenses.allowed.length > 0) {
|
|
367
|
-
const
|
|
368
|
-
if (
|
|
369
|
-
const
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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');
|
|
376
389
|
}
|
|
390
|
+
pkg = this._removeVersions(pkg, rejected.versions);
|
|
377
391
|
}
|
|
378
392
|
}
|
|
379
|
-
if ((
|
|
393
|
+
if ((_c = this.config.packageAge) === null || _c === void 0 ? void 0 : _c.enabled) {
|
|
380
394
|
const ageResult = this.packageAgeChecker.checkPackageAge(pkg);
|
|
381
395
|
if (!ageResult.allowed && !ageResult.warnOnly) {
|
|
382
396
|
const reason = ageResult.reason || 'Package too new';
|
|
@@ -403,33 +417,38 @@ class SecurityFilterPlugin {
|
|
|
403
417
|
pkg = this._removeVersions(pkg, removedVersions);
|
|
404
418
|
}
|
|
405
419
|
}
|
|
406
|
-
if ((
|
|
407
|
-
const
|
|
408
|
-
if (
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
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) {
|
|
414
427
|
this.metrics.record({
|
|
415
428
|
timestamp: new Date().toISOString(),
|
|
416
429
|
event: 'author_blocked',
|
|
417
430
|
packageName,
|
|
418
|
-
version:
|
|
419
|
-
reason,
|
|
431
|
+
version: rejected.all ? '*' : version,
|
|
432
|
+
reason: result.reason || 'Author not allowed',
|
|
420
433
|
metadata: {
|
|
421
|
-
blockedBy:
|
|
422
|
-
authorInfo:
|
|
434
|
+
blockedBy: result.blockedBy,
|
|
435
|
+
authorInfo: result.authorInfo,
|
|
423
436
|
},
|
|
424
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}`);
|
|
425
445
|
return this._blockedPackage(pkg, reason, {
|
|
426
|
-
blockedBy:
|
|
427
|
-
authorInfo:
|
|
446
|
+
blockedBy: rejected.first.blockedBy,
|
|
447
|
+
authorInfo: rejected.first.authorInfo,
|
|
428
448
|
});
|
|
429
449
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
450
|
+
this.logger.warn(`[filter_metadata] AUTHOR FILTERED: ${packageName}: ${rejected.versions.join(', ')}`);
|
|
451
|
+
pkg = this._removeVersions(pkg, rejected.versions);
|
|
433
452
|
}
|
|
434
453
|
}
|
|
435
454
|
this.logger.debug(`[filter_metadata] [OK] ALLOWED: ${packageName}`);
|
|
@@ -437,7 +456,7 @@ class SecurityFilterPlugin {
|
|
|
437
456
|
}
|
|
438
457
|
catch (error) {
|
|
439
458
|
this.logger.error(`[filter_metadata] Error processing ${packageName}: ${error.message}`);
|
|
440
|
-
const errorStrategy = ((
|
|
459
|
+
const errorStrategy = ((_e = this.config.errorHandling) === null || _e === void 0 ? void 0 : _e.onFilterError) || 'fail-open';
|
|
441
460
|
if (errorStrategy === 'fail-closed') {
|
|
442
461
|
this.logger.warn(`[filter_metadata] Error handling: fail-closed - BLOCKING ${packageName}`);
|
|
443
462
|
this.metrics.recordBlock(packageName, '*', `Error during processing: ${error.message}`);
|
package/build/lib/metrics.d.ts
CHANGED
|
@@ -9,9 +9,7 @@ export declare class MetricsCollector {
|
|
|
9
9
|
constructor(config?: MetricsConfig);
|
|
10
10
|
record(event: MetricsData): void;
|
|
11
11
|
recordBlock(packageName: string, version: string, reason: string, metadata?: Record<string, any>): void;
|
|
12
|
-
recordFallback(packageName: string, fromVersion: string, toVersion: string, reason: string): void;
|
|
13
12
|
recordPublishRejected(packageName: string, version: string, reason: string): void;
|
|
14
|
-
recordCVE(packageName: string, version: string, cveId: string, severity: string): void;
|
|
15
13
|
recordLicenseBlock(packageName: string, version: string, license: string, reason: string): void;
|
|
16
14
|
recordPackageTooNew(packageName: string, version: string, reason: string, metadata?: Record<string, any>): void;
|
|
17
15
|
flush(): void;
|
package/build/lib/metrics.js
CHANGED
|
@@ -75,16 +75,6 @@ class MetricsCollector {
|
|
|
75
75
|
metadata,
|
|
76
76
|
});
|
|
77
77
|
}
|
|
78
|
-
recordFallback(packageName, fromVersion, toVersion, reason) {
|
|
79
|
-
this.record({
|
|
80
|
-
timestamp: new Date().toISOString(),
|
|
81
|
-
event: 'fallback',
|
|
82
|
-
packageName,
|
|
83
|
-
version: fromVersion,
|
|
84
|
-
reason,
|
|
85
|
-
metadata: { toVersion },
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
78
|
recordPublishRejected(packageName, version, reason) {
|
|
89
79
|
this.record({
|
|
90
80
|
timestamp: new Date().toISOString(),
|
|
@@ -94,16 +84,6 @@ class MetricsCollector {
|
|
|
94
84
|
reason,
|
|
95
85
|
});
|
|
96
86
|
}
|
|
97
|
-
recordCVE(packageName, version, cveId, severity) {
|
|
98
|
-
this.record({
|
|
99
|
-
timestamp: new Date().toISOString(),
|
|
100
|
-
event: 'cve_detected',
|
|
101
|
-
packageName,
|
|
102
|
-
version,
|
|
103
|
-
reason: `CVE ${cveId} (${severity})`,
|
|
104
|
-
metadata: { cveId, severity },
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
87
|
recordLicenseBlock(packageName, version, license, reason) {
|
|
108
88
|
this.record({
|
|
109
89
|
timestamp: new Date().toISOString(),
|
|
@@ -12,13 +12,8 @@ export declare class WhitelistChecker {
|
|
|
12
12
|
addPackage(packageName: string, versionRange?: string): void;
|
|
13
13
|
removePackage(packageName: string): void;
|
|
14
14
|
addPattern(pattern: string): void;
|
|
15
|
-
meetsAutoApproveCriteria(_packageName: string): Promise<{
|
|
16
|
-
approved: boolean;
|
|
17
|
-
reason?: string;
|
|
18
|
-
}>;
|
|
19
15
|
getSummary(): {
|
|
20
16
|
totalPackages: number;
|
|
21
17
|
totalPatterns: number;
|
|
22
|
-
hasAutoApprove: boolean;
|
|
23
18
|
};
|
|
24
19
|
}
|
|
@@ -32,15 +32,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
36
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
37
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
38
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
39
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
40
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
41
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
36
|
exports.WhitelistChecker = void 0;
|
|
46
37
|
const semver = __importStar(require("semver"));
|
|
@@ -51,7 +42,6 @@ class WhitelistChecker {
|
|
|
51
42
|
packages: (config === null || config === void 0 ? void 0 : config.packages) || [],
|
|
52
43
|
patterns: (config === null || config === void 0 ? void 0 : config.patterns) || [],
|
|
53
44
|
versions: (config === null || config === void 0 ? void 0 : config.versions) || {},
|
|
54
|
-
autoApprove: config === null || config === void 0 ? void 0 : config.autoApprove,
|
|
55
45
|
};
|
|
56
46
|
}
|
|
57
47
|
isWhitelisted(packageName, version) {
|
|
@@ -115,22 +105,10 @@ class WhitelistChecker {
|
|
|
115
105
|
this.config.patterns.push(pattern);
|
|
116
106
|
}
|
|
117
107
|
}
|
|
118
|
-
meetsAutoApproveCriteria(_packageName) {
|
|
119
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
120
|
-
if (!this.config.autoApprove) {
|
|
121
|
-
return { approved: false, reason: 'Auto-approve not configured' };
|
|
122
|
-
}
|
|
123
|
-
return {
|
|
124
|
-
approved: false,
|
|
125
|
-
reason: 'Auto-approve requires npm registry API integration (not implemented)',
|
|
126
|
-
};
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
108
|
getSummary() {
|
|
130
109
|
return {
|
|
131
110
|
totalPackages: this.config.packages.length,
|
|
132
111
|
totalPatterns: this.config.patterns.length,
|
|
133
|
-
hasAutoApprove: !!this.config.autoApprove,
|
|
134
112
|
};
|
|
135
113
|
}
|
|
136
114
|
}
|
package/build/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Config
|
|
1
|
+
import { Config } from '@verdaccio/types';
|
|
2
2
|
export interface VersionRangeRule {
|
|
3
3
|
package: string;
|
|
4
4
|
range: string;
|
|
@@ -8,7 +8,7 @@ export interface VersionRangeRule {
|
|
|
8
8
|
}
|
|
9
9
|
export interface CVECheckConfig {
|
|
10
10
|
enabled: boolean;
|
|
11
|
-
databases:
|
|
11
|
+
databases: 'osv'[];
|
|
12
12
|
severity: ('low' | 'medium' | 'high' | 'critical')[];
|
|
13
13
|
autoBlock: boolean;
|
|
14
14
|
updateInterval?: number;
|
|
@@ -18,11 +18,6 @@ export interface WhitelistConfig {
|
|
|
18
18
|
packages: string[];
|
|
19
19
|
patterns: string[];
|
|
20
20
|
versions?: Record<string, string>;
|
|
21
|
-
autoApprove?: {
|
|
22
|
-
minDownloads?: number;
|
|
23
|
-
minStars?: number;
|
|
24
|
-
verifiedPublisher?: boolean;
|
|
25
|
-
};
|
|
26
21
|
}
|
|
27
22
|
export interface LicenseConfig {
|
|
28
23
|
allowed: string[];
|
|
@@ -59,11 +54,8 @@ export interface AuthorFilterConfig {
|
|
|
59
54
|
export interface SecurityConfig extends Config {
|
|
60
55
|
blockedVersions?: string[];
|
|
61
56
|
blockedPatterns?: string[];
|
|
62
|
-
minPackageSize?: number;
|
|
63
|
-
maxPackageSize?: number;
|
|
64
57
|
allowedScopes?: string[];
|
|
65
58
|
blockedScopes?: string[];
|
|
66
|
-
enforceChecksum?: boolean;
|
|
67
59
|
versionRangeRules?: VersionRangeRule[];
|
|
68
60
|
mode?: 'blacklist' | 'whitelist';
|
|
69
61
|
whitelist?: WhitelistConfig;
|
|
@@ -76,32 +68,15 @@ export interface SecurityConfig extends Config {
|
|
|
76
68
|
errorHandling?: {
|
|
77
69
|
onFilterError?: 'fail-open' | 'fail-closed';
|
|
78
70
|
onCveCheckError?: 'fail-open' | 'fail-closed';
|
|
79
|
-
onLicenseCheckError?: 'fail-open' | 'fail-closed';
|
|
80
71
|
};
|
|
81
72
|
}
|
|
82
73
|
export interface SecurityRules {
|
|
83
74
|
blockedVersions: string[];
|
|
84
75
|
blockedPatterns: string[];
|
|
85
|
-
minPackageSize: number;
|
|
86
|
-
maxPackageSize: number;
|
|
87
76
|
allowedScopes: string[];
|
|
88
77
|
blockedScopes: string[];
|
|
89
|
-
enforceChecksum: boolean;
|
|
90
78
|
versionRangeRules: VersionRangeRule[];
|
|
91
79
|
}
|
|
92
|
-
export interface VersionData extends Version {
|
|
93
|
-
_fallback?: boolean;
|
|
94
|
-
_fallbackFrom?: string;
|
|
95
|
-
_fallbackReason?: string;
|
|
96
|
-
}
|
|
97
|
-
export interface FilterResult {
|
|
98
|
-
versions: Record<string, VersionData>;
|
|
99
|
-
blockedVersions: string[];
|
|
100
|
-
fallbacksApplied: Array<{
|
|
101
|
-
original: string;
|
|
102
|
-
fallback: string;
|
|
103
|
-
}>;
|
|
104
|
-
}
|
|
105
80
|
export interface CVEVulnerability {
|
|
106
81
|
id: string;
|
|
107
82
|
severity: 'low' | 'medium' | 'high' | 'critical';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "verdaccio-security-filter",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.12",
|
|
4
4
|
"description": "Advanced security filter plugin for Verdaccio with version range blocking and fallback strategies",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"test": "jest",
|
|
29
29
|
"test:watch": "jest --watch",
|
|
30
30
|
"test:coverage": "jest --coverage",
|
|
31
|
+
"test:e2e": "npm run build && jest --testMatch \"**/*.e2e.ts\" --coverage=false",
|
|
31
32
|
"debug:verdaccio": "node --inspect-brk $(which verdaccio)",
|
|
32
33
|
"debug:build": "npm run build && npm link",
|
|
33
34
|
"prepack": "npm run build",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
},
|
|
56
57
|
"overrides": {
|
|
57
58
|
"qs": "6.16.0",
|
|
58
|
-
"@cypress/request": "
|
|
59
|
+
"@cypress/request": "3.0.10"
|
|
59
60
|
},
|
|
60
61
|
"author": {
|
|
61
62
|
"name": "Vitaliy Ponomarenko",
|