verdaccio-security-filter 1.3.9 → 1.3.10

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/build/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
6
6
  logger: SecurityLogger;
7
7
  config: SecurityConfig;
8
8
  private readonly securityRules;
9
+ private readonly blockedPatternRegexes;
9
10
  private readonly metrics;
10
11
  private readonly whitelistChecker;
11
12
  private readonly cveChecker;
@@ -20,7 +21,10 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
20
21
  private _isScopeAllowed;
21
22
  private _getVersionRangeRule;
22
23
  register_middlewares(app: Application, _auth: IBasicAuth<SecurityConfig>, _storage: IStorageManager<SecurityConfig>): void;
24
+ private _blockedPackage;
25
+ private _removeVersions;
23
26
  filter_metadata(packageInfo: Package): Promise<Package>;
27
+ private _versionBlockReason;
24
28
  private _checkPackageBlock;
25
29
  private _getAppliedRules;
26
30
  destroy(): void;
package/build/index.js CHANGED
@@ -47,6 +47,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
48
  exports.AuthorChecker = exports.PackageAgeChecker = exports.LicenseChecker = exports.CVEChecker = exports.WhitelistChecker = exports.MetricsCollector = exports.SecurityLogger = void 0;
49
49
  const semver = __importStar(require("semver"));
50
+ const fs_1 = require("fs");
51
+ const path_1 = require("path");
50
52
  const logger_1 = require("./lib/logger");
51
53
  const metrics_1 = require("./lib/metrics");
52
54
  const whitelist_checker_1 = require("./lib/whitelist-checker");
@@ -54,6 +56,7 @@ const cve_checker_1 = require("./lib/cve-checker");
54
56
  const license_checker_1 = require("./lib/license-checker");
55
57
  const package_age_checker_1 = require("./lib/package-age-checker");
56
58
  const author_checker_1 = require("./lib/author-checker");
