verdaccio-security-filter 1.3.0 → 1.3.2

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
@@ -2,6 +2,7 @@
2
2
 
3
3
  Advanced security filter plugin for Verdaccio with **dual-layer protection architecture** combining middleware interception and metadata filtering for comprehensive package security.
4
4
 
5
+ [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/badges/StandWithUkraine.svg)](https://stand-with-ukraine.pp.ua)
5
6
  [![npm version](https://img.shields.io/npm/v/verdaccio-security-filter.svg)](https://www.npmjs.com/package/verdaccio-security-filter)
6
7
  [![npm downloads](https://img.shields.io/npm/dm/verdaccio-security-filter.svg)](https://www.npmjs.com/package/verdaccio-security-filter)
7
8
  [![CI](https://github.com/ponomarenko/verdaccio-security-filter/actions/workflows/ci.yml/badge.svg)](https://github.com/ponomarenko/verdaccio-security-filter/actions/workflows/ci.yml)
package/build/index.d.ts CHANGED
@@ -2,15 +2,6 @@ import { IPluginMiddleware, IPluginStorageFilter, PluginOptions, IBasicAuth, ISt
2
2
  import { Application } from 'express';
3
3
  import { SecurityConfig } from './types';
4
4
  import { SecurityLogger } from './lib/logger';
5
- /**
6
- * Security Filter Plugin for Verdaccio
7
- * Works with both middleware and filter interfaces for complete protection
8
- * Compatible with Verdaccio 6.x and 7.x
9
- *
10
- * Two-layer protection:
11
- * - Layer 1 (filter_metadata): CVE/License/Age checks when metadata is available
12
- * - Layer 2 (middleware): Whitelist/Pattern/Scope checks + tarball blocking
13
- */
14
5
  export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityConfig>, IPluginStorageFilter<SecurityConfig> {
15
6
  logger: SecurityLogger;
16
7
  config: SecurityConfig;
@@ -21,62 +12,18 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
21
12
  private readonly licenseChecker;
22
13
  private readonly packageAgeChecker;
23
14
  private readonly authorChecker;
24
- /**
25
- * Creates a new SecurityFilterPlugin instance
26
- * @param {SecurityConfig} config - Plugin configuration
27
- * @param {PluginOptions} options - Verdaccio plugin options
28
- */
29
15
  constructor(config: SecurityConfig, options: PluginOptions<SecurityConfig>);
30
- /**
31
- * Parse and validate version range rules
32
- * @private
33
- */
34
16
  private _parseVersionRangeRules;
35
- /**
36
- * Log initialization details
37
- * @private
38
- */
39
17
  private _logInitialization;
40
- /**
41
- * Get list of enabled features
42
- * @private
43
- */
44
18
  private _getEnabledFeatures;
45
- /**
46
- * Check if package name matches blocked patterns
47
- * @private
48
- */
49
19
  private _isBlockedByPattern;
50
- /**
51
- * Check if package scope is allowed
52
- * @private
53
- */
54
20
  private _isScopeAllowed;
55
- /**
56
- * Get version range rule for a specific package and version
57
- * @private
58
- */
59
21
  private _getVersionRangeRule;
60
- /**
61
- * Register Express middleware to intercept package requests
62
- * Intercepts both metadata and tarball requests
63
- */
64
22
  register_middlewares(app: Application, _auth: IBasicAuth<SecurityConfig>, _storage: IStorageManager<SecurityConfig>): void;
65
- /**
66
- * Filter package metadata before returning to client
67
- * This provides CVE, License, and Age checking when metadata is available
68
- */
69
23
  filter_metadata(packageInfo: Package): Promise<Package>;
70
- /**
71
- * Check if package should be blocked (used by middleware)
72
- * @private
73
- */
74
24
  private _checkPackageBlock;
75
- /**
76
- * Get applied security rules for a package
77
- * @private
78
- */
79
25
  private _getAppliedRules;
26
+ destroy(): void;
80
27
  }
81
28
  export { SecurityLogger } from './lib/logger';
82
29
  export { MetricsCollector } from './lib/metrics';
package/build/index.js CHANGED
@@ -54,24 +54,9 @@ const cve_checker_1 = require("./lib/cve-checker");
54
54
  const license_checker_1 = require("./lib/license-checker");
55
55
  const package_age_checker_1 = require("./lib/package-age-checker");
56
56
  const author_checker_1 = require("./lib/author-checker");
57
- /**
58
- * Security Filter Plugin for Verdaccio
59
- * Works with both middleware and filter interfaces for complete protection
60
- * Compatible with Verdaccio 6.x and 7.x
61
- *
62
- * Two-layer protection:
63
- * - Layer 1 (filter_metadata): CVE/License/Age checks when metadata is available
64
- * - Layer 2 (middleware): Whitelist/Pattern/Scope checks + tarball blocking
65
- */
66
57
  class SecurityFilterPlugin {
67
- /**
68
- * Creates a new SecurityFilterPlugin instance
69
- * @param {SecurityConfig} config - Plugin configuration
70
- * @param {PluginOptions} options - Verdaccio plugin options
71
- */
72
58
  constructor(config, options) {
73
59
  this.config = config || {};
74
- // Set default error handling strategy if not provided
75
60
  if (!this.config.errorHandling) {
76
61
  this.config.errorHandling = {
77
62
  onFilterError: 'fail-open',
@@ -79,23 +64,18 @@ class SecurityFilterPlugin {
79
64
  onLicenseCheckError: 'fail-open',
80
65
  };
81
66
  }
82
- // Initialize enhanced logger
83
67
  this.logger = new logger_1.SecurityLogger(options.logger, this.config.logger);
84
- // Initialize metrics collector
85
68
  this.metrics = new metrics_1.MetricsCollector(this.config.metrics);
86
- // Initialize whitelist checker
87
69
  this.whitelistChecker = new whitelist_checker_1.WhitelistChecker(this.config.whitelist);
88
- // Initialize security checkers
89
70
  this.cveChecker = new cve_checker_1.CVEChecker(this.config.cveCheck);
90
71
  this.licenseChecker = new license_checker_1.LicenseChecker(this.config.licenses);
91
72
  this.packageAgeChecker = new package_age_checker_1.PackageAgeChecker(this.config.packageAge);
92
73
  this.authorChecker = new author_checker_1.AuthorChecker(this.config.authorFilter);
93
- // Security rules configuration
94
74
  this.securityRules = {
95
75
  blockedVersions: this.config.blockedVersions || [],
96
76
  blockedPatterns: this.config.blockedPatterns || [],
97
77
  minPackageSize: this.config.minPackageSize || 0,
98
- maxPackageSize: this.config.maxPackageSize || 100 * 1024 * 1024, // 100MB
78
+ maxPackageSize: this.config.maxPackageSize || 100 * 1024 * 1024,
99
79
  allowedScopes: this.config.allowedScopes || [],
100
80
  blockedScopes: this.config.blockedScopes || [],
101
81
  enforceChecksum: this.config.enforceChecksum !== false,
@@ -103,10 +83,6 @@ class SecurityFilterPlugin {
103
83
  };
104
84
  this._logInitialization();
105
85
  }
106
- /**
107
- * Parse and validate version range rules
108
- * @private
109
- */
110
86
  _parseVersionRangeRules(rules) {
111
87
  return rules.map((rule, index) => {
112
88
  if (!rule.package || !rule.range || !rule.strategy) {
@@ -117,7 +93,6 @@ class SecurityFilterPlugin {
117
93
  this.logger.warn(`Fallback strategy requires fallbackVersion for ${rule.package}, skipping`);
118
94
  return null;
119
95
  }
120
- // Validate semver range
121
96
  try {
122
97
  const validRange = semver.validRange(rule.range);
123
98
  if (!validRange) {
@@ -132,10 +107,6 @@ class SecurityFilterPlugin {
132
107
  return rule;
133
108
  }).filter((rule) => rule !== null);
134
109
  }
135
- /**
136
- * Log initialization details
137
- * @private
138
- */
139
110
  _logInitialization() {
140
111
  var _a;
141
112
  const features = this._getEnabledFeatures();
@@ -159,10 +130,6 @@ class SecurityFilterPlugin {
159
130
  this.logger.info(`Author filtering enabled: ${summary.blockedAuthors} authors, ${summary.blockedEmails} emails, ${summary.blockedRegions} regions blocked`);
160
131
  }
161
132
  }
162
- /**
163
- * Get list of enabled features
164
- * @private
165
- */
166
133
  _getEnabledFeatures() {
167
134
  var _a, _b, _c, _d, _e;
168
135
  const features = [];
@@ -188,40 +155,25 @@ class SecurityFilterPlugin {
188
155
  features.push('scope-blocking');
189
156
  return features;
190
157
  }
191
- /**
192
- * Check if package name matches blocked patterns
193
- * @private
194
- */
195
158
  _isBlockedByPattern(packageName) {
196
159
  return this.securityRules.blockedPatterns.some(pattern => {
197
160
  const regex = new RegExp(pattern);
198
161
  return regex.test(packageName);
199
162
  });
200
163
  }
201
- /**
202
- * Check if package scope is allowed
203
- * @private
204
- */
205
164
  _isScopeAllowed(packageName) {
206
- // If not a scoped package, check if allowedScopes is empty
207
165
  if (!packageName.startsWith('@')) {
208
166
  return this.securityRules.allowedScopes.length === 0;
209
167
  }
210
168
  const scope = packageName.split('/')[0];
211
- // Check blacklist
212
169
  if (this.securityRules.blockedScopes.includes(scope)) {
213
170
  return false;
214
171
  }
215
- // Check whitelist (if exists)
216
172
  if (this.securityRules.allowedScopes.length > 0) {
217
173
  return this.securityRules.allowedScopes.includes(scope);
218
174
  }
219
175
  return true;
220
176
  }
221
- /**
222
- * Get version range rule for a specific package and version
223
- * @private
224
- */
225
177
  _getVersionRangeRule(packageName, version) {
226
178
  return this.securityRules.versionRangeRules.find(rule => {
227
179
  if (rule.package !== packageName) {
@@ -236,43 +188,28 @@ class SecurityFilterPlugin {
236
188
  }
237
189
  }) || null;
238
190
  }
239
- /**
240
- * Register Express middleware to intercept package requests
241
- * Intercepts both metadata and tarball requests
242
- */
243
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
244
191
  register_middlewares(app, _auth, _storage) {
245
192
  this.logger.info('[Middleware] Registering security filter middleware');
246
193
  this.logger.info(`[Middleware] Config mode: ${this.config.mode || 'NOT SET'}`);
247
- // Middleware to intercept all package requests
248
194
  app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
249
195
  try {
250
- // Extract package name and version from URL
251
- // Tarball URLs: /:package/-/:filename.tgz
252
- // Scoped tarball URLs: /@scope/:package/-/:filename.tgz
253
- // Metadata URLs: /:package or /@scope/:package
254
196
  const urlMatch = req.url.match(/^\/(@[^/]+\/)?([^/]+)(?:\/-\/([^/]+\.tgz))?/);
255
197
  if (!urlMatch) {
256
- // Not a package-related request, pass through
257
198
  this.logger.debug(`[Middleware] Non-package request: ${req.method} ${req.url}`);
258
199
  return next();
259
200
  }
260
201
  const scope = urlMatch[1] || '';
261
202
  const packageName = scope + urlMatch[2];
262
203
  const tarballFile = urlMatch[3];
263
- // Extract version from tarball filename if present
264
204
  let version;
265
205
  if (tarballFile) {
266
- // This is a tarball download request - block it immediately
267
206
  const versionMatch = tarballFile.match(/-([\d.]+(?:-[a-zA-Z0-9.]+)?)\.tgz$/);
268
207
  version = versionMatch ? versionMatch[1] : undefined;
269
208
  this.logger.info(`[Middleware] TARBALL REQUEST: ${packageName}@${version || 'unknown'}`);
270
- // Apply all security checks for tarball downloads
271
209
  const blockResult = yield this._checkPackageBlock(packageName, version);
272
210
  if (blockResult.blocked) {
273
211
  this.logger.warn(`[Middleware] [X] BLOCKED TARBALL: ${packageName}@${version || '*'} - ${blockResult.reason}`);
274
212
  this.metrics.recordBlock(packageName, version || '*', blockResult.reason);
275
- // Return 403 Forbidden with detailed error
276
213
  res.status(403).json({
277
214
  error: 'Package blocked by security filter',
278
215
  package: packageName,
@@ -285,90 +222,41 @@ class SecurityFilterPlugin {
285
222
  this.logger.info(`[Middleware] [OK] ALLOWED TARBALL: ${packageName}@${version || 'unknown'}`);
286
223
  }
287
224
  else {
288
- // This is a metadata request - intercept response and modify it
289
225
  this.logger.info(`[Middleware] METADATA REQUEST: ${packageName} - will intercept response`);
290
- // Check if package should be blocked
291
226
  const blockResult = yield this._checkPackageBlock(packageName);
292
227
  if (blockResult.blocked) {
293
- // Intercept the response
294
- const originalSend = res.send;
295
- const originalJson = res.json;
296
- // eslint-disable-next-line @typescript-eslint/no-this-alias
297
- const self = this;
298
- // Override res.json to intercept JSON responses
299
- res.json = function (body) {
300
- if (body && typeof body === 'object' && body.name === packageName) {
301
- // This is package metadata - modify it to show blocked info
302
- self.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
303
- self.logger.info(`[Middleware] Modifying response to show blocking info`);
304
- const blockedResponse = {
305
- name: packageName,
306
- versions: {},
307
- 'dist-tags': {},
308
- security: {
309
- blocked: true,
310
- reason: blockResult.reason,
311
- plugin: {
312
- name: 'verdaccio-security-filter',
313
- version: '1.0.0',
314
- mode: self.config.mode || 'blacklist',
315
- },
316
- blockedAt: new Date().toISOString(),
317
- rules: self._getAppliedRules(packageName),
318
- message: `This package has been blocked by security filter: ${blockResult.reason}`,
319
- contact: 'Please contact your registry administrator for more information',
320
- },
321
- _security: {
322
- blocked: true,
323
- reason: blockResult.reason,
324
- blockedBy: 'verdaccio-security-filter',
325
- blockedAt: new Date().toISOString(),
326
- }
327
- };
328
- return originalJson.call(this, blockedResponse);
329
- }
330
- return originalJson.call(this, body);
331
- };
332
- // Override res.send for non-JSON responses
333
- res.send = function (body) {
334
- if (typeof body === 'string') {
335
- try {
336
- const parsed = JSON.parse(body);
337
- if (parsed && parsed.name === packageName) {
338
- self.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
339
- const blockedResponse = {
340
- name: packageName,
341
- versions: {},
342
- 'dist-tags': {},
343
- security: {
344
- blocked: true,
345
- reason: blockResult.reason,
346
- plugin: {
347
- name: 'verdaccio-security-filter',
348
- version: '1.0.0',
349
- mode: self.config.mode || 'blacklist',
350
- },
351
- blockedAt: new Date().toISOString(),
352
- rules: self._getAppliedRules(packageName),
353
- message: `This package has been blocked by security filter: ${blockResult.reason}`,
354
- contact: 'Please contact your registry administrator for more information',
355
- }
356
- };
357
- return originalSend.call(this, JSON.stringify(blockedResponse));
358
- }
359
- }
360
- catch (_a) {
361
- // Not JSON, pass through
362
- }
228
+ this.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
229
+ const blockedResponse = {
230
+ name: packageName,
231
+ versions: {},
232
+ 'dist-tags': {},
233
+ security: {
234
+ blocked: true,
235
+ reason: blockResult.reason,
236
+ plugin: {
237
+ name: 'verdaccio-security-filter',
238
+ version: '1.0.0',
239
+ mode: this.config.mode || 'blacklist',
240
+ },
241
+ blockedAt: new Date().toISOString(),
242
+ rules: this._getAppliedRules(packageName),
243
+ message: `This package has been blocked by security filter: ${blockResult.reason}`,
244
+ contact: 'Please contact your registry administrator for more information',
245
+ },
246
+ _security: {
247
+ blocked: true,
248
+ reason: blockResult.reason,
249
+ blockedBy: 'verdaccio-security-filter',
250
+ blockedAt: new Date().toISOString(),
363
251
  }
364
- return originalSend.call(this, body);
365
252
  };
253
+ res.status(200).json(blockedResponse);
254
+ return;
366
255
  }
367
256
  else {
368
257
  this.logger.info(`[Middleware] [OK] ALLOWED METADATA: ${packageName}`);
369
258
  }
370
259
  }
371
- // Continue to next middleware
372
260
  next();
373
261
  }
374
262
  catch (error) {
@@ -378,22 +266,16 @@ class SecurityFilterPlugin {
378
266
  }));
379
267
  this.logger.info('[Middleware] Security filter middleware registered successfully');
380
268
  }
381
- /**
382
- * Filter package metadata before returning to client
383
- * This provides CVE, License, and Age checking when metadata is available
384
- */
385
269
  filter_metadata(packageInfo) {
386
270
  return __awaiter(this, void 0, void 0, function* () {
387
271
  var _a, _b, _c, _d, _e, _f, _g, _h;
388
272
  const packageName = packageInfo.name;
389
273
  this.logger.info(`[filter_metadata] --> Processing: ${packageName}`);
390
274
  try {
391
- // 1. Whitelist/blacklist check (basic filtering)
392
275
  const blockResult = yield this._checkPackageBlock(packageName);
393
276
  if (blockResult.blocked) {
394
277
  this.logger.warn(`[filter_metadata] BLOCKED: ${packageName}@* - ${blockResult.reason}`);
395
278
  this.metrics.recordBlock(packageName, '*', blockResult.reason);
396
- // Return empty versions with security field
397
279
  return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
398
280
  blocked: true,
399
281
  reason: blockResult.reason,
@@ -406,18 +288,24 @@ class SecurityFilterPlugin {
406
288
  rules: this._getAppliedRules(packageName),
407
289
  } });
408
290
  }
409
- // 2. CVE Check - check all versions
410
291
  if ((_a = this.config.cveCheck) === null || _a === void 0 ? void 0 : _a.enabled) {
411
292
  const versions = Object.keys(packageInfo.versions || {});
412
293
  const vulnerableVersions = [];
413
- for (const version of versions) {
414
- const cveResult = yield this.cveChecker.checkPackage(packageName, version);
415
- if (cveResult.isVulnerable) {
416
- vulnerableVersions.push(version);
417
- this.logger.warn(`[filter_metadata] CVE found in ${packageName}@${version}: ${cveResult.vulnerabilities.length} vulnerabilities`);
418
- }
294
+ const BATCH_SIZE = 10;
295
+ for (let i = 0; i < versions.length; i += BATCH_SIZE) {
296
+ const batch = versions.slice(i, i + BATCH_SIZE);
297
+ const results = yield Promise.allSettled(batch.map(version => this.cveChecker.checkPackage(packageName, version)));
298
+ results.forEach((result, index) => {
299
+ if (result.status === 'fulfilled' && result.value.isVulnerable) {
300
+ const version = batch[index];
301
+ vulnerableVersions.push(version);
302
+ this.logger.warn(`[filter_metadata] CVE found in ${packageName}@${version}: ${result.value.vulnerabilities.length} vulnerabilities`);
303
+ }
304
+ else if (result.status === 'rejected') {
305
+ this.logger.error(`[filter_metadata] CVE check failed for ${packageName}@${batch[index]}: ${result.reason}`);
306
+ }
307
+ });
419
308
  }
420
- // If configured to auto-block and vulnerabilities found
421
309
  if (this.config.cveCheck.autoBlock && vulnerableVersions.length > 0) {
422
310
  const reason = `Package has ${vulnerableVersions.length} vulnerable version(s)`;
423
311
  this.logger.warn(`[filter_metadata] CVE BLOCKED: ${packageName} - ${reason}`);
@@ -434,7 +322,6 @@ class SecurityFilterPlugin {
434
322
  } });
435
323
  }
436
324
  }
437
- // 3. License Check - check latest version
438
325
  if (this.config.licenses && this.config.licenses.allowed && this.config.licenses.allowed.length > 0) {
439
326
  const latestVersion = (_b = packageInfo['dist-tags']) === null || _b === void 0 ? void 0 : _b.latest;
440
327
  if (latestVersion && ((_c = packageInfo.versions) === null || _c === void 0 ? void 0 : _c[latestVersion])) {
@@ -456,7 +343,6 @@ class SecurityFilterPlugin {
456
343
  }
457
344
  }
458
345
  }
459
- // 4. Package Age Check
460
346
  if ((_d = this.config.packageAge) === null || _d === void 0 ? void 0 : _d.enabled) {
461
347
  const ageResult = this.packageAgeChecker.checkPackageAge(packageInfo);
462
348
  if (!ageResult.allowed && !ageResult.warnOnly) {
@@ -477,7 +363,6 @@ class SecurityFilterPlugin {
477
363
  this.logger.warn(`[filter_metadata] AGE WARNING: ${packageName} - ${ageResult.reason} (warn-only mode)`);
478
364
  }
479
365
  }
480
- // 5. Author Filter Check
481
366
  if ((_e = this.config.authorFilter) === null || _e === void 0 ? void 0 : _e.enabled) {
482
367
  const latestVersion = (_f = packageInfo['dist-tags']) === null || _f === void 0 ? void 0 : _f.latest;
483
368
  if (latestVersion && ((_g = packageInfo.versions) === null || _g === void 0 ? void 0 : _g[latestVersion])) {
@@ -537,34 +422,25 @@ class SecurityFilterPlugin {
537
422
  }
538
423
  });
539
424
  }
540
- /**
541
- * Check if package should be blocked (used by middleware)
542
- * @private
543
- */
544
425
  _checkPackageBlock(packageName, version) {
545
426
  return __awaiter(this, void 0, void 0, function* () {
546
- // 1. Whitelist mode
547
427
  if (this.config.mode === 'whitelist') {
548
428
  const whitelistCheck = this.whitelistChecker.isWhitelisted(packageName, version);
549
429
  if (!whitelistCheck.allowed) {
550
430
  return { blocked: true, reason: whitelistCheck.reason || 'Package is not in whitelist' };
551
431
  }
552
432
  }
553
- // 2. Blocked patterns
554
433
  if (this._isBlockedByPattern(packageName)) {
555
434
  return { blocked: true, reason: 'Package name matches blocked pattern' };
556
435
  }
557
- // 3. Scopes
558
436
  if (!this._isScopeAllowed(packageName)) {
559
437
  return { blocked: true, reason: 'Package scope not allowed' };
560
438
  }
561
- // 4. Exact version blocking
562
439
  if (version) {
563
440
  const versionKey = `${packageName}@${version}`;
564
441
  if (this.securityRules.blockedVersions.includes(versionKey)) {
565
442
  return { blocked: true, reason: 'Exact version match in blocklist' };
566
443
  }
567
- // 5. Version range rules
568
444
  const rangeRule = this._getVersionRangeRule(packageName, version);
569
445
  if (rangeRule && rangeRule.strategy === 'block') {
570
446
  return { blocked: true, reason: `Version matches blocked range: ${rangeRule.range}` };
@@ -573,13 +449,8 @@ class SecurityFilterPlugin {
573
449
  return { blocked: false, reason: '' };
574
450
  });
575
451
  }
576
- /**
577
- * Get applied security rules for a package
578
- * @private
579
- */
580
452
  _getAppliedRules(packageName) {
581
453
  const rules = {};
582
- // Check which rules apply to this package
583
454
  if (this.config.mode === 'whitelist') {
584
455
  rules.mode = 'whitelist';
585
456
  rules.whitelisted = false;
@@ -610,9 +481,19 @@ class SecurityFilterPlugin {
610
481
  }
611
482
  return rules;
612
483
  }
484
+ destroy() {
485
+ this.logger.info('[Cleanup] Shutting down security filter plugin');
486
+ try {
487
+ this.cveChecker.destroy();
488
+ this.metrics.cleanup();
489
+ this.logger.info('[Cleanup] Plugin resources cleaned up successfully');
490
+ }
491
+ catch (error) {
492
+ this.logger.error(`[Cleanup] Error during cleanup: ${error.message}`);
493
+ }
494
+ }
613
495
  }
614
496
  exports.default = SecurityFilterPlugin;
615
- // Export utility classes for testing
616
497
  var logger_2 = require("./lib/logger");
617
498
  Object.defineProperty(exports, "SecurityLogger", { enumerable: true, get: function () { return logger_2.SecurityLogger; } });
618
499
  var metrics_2 = require("./lib/metrics");
@@ -1,9 +1,5 @@
1
1
  import { Version } from '@verdaccio/types';
2
2
  import { AuthorFilterConfig, AuthorCheckResult } from '../types';
3
- /**
4
- * AuthorChecker validates package authors and maintainers
5
- * against configured security policies.
6
- */
7
3
  export declare class AuthorChecker {
8
4
  private readonly config?;
9
5
  private readonly blockedAuthors;
@@ -13,25 +9,10 @@ export declare class AuthorChecker {
13
9
  private readonly blockedEmailDomains;
14
10
  private readonly regionDomainMap;
15
11
  constructor(config?: AuthorFilterConfig);
16
- /**
17
- * Check if package author/maintainer is allowed
18
- */
19
12
  checkAuthor(versionData: Version): AuthorCheckResult;
20
- /**
21
- * Extract author information from package version data
22
- */
23
13
  private _extractAuthors;
24
- /**
25
- * Normalize author data to AuthorInfo format
26
- */
27
14
  private _normalizeAuthor;
28
- /**
29
- * Check a single author against all blocking rules
30
- */
31
15
  private _checkSingleAuthor;
32
- /**
33
- * Get summary of configuration
34
- */
35
16
  getSummary(): {
36
17
  enabled: boolean;
37
18
  blockedAuthors: number;
@@ -1,23 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthorChecker = void 0;
4
- /**
5
- * Region to TLD mapping for email domain filtering
6
- */
7
4
  const REGION_DOMAINS = {
8
5
  'ru': ['.ru', 'yandex.ru', 'mail.ru', 'rambler.ru', 'ya.ru', 'bk.ru', 'list.ru'],
9
6
  'cn': ['.cn', 'qq.com', '163.com', '126.com', 'sina.com', 'sohu.com'],
10
7
  'by': ['.by', 'tut.by', 'mail.by'],
11
- 'kp': ['.kp'], // North Korea
12
- 'ir': ['.ir'], // Iran
13
- 'sy': ['.sy'], // Syria
14
- 'cu': ['.cu'], // Cuba
15
- 'sd': ['.sd'], // Sudan
8
+ 'kp': ['.kp'],
9
+ 'ir': ['.ir'],
10
+ 'sy': ['.sy'],
11
+ 'cu': ['.cu'],
12
+ 'sd': ['.sd'],
16
13
  };
17
- /**
18
- * AuthorChecker validates package authors and maintainers
19
- * against configured security policies.
20
- */
21
14
  class AuthorChecker {
22
15
  constructor(config) {
23
16
  this.config = config;
@@ -34,9 +27,6 @@ class AuthorChecker {
34
27
  this.regionDomainMap.set(region.toLowerCase(), new Set(domains));
35
28
  });
36
29
  }
37
- /**
38
- * Check if package author/maintainer is allowed
39
- */
40
30
  checkAuthor(versionData) {
41
31
  var _a;
42
32
  if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.enabled)) {
@@ -61,9 +51,6 @@ class AuthorChecker {
61
51
  }
62
52
  return { allowed: true };
63
53
  }
64
- /**
65
- * Extract author information from package version data
66
- */
67
54
  _extractAuthors(versionData) {
68
55
  const authors = [];
69
56
  if (versionData.author) {
@@ -81,9 +68,6 @@ class AuthorChecker {
81
68
  }
82
69
  return authors;
83
70
  }
84
- /**
85
- * Normalize author data to AuthorInfo format
86
- */
87
71
  _normalizeAuthor(author) {
88
72
  if (typeof author === 'string') {
89
73
  const emailMatch = author.match(/<([^>]+)>/);
@@ -99,11 +83,7 @@ class AuthorChecker {
99
83
  url: author.url,
100
84
  };
101
85
  }
102
- /**
103
- * Check a single author against all blocking rules
104
- */
105
86
  _checkSingleAuthor(author) {
106
- // Check blocked author names
107
87
  if (author.name) {
108
88
  const normalizedName = author.name.toLowerCase().trim();
109
89
  if (this.blockedAuthors.has(normalizedName)) {
@@ -125,7 +105,6 @@ class AuthorChecker {
125
105
  }
126
106
  }
127
107
  }
128
- // Check blocked emails
129
108
  if (author.email) {
130
109
  const normalizedEmail = author.email.toLowerCase().trim();
131
110
  if (this.blockedEmails.has(normalizedEmail)) {
@@ -146,7 +125,6 @@ class AuthorChecker {
146
125
  };
147
126
  }
148
127
  }
149
- // Check blocked email domains
150
128
  for (const domain of this.blockedEmailDomains) {
151
129
  if (normalizedEmail.endsWith(domain) || normalizedEmail.includes(`@${domain}`)) {
152
130
  return {
@@ -157,7 +135,6 @@ class AuthorChecker {
157
135
  };
158
136
  }
159
137
  }
160
- // Check blocked regions
161
138
  for (const [region, domains] of this.regionDomainMap.entries()) {
162
139
  for (const domain of domains) {
163
140
  if (normalizedEmail.endsWith(domain) ||
@@ -175,9 +152,6 @@ class AuthorChecker {
175
152
  }
176
153
  return { allowed: true };
177
154
  }
178
- /**
179
- * Get summary of configuration
180
- */
181
155
  getSummary() {
182
156
  var _a;
183
157
  return {