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 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
- - [ ] **Dry Run Mode** - Test rules without actually blocking
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;
@@ -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;
@@ -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',
@@ -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;
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verdaccio-security-filter",
3
- "version": "1.3.12",
3
+ "version": "1.4.0",
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",