59
+ const PLUGIN_VERSION = JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(__dirname, '..', 'package.json'), 'utf8')).version;
57
60
  class SecurityFilterPlugin {
58
61
  constructor(config, options) {
59
62
  this.config = config || {};
@@ -81,6 +84,15 @@ class SecurityFilterPlugin {
81
84
  enforceChecksum: this.config.enforceChecksum !== false,
82
85
  versionRangeRules: this._parseVersionRangeRules(this.config.versionRangeRules || [])
83
86
  };
87
+ this.blockedPatternRegexes = this.securityRules.blockedPatterns.flatMap(pattern => {
88
+ try {
89
+ return [new RegExp(pattern)];
90
+ }
91
+ catch (error) {
92
+ this.logger.warn(`Invalid blocked pattern "${pattern}", skipping: ${error.message}`);
93
+ return [];
94
+ }
95
+ });
84
96
  this._logInitialization();
85
97
  }
86
98
  _parseVersionRangeRules(rules) {
@@ -89,9 +101,8 @@ class SecurityFilterPlugin {
89
101
  this.logger.warn(`Invalid version range rule at index ${index}, skipping`);
90
102
  return null;
91
103
  }
92
- if (rule.strategy === 'fallback' && !rule.fallbackVersion) {
93
- this.logger.warn(`Fallback strategy requires fallbackVersion for ${rule.package}, skipping`);
94
- return null;
104
+ if (rule.strategy === 'fallback') {
105
+ this.logger.warn(`Strategy "fallback" for ${rule.package} is deprecated and works like "block": matching versions are hidden, so npm resolves to the highest allowed one`);
95
106
  }
96
107
  try {
97
108
  const validRange = semver.validRange(rule.range);
@@ -156,10 +167,7 @@ class SecurityFilterPlugin {
156
167
  return features;
157
168
  }
158
169
  _isBlockedByPattern(packageName) {
159
- return this.securityRules.blockedPatterns.some(pattern => {
160
- const regex = new RegExp(pattern);
161
- return regex.test(packageName);
162
- });
170
+ return this.blockedPatternRegexes.some(regex => regex.test(packageName));
163
171
  }
164
172
  _isScopeAllowed(packageName) {
165
173
  if (!packageName.startsWith('@')) {
@@ -212,9 +220,11 @@ class SecurityFilterPlugin {
212
220
  const tarballFile = urlMatch[3];
213
221
  let version;
214
222
  if (tarballFile) {
215
- const versionMatch = tarballFile.match(/-([\d.]+(?:-[a-zA-Z0-9.]+)?)\.tgz$/);
216
- version = versionMatch ? versionMatch[1] : undefined;
217
- this.logger.info(`[Middleware] TARBALL REQUEST: ${packageName}@${version || 'unknown'}`);
223
+ const filePrefix = `${urlMatch[2]}-`;
224
+ version = tarballFile.startsWith(filePrefix)
225
+ ? tarballFile.slice(filePrefix.length, -'.tgz'.length)
226
+ : undefined;
227
+ this.logger.debug(`[Middleware] TARBALL REQUEST: ${packageName}@${version || 'unknown'}`);
218
228
  const blockResult = yield this._checkPackageBlock(packageName, version);
219
229
  if (blockResult.blocked) {
220
230
  this.logger.warn(`[Middleware] [X] BLOCKED TARBALL: ${packageName}@${version || '*'} - ${blockResult.reason}`);
@@ -228,10 +238,10 @@ class SecurityFilterPlugin {
228
238
  });
229
239
  return;
230
240
  }
231
- this.logger.info(`[Middleware] [OK] ALLOWED TARBALL: ${packageName}@${version || 'unknown'}`);
241
+ this.logger.debug(`[Middleware] [OK] ALLOWED TARBALL: ${packageName}@${version || 'unknown'}`);
232
242
  }
233
243
  else {
234
- this.logger.info(`[Middleware] METADATA REQUEST: ${packageName} - will intercept response`);
244
+ this.logger.debug(`[Middleware] METADATA REQUEST: ${packageName} - will intercept response`);
235
245
  const blockResult = yield this._checkPackageBlock(packageName);
236
246
  if (blockResult.blocked) {
237
247
  this.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
@@ -244,7 +254,7 @@ class SecurityFilterPlugin {
244
254
  reason: blockResult.reason,
245
255
  plugin: {
246
256
  name: 'verdaccio-security-filter',
247
- version: '1.0.0',
257
+ version: PLUGIN_VERSION,
248
258
  mode: this.config.mode || 'blacklist',
249
259
  },
250
260
  blockedAt: new Date().toISOString(),
@@ -263,7 +273,7 @@ class SecurityFilterPlugin {
263
273
  return;
264
274
  }
265
275
  else {
266
- this.logger.info(`[Middleware] [OK] ALLOWED METADATA: ${packageName}`);
276
+ this.logger.debug(`[Middleware] [OK] ALLOWED METADATA: ${packageName}`);
267
277
  }
268
278
  }
269
279
  next();
@@ -275,28 +285,51 @@ class SecurityFilterPlugin {
275
285
  }));
276
286
  this.logger.info('[Middleware] Security filter middleware registered successfully');
277
287
  }
288
+ _blockedPackage(pkg, reason, extra = {}) {
289
+ return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: Object.assign(Object.assign({ blocked: true, reason }, extra), { plugin: {
290
+ name: 'verdaccio-security-filter',
291
+ version: PLUGIN_VERSION,
292
+ mode: this.config.mode || 'blacklist',
293
+ }, blockedAt: new Date().toISOString() }) });
294
+ }
295
+ _removeVersions(pkg, removed) {
296
+ if (removed.length === 0) {
297
+ return pkg;
298
+ }
299
+ const gone = new Set(removed);
300
+ const versions = Object.assign({}, pkg.versions);
301
+ for (const version of gone) {
302
+ delete versions[version];
303
+ }
304
+ const distTags = Object.assign({}, pkg['dist-tags']);
305
+ for (const [tag, tagVersion] of Object.entries(distTags)) {
306
+ if (gone.has(tagVersion)) {
307
+ delete distTags[tag];
308
+ this.logger.debug(`[filter_metadata] Removed dist-tag "${tag}" pointing to filtered version ${tagVersion}`);
309
+ }
310
+ }
311
+ return Object.assign(Object.assign({}, pkg), { versions, 'dist-tags': distTags });
312
+ }
278
313
  filter_metadata(packageInfo) {
279
314
  return __awaiter(this, void 0, void 0, function* () {
280
315
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
281
316
  let pkg = packageInfo;
282
317
  const packageName = pkg.name;
283
- this.logger.info(`[filter_metadata] --> Processing: ${packageName}`);
318
+ this.logger.debug(`[filter_metadata] --> Processing: ${packageName}`);
284
319
  try {
285
320
  const blockResult = yield this._checkPackageBlock(packageName);
286
321
  if (blockResult.blocked) {
287
322
  this.logger.warn(`[filter_metadata] BLOCKED: ${packageName}@* - ${blockResult.reason}`);
288
323
  this.metrics.recordBlock(packageName, '*', blockResult.reason);
289
- return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: {
290
- blocked: true,
291
- reason: blockResult.reason,
292
- plugin: {
293
- name: 'verdaccio-security-filter',
294
- version: '2.0.0',
295
- mode: this.config.mode || 'blacklist',
296
- },
297
- blockedAt: new Date().toISOString(),
298
- rules: this._getAppliedRules(packageName),
299
- } });
324
+ return this._blockedPackage(pkg, blockResult.reason, { rules: this._getAppliedRules(packageName) });
325
+ }
326
+ if (this.securityRules.blockedVersions.length > 0 || this.securityRules.versionRangeRules.length > 0) {
327
+ const ruleBlocked = Object.keys(pkg.versions || {})
328
+ .filter(version => this._versionBlockReason(packageName, version) !== null);
329
+ if (ruleBlocked.length > 0) {
330
+ this.logger.warn(`[filter_metadata] RULE FILTERED: ${packageName}: ${ruleBlocked.join(', ')}`);
331
+ pkg = this._removeVersions(pkg, ruleBlocked);
332
+ }
300
333
  }
301
334
  if ((_a = this.config.cveCheck) === null || _a === void 0 ? void 0 : _a.enabled) {
302
335
  const versions = Object.keys(pkg.versions || {});
@@ -310,15 +343,7 @@ class SecurityFilterPlugin {
310
343
  if (((_b = this.config.errorHandling) === null || _b === void 0 ? void 0 : _b.onCveCheckError) === 'fail-closed') {
311
344
  const reason = `CVE check failed (fail-closed mode): ${error.message}`;
312
345
  this.metrics.recordBlock(packageName, '*', reason);
313
- return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: {
314
- blocked: true,
315
- reason,
316
- plugin: {
317
- name: 'verdaccio-security-filter',
318
- version: '2.0.0',
319
- },
320
- blockedAt: new Date().toISOString(),
321
- } });
346
+ return this._blockedPackage(pkg, reason);
322
347
  }
323
348
  }
324
349
  if (vulnerableVersions.length > 0 && !this.config.cveCheck.autoBlock) {
@@ -329,31 +354,13 @@ class SecurityFilterPlugin {
329
354
  if (vulnerableVersions.length === versions.length) {
330
355
  this.logger.warn(`[filter_metadata] CVE BLOCKED: ${packageName} - ${reason}`);
331
356
  this.metrics.recordBlock(packageName, '*', reason);
332
- return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: {
333
- blocked: true,
334
- reason,
335
- vulnerableVersions,
336
- plugin: {
337
- name: 'verdaccio-security-filter',
338
- version: '2.0.0',
339
- },
340
- blockedAt: new Date().toISOString(),
341
- } });
357
+ return this._blockedPackage(pkg, reason, { vulnerableVersions });
342
358
  }
343
359
  this.logger.warn(`[filter_metadata] CVE FILTERED: ${packageName} - ${reason}: ${vulnerableVersions.join(', ')}`);
344
- const filteredVersions = Object.assign({}, pkg.versions);
345
360
  for (const version of vulnerableVersions) {
346
- delete filteredVersions[version];
347
361
  this.metrics.recordBlock(packageName, version, reason);
348
362
  }
349
- const filteredDistTags = Object.assign({}, pkg['dist-tags']);
350
- for (const [tag, tagVersion] of Object.entries(filteredDistTags)) {
351
- if (vulnerableVersions.includes(tagVersion)) {
352
- delete filteredDistTags[tag];
353
- this.logger.debug(`[filter_metadata] Removed dist-tag "${tag}" pointing to vulnerable version ${tagVersion}`);
354
- }
355
- }
356
- pkg = Object.assign(Object.assign({}, pkg), { versions: filteredVersions, 'dist-tags': filteredDistTags });
363
+ pkg = this._removeVersions(pkg, vulnerableVersions);
357
364
  }
358
365
  }
359
366
  if (this.config.licenses && this.config.licenses.allowed && this.config.licenses.allowed.length > 0) {
@@ -362,66 +369,38 @@ class SecurityFilterPlugin {
362
369
  const versionData = pkg.versions[latestVersion];
363
370
  const licenseResult = this.licenseChecker.checkLicense(versionData);
364
371
  if (!licenseResult.allowed) {
365
- this.logger.warn(`[filter_metadata] LICENSE BLOCKED: ${packageName} - ${licenseResult.reason}`);
366
- this.metrics.recordBlock(packageName, '*', licenseResult.reason || 'License not allowed');
367
- return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: {
368
- blocked: true,
369
- reason: licenseResult.reason || 'License not allowed',
370
- license: licenseResult.license,
371
- plugin: {
372
- name: 'verdaccio-security-filter',
373
- version: '2.0.0',
374
- },
375
- blockedAt: new Date().toISOString(),
376
- } });
372
+ const reason = licenseResult.reason || 'License not allowed';
373
+ this.logger.warn(`[filter_metadata] LICENSE BLOCKED: ${packageName} - ${reason}`);
374
+ this.metrics.recordBlock(packageName, '*', reason);
375
+ return this._blockedPackage(pkg, reason, { license: licenseResult.license });
377
376
  }
378
377
  }
379
378
  }
