verdaccio-security-filter 1.3.12 → 1.4.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/README.md +20 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +25 -2
- package/build/lib/logger.d.ts +1 -1
- package/build/lib/logger.js +2 -2
- package/build/lib/metrics.d.ts +2 -1
- package/build/lib/metrics.js +3 -2
- package/build/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,7 +117,7 @@ This plugin implements **two independent security layers** for complete protecti
|
|
|
117
117
|
- [ ] **ML-based Anomaly Detection** - Machine learning for threat detection
|
|
118
118
|
|
|
119
119
|
#### Phase 5: Developer Experience
|
|
120
|
-
- [
|
|
120
|
+
- [x] **Dry Run Mode** - Test rules without actually blocking (`dryRun: true`)
|
|
121
121
|
- [ ] **Auto-approve Criteria** - Automatic approval based on npm stats (downloads, stars)
|
|
122
122
|
- [ ] **CLI Tool** - Command-line interface for rule management
|
|
123
123
|
- [ ] **Visual Studio Code Extension** - IDE integration
|
|
@@ -406,11 +406,30 @@ middlewares:
|
|
|
406
406
|
| Option | Type | Default | Description |
|
|
407
407
|
|--------|------|---------|-------------|
|
|
408
408
|
| `mode` | `string` | `blacklist` | Filter mode: `blacklist` or `whitelist` |
|
|
409
|
+
| `dryRun` | `boolean` | `false` | Log and record every decision, but block nothing. See [Dry-run mode](#dry-run-mode) |
|
|
409
410
|
| `blockedVersions` | `string[]` | `[]` | List of `package@version` to block |
|
|
410
411
|
| `blockedPatterns` | `string[]` | `[]` | Regex patterns for package names |
|
|
411
412
|
| `allowedScopes` | `string[]` | `[]` | Allowed package scopes |
|
|
412
413
|
| `blockedScopes` | `string[]` | `[]` | Blocked package scopes |
|
|
413
414
|
|
|
415
|
+
### Dry-run mode
|
|
416
|
+
|
|
417
|
+
Set `dryRun: true` to see what a rule set would do before enforcing it. Put it in both the `filters` and the `middlewares` block, like the rest of the configuration.
|
|
418
|
+
|
|
419
|
+
```yaml
|
|
420
|
+
dryRun: true
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
While it is on:
|
|
424
|
+
|
|
425
|
+
- every check runs as usual, including CVE lookups
|
|
426
|
+
- nothing is blocked: metadata is served unchanged and every tarball can be downloaded
|
|
427
|
+
- each decision is logged at `warn` with a `[DRY RUN]` marker, followed by a summary such as `lodash: would remove 115 of 117 version(s)`
|
|
428
|
+
- metrics events carry `"dryRun": true`
|
|
429
|
+
- one warning at start-up says that dry-run is active
|
|
430
|
+
|
|
431
|
+
Recommended before turning on `cveCheck.autoBlock`, a license policy or the author filter on a registry that is already in use.
|
|
432
|
+
|
|
414
433
|
### CVE Check Options
|
|
415
434
|
|
|
416
435
|
| Option | Type | Default | Description |
|
package/build/index.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
|
|
|
25
25
|
private _removeVersions;
|
|
26
26
|
private _rejectedVersions;
|
|
27
27
|
filter_metadata(packageInfo: Package): Promise<Package>;
|
|
28
|
+
private _filterMetadata;
|
|
28
29
|
private _versionBlockReason;
|
|
29
30
|
private _checkPackageBlock;
|
|
30
31
|
private _getAppliedRules;
|
package/build/index.js
CHANGED
|
@@ -66,8 +66,8 @@ class SecurityFilterPlugin {
|
|
|
66
66
|
onCveCheckError: 'fail-open',
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
|
-
this.logger = new logger_1.SecurityLogger(options.logger, this.config.logger);
|
|
70
|
-
this.metrics = new metrics_1.MetricsCollector(this.config.metrics);
|
|
69
|
+
this.logger = new logger_1.SecurityLogger(options.logger, this.config.logger, !!this.config.dryRun);
|
|
70
|
+
this.metrics = new metrics_1.MetricsCollector(this.config.metrics, !!this.config.dryRun);
|
|
71
71
|
this.whitelistChecker = new whitelist_checker_1.WhitelistChecker(this.config.whitelist);
|
|
72
72
|
this.cveChecker = new cve_checker_1.CVEChecker(this.config.cveCheck);
|
|
73
73
|
this.licenseChecker = new license_checker_1.LicenseChecker(this.config.licenses);
|
|
@@ -90,6 +90,9 @@ class SecurityFilterPlugin {
|
|
|
90
90
|
}
|
|
91
91
|
});
|
|
92
92
|
this._logInitialization();
|
|
93
|
+
if (this.config.dryRun) {
|
|
94
|
+
this.logger.warn('Dry-run mode is on: decisions are logged and recorded in metrics, but nothing is blocked');
|
|
95
|
+
}
|
|
93
96
|
}
|
|
94
97
|
_parseVersionRangeRules(rules) {
|
|
95
98
|
return rules.map((rule, index) => {
|
|
@@ -140,6 +143,8 @@ class SecurityFilterPlugin {
|
|
|
140
143
|
_getEnabledFeatures() {
|
|
141
144
|
var _a, _b, _c, _d, _e;
|
|
142
145
|
const features = [];
|
|
146
|
+
if (this.config.dryRun)
|
|
147
|
+
features.push('dry-run');
|
|
143
148
|
if (this.config.mode === 'whitelist')
|
|
144
149
|
features.push('whitelist-mode');
|
|
145
150
|
if ((_a = this.config.metrics) === null || _a === void 0 ? void 0 : _a.enabled)
|
|
@@ -225,6 +230,9 @@ class SecurityFilterPlugin {
|
|
|
225
230
|
if (blockResult.blocked) {
|
|
226
231
|
this.logger.warn(`[Middleware] [X] BLOCKED TARBALL: ${packageName}@${version || '*'} - ${blockResult.reason}`);
|
|
227
232
|
this.metrics.recordBlock(packageName, version || '*', blockResult.reason);
|
|
233
|
+
if (this.config.dryRun) {
|
|
234
|
+
return next();
|
|
235
|
+
}
|
|
228
236
|
res.status(403).json({
|
|
229
237
|
error: 'Package blocked by security filter',
|
|
230
238
|
package: packageName,
|
|
@@ -241,6 +249,9 @@ class SecurityFilterPlugin {
|
|
|
241
249
|
const blockResult = yield this._checkPackageBlock(packageName);
|
|
242
250
|
if (blockResult.blocked) {
|
|
243
251
|
this.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
|
|
252
|
+
if (this.config.dryRun) {
|
|
253
|
+
return next();
|
|
254
|
+
}
|
|
244
255
|
const blockedResponse = {
|
|
245
256
|
name: packageName,
|
|
246
257
|
versions: {},
|
|
@@ -321,6 +332,18 @@ class SecurityFilterPlugin {
|
|
|
321
332
|
};
|
|
322
333
|
}
|
|
323
334
|
filter_metadata(packageInfo) {
|
|
335
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
336
|
+
const filtered = yield this._filterMetadata(packageInfo);
|
|
337
|
+
if (!this.config.dryRun || filtered === packageInfo) {
|
|
338
|
+
return filtered;
|
|
339
|
+
}
|
|
340
|
+
const total = Object.keys(packageInfo.versions || {}).length;
|
|
341
|
+
const left = Object.keys(filtered.versions || {}).length;
|
|
342
|
+
this.logger.warn(`[filter_metadata] ${packageInfo.name}: would remove ${total - left} of ${total} version(s)`);
|
|
343
|
+
return packageInfo;
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
_filterMetadata(packageInfo) {
|
|
324
347
|
return __awaiter(this, void 0, void 0, function* () {
|
|
325
348
|
var _a, _b, _c, _d, _e;
|
|
326
349
|
let pkg = packageInfo;
|
package/build/lib/logger.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare class SecurityLogger {
|
|
|
4
4
|
private verdaccioLogger;
|
|
5
5
|
private config;
|
|
6
6
|
private readonly prefix;
|
|
7
|
-
constructor(verdaccioLogger: Logger, config?: LoggerConfig);
|
|
7
|
+
constructor(verdaccioLogger: Logger, config?: LoggerConfig, dryRun?: boolean);
|
|
8
8
|
private shouldLog;
|
|
9
9
|
private formatMessage;
|
|
10
10
|
trace(message: string, ...args: any[]): void;
|
package/build/lib/logger.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SecurityLogger = void 0;
|
|
4
4
|
class SecurityLogger {
|
|
5
|
-
constructor(verdaccioLogger, config) {
|
|
6
|
-
this.prefix = '[Security Filter]';
|
|
5
|
+
constructor(verdaccioLogger, config, dryRun = false) {
|
|
6
|
+
this.prefix = dryRun ? '[Security Filter] [DRY RUN]' : '[Security Filter]';
|
|
7
7
|
this.verdaccioLogger = verdaccioLogger;
|
|
8
8
|
this.config = {
|
|
9
9
|
level: (config === null || config === void 0 ? void 0 : config.level) || 'info',
|
package/build/lib/metrics.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { MetricsConfig, MetricsData } from '../types';
|
|
2
2
|
export declare class MetricsCollector {
|
|
3
|
+
private readonly dryRun;
|
|
3
4
|
private config;
|
|
4
5
|
private metricsBuffer;
|
|
5
6
|
private readonly bufferSize;
|
|
6
7
|
private readonly maxBufferSize;
|
|
7
8
|
private readonly flushIntervalMs;
|
|
8
9
|
private flushTimer;
|
|
9
|
-
constructor(config?: MetricsConfig);
|
|
10
|
+
constructor(config?: MetricsConfig, dryRun?: boolean);
|
|
10
11
|
record(event: MetricsData): void;
|
|
11
12
|
recordBlock(packageName: string, version: string, reason: string, metadata?: Record<string, any>): void;
|
|
12
13
|
recordPublishRejected(packageName: string, version: string, reason: string): void;
|
package/build/lib/metrics.js
CHANGED
|
@@ -37,7 +37,8 @@ exports.MetricsCollector = void 0;
|
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
class MetricsCollector {
|
|
40
|
-
constructor(config) {
|
|
40
|
+
constructor(config, dryRun = false) {
|
|
41
|
+
this.dryRun = dryRun;
|
|
41
42
|
this.metricsBuffer = [];
|
|
42
43
|
this.bufferSize = 100;
|
|
43
44
|
this.maxBufferSize = 500;
|
|
@@ -60,7 +61,7 @@ class MetricsCollector {
|
|
|
60
61
|
if (this.metricsBuffer.length >= this.maxBufferSize) {
|
|
61
62
|
this.flush();
|
|
62
63
|
}
|
|
63
|
-
this.metricsBuffer.push(event);
|
|
64
|
+
this.metricsBuffer.push(this.dryRun ? Object.assign(Object.assign({}, event), { dryRun: true }) : event);
|
|
64
65
|
if (this.metricsBuffer.length >= this.bufferSize) {
|
|
65
66
|
this.flush();
|
|
66
67
|
}
|
package/build/types.d.ts
CHANGED
|
@@ -58,6 +58,7 @@ export interface SecurityConfig extends Config {
|
|
|
58
58
|
blockedScopes?: string[];
|
|
59
59
|
versionRangeRules?: VersionRangeRule[];
|
|
60
60
|
mode?: 'blacklist' | 'whitelist';
|
|
61
|
+
dryRun?: boolean;
|
|
61
62
|
whitelist?: WhitelistConfig;
|
|
62
63
|
licenses?: LicenseConfig;
|
|
63
64
|
cveCheck?: CVECheckConfig;
|
|
@@ -100,6 +101,7 @@ export interface MetricsData {
|
|
|
100
101
|
version?: string;
|
|
101
102
|
reason: string;
|
|
102
103
|
metadata?: Record<string, any>;
|
|
104
|
+
dryRun?: boolean;
|
|
103
105
|
}
|
|
104
106
|
export interface AuthorInfo {
|
|
105
107
|
name?: string;
|
package/package.json
CHANGED