380
379
  if ((_e = this.config.packageAge) === null || _e === void 0 ? void 0 : _e.enabled) {
381
380
  const ageResult = this.packageAgeChecker.checkPackageAge(pkg);
382
381
  if (!ageResult.allowed && !ageResult.warnOnly) {
383
- this.logger.warn(`[filter_metadata] AGE BLOCKED: ${packageName} - ${ageResult.reason}`);
384
- this.metrics.recordBlock(packageName, '*', ageResult.reason || 'Package too new');
385
- return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: {
386
- blocked: true,
387
- reason: ageResult.reason || 'Package too new',
388
- ageDays: ageResult.ageDays,
389
- plugin: {
390
- name: 'verdaccio-security-filter',
391
- version: '2.0.0',
392
- },
393
- blockedAt: new Date().toISOString(),
394
- } });
382
+ const reason = ageResult.reason || 'Package too new';
383
+ this.logger.warn(`[filter_metadata] AGE BLOCKED: ${packageName} - ${reason}`);
384
+ this.metrics.recordBlock(packageName, '*', reason);
385
+ return this._blockedPackage(pkg, reason, { ageDays: ageResult.ageDays });
395
386
  }
396
387
  else if (!ageResult.allowed && ageResult.warnOnly) {
397
388
  this.logger.warn(`[filter_metadata] AGE WARNING: ${packageName} - ${ageResult.reason} (warn-only mode)`);
398
389
  }
399
390
  if (this.config.packageAge.minVersionAgeDays) {
400
- const filteredVersions = Object.assign({}, pkg.versions);
401
391
  const removedVersions = [];
402
- for (const version of Object.keys(filteredVersions)) {
392
+ for (const version of Object.keys(pkg.versions || {})) {
403
393
  const versionAgeResult = this.packageAgeChecker.checkVersionAge(pkg, version);
404
394
  if (!versionAgeResult.allowed && !versionAgeResult.warnOnly) {
405
395
  this.logger.warn(`[filter_metadata] VERSION AGE FILTERED: ${packageName}@${version} - ${versionAgeResult.reason}`);
406
396
  this.metrics.recordBlock(packageName, version, versionAgeResult.reason || 'Version too new');
407
- delete filteredVersions[version];
408
397
  removedVersions.push(version);
409
398
  }
410
399
  else if (!versionAgeResult.allowed && versionAgeResult.warnOnly) {
411
400
  this.logger.warn(`[filter_metadata] VERSION AGE WARNING: ${packageName}@${version} - ${versionAgeResult.reason} (warn-only mode)`);
412
401
  }
413
402
  }
414
- if (removedVersions.length > 0) {
415
- this.logger.info(`[filter_metadata] Removed ${removedVersions.length} too-new version(s) from ${packageName}: ${removedVersions.join(', ')}`);
416
- const filteredDistTags = Object.assign({}, pkg['dist-tags']);
417
- for (const [tag, tagVersion] of Object.entries(filteredDistTags)) {
418
- if (removedVersions.includes(tagVersion)) {
419
- delete filteredDistTags[tag];
420
- this.logger.debug(`[filter_metadata] Removed dist-tag "${tag}" pointing to filtered version ${tagVersion}`);
421
- }
422
- }
423
- pkg = Object.assign(Object.assign({}, pkg), { versions: filteredVersions, 'dist-tags': filteredDistTags });
424
- }
403
+ pkg = this._removeVersions(pkg, removedVersions);
425
404
  }
426
405
  }
427
406
  if ((_f = this.config.authorFilter) === null || _f === void 0 ? void 0 : _f.enabled) {
@@ -430,36 +409,30 @@ class SecurityFilterPlugin {
430
409
  const versionData = pkg.versions[latestVersion];
431
410
  const authorResult = this.authorChecker.checkAuthor(versionData);
432
411
  if (!authorResult.allowed && !this.config.authorFilter.warnOnly) {
433
- this.logger.warn(`[filter_metadata] AUTHOR BLOCKED: ${packageName} - ${authorResult.reason}`);
412
+ const reason = authorResult.reason || 'Author not allowed';
413
+ this.logger.warn(`[filter_metadata] AUTHOR BLOCKED: ${packageName} - ${reason}`);
434
414
  this.metrics.record({
435
415
  timestamp: new Date().toISOString(),
436
416
  event: 'author_blocked',
437
417
  packageName,
438
418
  version: latestVersion,
439
- reason: authorResult.reason || 'Author not allowed',
419
+ reason,
440
420
  metadata: {
441
421
  blockedBy: authorResult.blockedBy,
442
422
  authorInfo: authorResult.authorInfo,
443
423
  },
444
424
  });
445
- return Object.assign(Object.assign({}, pkg), { versions: {}, 'dist-tags': {}, security: {
446
- blocked: true,
447
- reason: authorResult.reason || 'Author not allowed',
448
- blockedBy: authorResult.blockedBy,
449
- authorInfo: authorResult.authorInfo,
450
- plugin: {
451
- name: 'verdaccio-security-filter',
452
- version: '2.0.0',
453
- },
454
- blockedAt: new Date().toISOString(),
455
- } });
425
+ return this._blockedPackage(pkg, reason, {
426
+ blockedBy: authorResult.blockedBy,
427
+ authorInfo: authorResult.authorInfo,
428
+ });
456
429
  }
457
430
  else if (!authorResult.allowed && this.config.authorFilter.warnOnly) {
458
431
  this.logger.warn(`[filter_metadata] AUTHOR WARNING: ${packageName} - ${authorResult.reason} (warn-only mode)`);
459
432
  }
460
433
  }
461
434
  }
462
- this.logger.info(`[filter_metadata] [OK] ALLOWED: ${packageName}`);
435
+ this.logger.debug(`[filter_metadata] [OK] ALLOWED: ${packageName}`);
463
436
  return pkg;
464
437
  }
465
438
  catch (error) {
@@ -468,21 +441,20 @@ class SecurityFilterPlugin {
468
441
  if (errorStrategy === 'fail-closed') {
469
442
  this.logger.warn(`[filter_metadata] Error handling: fail-closed - BLOCKING ${packageName}`);
470
443
  this.metrics.recordBlock(packageName, '*', `Error during processing: ${error.message}`);
471
- return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
472
- blocked: true,
473
- reason: `Error during security processing (fail-closed mode): ${error.message}`,
474
- plugin: {
475
- name: 'verdaccio-security-filter',
476
- version: '2.0.0',
477
- },
478
- blockedAt: new Date().toISOString(),
479
- } });
444
+ return this._blockedPackage(packageInfo, `Error during security processing (fail-closed mode): ${error.message}`);
480
445
  }
481
446
  this.logger.warn(`[filter_metadata] Error handling: fail-open - ALLOWING ${packageName}`);
482
447
  return packageInfo;
483
448
  }
484
449
  });
485
450
  }
451
+ _versionBlockReason(packageName, version) {
452
+ if (this.securityRules.blockedVersions.includes(`${packageName}@${version}`)) {
453
+ return 'Exact version match in blocklist';
454
+ }
455
+ const rangeRule = this._getVersionRangeRule(packageName, version);
456
+ return rangeRule ? `Version matches blocked range: ${rangeRule.range}` : null;
457
+ }
486
458
  _checkPackageBlock(packageName, version) {
487
459
  return __awaiter(this, void 0, void 0, function* () {
488
460
  if (this.config.mode === 'whitelist') {
@@ -497,15 +469,9 @@ class SecurityFilterPlugin {
497
469
  if (!this._isScopeAllowed(packageName)) {
498
470
  return { blocked: true, reason: 'Package scope not allowed' };
499
471
  }
500
- if (version) {
501
- const versionKey = `${packageName}@${version}`;
502
- if (this.securityRules.blockedVersions.includes(versionKey)) {
503
- return { blocked: true, reason: 'Exact version match in blocklist' };
504
- }
505
- const rangeRule = this._getVersionRangeRule(packageName, version);
506
- if (rangeRule && rangeRule.strategy === 'block') {
507
- return { blocked: true, reason: `Version matches blocked range: ${rangeRule.range}` };
508
- }
472
+ const versionReason = version ? this._versionBlockReason(packageName, version) : null;
473
+ if (versionReason) {
474
+ return { blocked: true, reason: versionReason };
509
475
  }
510
476
  return { blocked: false, reason: '' };
511
477
  });
@@ -516,11 +482,9 @@ class SecurityFilterPlugin {
516
482
  rules.mode = 'whitelist';
517
483
  rules.whitelisted = false;
518
484
  }
519
- if (this._isBlockedByPattern(packageName)) {
520
- rules.blockedPattern = this.securityRules.blockedPatterns.find(pattern => {
521
- const regex = new RegExp(pattern);
522
- return regex.test(packageName);
523
- });
485
+ const blockedPattern = this.blockedPatternRegexes.find(regex => regex.test(packageName));
486
+ if (blockedPattern) {
487
+ rules.blockedPattern = blockedPattern.source;
524
488
  }
525
489
  if (packageName.startsWith('@')) {
526
490
  const scope = packageName.split('/')[0];
@@ -4,6 +4,8 @@ export declare class MetricsCollector {
4
4
  private metricsBuffer;
5
5
  private readonly bufferSize;
6
6
  private readonly maxBufferSize;
7
+ private readonly flushIntervalMs;
8
+ private flushTimer;
7
9
  constructor(config?: MetricsConfig);
8
10
  record(event: MetricsData): void;
9
11
  recordBlock(packageName: string, version: string, reason: string, metadata?: Record<string, any>): void;
@@ -41,11 +41,17 @@ class MetricsCollector {
41
41
  this.metricsBuffer = [];
42
42
  this.bufferSize = 100;
43
43
  this.maxBufferSize = 500;
44
+ this.flushIntervalMs = 10000;
45
+ this.flushTimer = null;
44
46
  this.config = {
45
47
  enabled: (config === null || config === void 0 ? void 0 : config.enabled) || false,
46
48
  output: (config === null || config === void 0 ? void 0 : config.output) || 'stdout',
47
49
  filePath: (config === null || config === void 0 ? void 0 : config.filePath) || './security-metrics.json',
48
50
  };
51
+ if (this.config.enabled) {
52
+ this.flushTimer = setInterval(() => this.flush(), this.flushIntervalMs);
53
+ this.flushTimer.unref();
54
+ }
49
55
  }
50
56
  record(event) {
51
57
  if (!this.config.enabled) {
@@ -163,6 +169,10 @@ class MetricsCollector {
163
169
  return summary;
164
170
  }
165
171
  cleanup() {
172
+ if (this.flushTimer) {
173
+ clearInterval(this.flushTimer);
174
+ this.flushTimer = null;
175
+ }
166
176
  this.flush();
167
177
  }
168
178
  }
@@ -1,12 +1,14 @@
1
1
  import { WhitelistConfig } from '../types';
2
2
  export declare class WhitelistChecker {
3
3
  private config;
4
+ private regexes;
4
5
  constructor(config?: WhitelistConfig);
5
6
  isWhitelisted(packageName: string, version?: string): {
6
7
  allowed: boolean;
7
8
  reason?: string;
8
9
  };
9
10
  private matchesPattern;
11
+ private compile;
10
12
  addPackage(packageName: string, versionRange?: string): void;
11
13
  removePackage(packageName: string): void;
12
14
  addPattern(pattern: string): void;
@@ -46,6 +46,7 @@ exports.WhitelistChecker = void 0;
46
46
  const semver = __importStar(require("semver"));
47
47
  class WhitelistChecker {
48
48
  constructor(config) {
49
+ this.regexes = new Map();
49
50
  this.config = {
50
51
  packages: (config === null || config === void 0 ? void 0 : config.packages) || [],
51
52
  patterns: (config === null || config === void 0 ? void 0 : config.patterns) || [],
@@ -75,16 +76,21 @@ class WhitelistChecker {
75
76
  };
76
77
  }
77
78
  matchesPattern(packageName) {
78
- return this.config.patterns.some(pattern => {
79
+ return this.config.patterns.some(pattern => { var _a; return (_a = this.compile(pattern)) === null || _a === void 0 ? void 0 : _a.test(packageName); });
80
+ }
81
+ compile(pattern) {
82
+ let regex = this.regexes.get(pattern);
83
+ if (regex === undefined) {
79
84
  try {
80
- const regex = new RegExp(pattern);
81
- return regex.test(packageName);
85
+ regex = new RegExp(pattern);
82
86
  }
83
87
  catch (_a) {
84
88
  console.error(`[Whitelist] Invalid pattern: ${pattern}`);
85
- return false;
89
+ regex = null;
86
90
  }
87
- });
91
+ this.regexes.set(pattern, regex);
92
+ }
93
+ return regex;
88
94
  }
89
95
  addPackage(packageName, versionRange) {
90
96
  if (!this.config.packages.includes(packageName)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verdaccio-security-filter",
3
- "version": "1.3.9",
3
+ "version": "1.3.10",
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",