verdaccio-security-filter 0.0.0 → 1.1.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.
@@ -0,0 +1,86 @@
1
+ import { IPluginMiddleware, IPluginStorageFilter, PluginOptions, IBasicAuth, IStorageManager, Package } from '@verdaccio/types';
2
+ import { Application } from 'express';
3
+ import { SecurityConfig } from './types';
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
+ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityConfig>, IPluginStorageFilter<SecurityConfig> {
15
+ logger: SecurityLogger;
16
+ config: SecurityConfig;
17
+ private readonly securityRules;
18
+ private readonly metrics;
19
+ private readonly whitelistChecker;
20
+ private readonly cveChecker;
21
+ private readonly licenseChecker;
22
+ private readonly packageAgeChecker;
23
+ /**
24
+ * Creates a new SecurityFilterPlugin instance
25
+ * @param {SecurityConfig} config - Plugin configuration
26
+ * @param {PluginOptions} options - Verdaccio plugin options
27
+ */
28
+ constructor(config: SecurityConfig, options: PluginOptions<SecurityConfig>);
29
+ /**
30
+ * Parse and validate version range rules
31
+ * @private
32
+ */
33
+ private _parseVersionRangeRules;
34
+ /**
35
+ * Log initialization details
36
+ * @private
37
+ */
38
+ private _logInitialization;
39
+ /**
40
+ * Get list of enabled features
41
+ * @private
42
+ */
43
+ private _getEnabledFeatures;
44
+ /**
45
+ * Check if package name matches blocked patterns
46
+ * @private
47
+ */
48
+ private _isBlockedByPattern;
49
+ /**
50
+ * Check if package scope is allowed
51
+ * @private
52
+ */
53
+ private _isScopeAllowed;
54
+ /**
55
+ * Get version range rule for a specific package and version
56
+ * @private
57
+ */
58
+ private _getVersionRangeRule;
59
+ /**
60
+ * Register Express middleware to intercept package requests
61
+ * Intercepts both metadata and tarball requests
62
+ */
63
+ register_middlewares(app: Application, _auth: IBasicAuth<SecurityConfig>, _storage: IStorageManager<SecurityConfig>): void;
64
+ /**
65
+ * Filter package metadata before returning to client
66
+ * This provides CVE, License, and Age checking when metadata is available
67
+ */
68
+ filter_metadata(packageInfo: Package): Promise<Package>;
69
+ /**
70
+ * Check if package should be blocked (used by middleware)
71
+ * @private
72
+ */
73
+ private _checkPackageBlock;
74
+ /**
75
+ * Get applied security rules for a package
76
+ * @private
77
+ */
78
+ private _getAppliedRules;
79
+ }
80
+ export { SecurityLogger } from './lib/logger';
81
+ export { MetricsCollector } from './lib/metrics';
82
+ export { WhitelistChecker } from './lib/whitelist-checker';
83
+ export { CVEChecker } from './lib/cve-checker';
84
+ export { LicenseChecker } from './lib/license-checker';
85
+ export { PackageAgeChecker } from './lib/package-age-checker';
86
+ export * from './types';
package/build/index.js ADDED
@@ -0,0 +1,583 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
38
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
39
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
40
+ return new (P || (P = Promise))(function (resolve, reject) {
41
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
42
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
43
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
44
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
45
+ });
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.PackageAgeChecker = exports.LicenseChecker = exports.CVEChecker = exports.WhitelistChecker = exports.MetricsCollector = exports.SecurityLogger = void 0;
49
+ const semver = __importStar(require("semver"));
50
+ const logger_1 = require("./lib/logger");
51
+ const metrics_1 = require("./lib/metrics");
52
+ const whitelist_checker_1 = require("./lib/whitelist-checker");
53
+ const cve_checker_1 = require("./lib/cve-checker");
54
+ const license_checker_1 = require("./lib/license-checker");
55
+ const package_age_checker_1 = require("./lib/package-age-checker");
56
+ /**
57
+ * Security Filter Plugin for Verdaccio
58
+ * Works with both middleware and filter interfaces for complete protection
59
+ * Compatible with Verdaccio 6.x and 7.x
60
+ *
61
+ * Two-layer protection:
62
+ * - Layer 1 (filter_metadata): CVE/License/Age checks when metadata is available
63
+ * - Layer 2 (middleware): Whitelist/Pattern/Scope checks + tarball blocking
64
+ */
65
+ class SecurityFilterPlugin {
66
+ /**
67
+ * Creates a new SecurityFilterPlugin instance
68
+ * @param {SecurityConfig} config - Plugin configuration
69
+ * @param {PluginOptions} options - Verdaccio plugin options
70
+ */
71
+ constructor(config, options) {
72
+ this.config = config || {};
73
+ // Set default error handling strategy if not provided
74
+ if (!this.config.errorHandling) {
75
+ this.config.errorHandling = {
76
+ onFilterError: 'fail-open',
77
+ onCveCheckError: 'fail-open',
78
+ onLicenseCheckError: 'fail-open',
79
+ };
80
+ }
81
+ // Initialize enhanced logger
82
+ this.logger = new logger_1.SecurityLogger(options.logger, this.config.logger);
83
+ // Initialize metrics collector
84
+ this.metrics = new metrics_1.MetricsCollector(this.config.metrics);
85
+ // Initialize whitelist checker
86
+ this.whitelistChecker = new whitelist_checker_1.WhitelistChecker(this.config.whitelist);
87
+ // Initialize security checkers
88
+ this.cveChecker = new cve_checker_1.CVEChecker(this.config.cveCheck);
89
+ this.licenseChecker = new license_checker_1.LicenseChecker(this.config.licenses);
90
+ this.packageAgeChecker = new package_age_checker_1.PackageAgeChecker(this.config.packageAge);
91
+ // Security rules configuration
92
+ this.securityRules = {
93
+ blockedVersions: this.config.blockedVersions || [],
94
+ blockedPatterns: this.config.blockedPatterns || [],
95
+ minPackageSize: this.config.minPackageSize || 0,
96
+ maxPackageSize: this.config.maxPackageSize || 100 * 1024 * 1024, // 100MB
97
+ allowedScopes: this.config.allowedScopes || [],
98
+ blockedScopes: this.config.blockedScopes || [],
99
+ enforceChecksum: this.config.enforceChecksum !== false,
100
+ versionRangeRules: this._parseVersionRangeRules(this.config.versionRangeRules || [])
101
+ };
102
+ this._logInitialization();
103
+ }
104
+ /**
105
+ * Parse and validate version range rules
106
+ * @private
107
+ */
108
+ _parseVersionRangeRules(rules) {
109
+ return rules.map((rule, index) => {
110
+ if (!rule.package || !rule.range || !rule.strategy) {
111
+ this.logger.warn(`Invalid version range rule at index ${index}, skipping`);
112
+ return null;
113
+ }
114
+ if (rule.strategy === 'fallback' && !rule.fallbackVersion) {
115
+ this.logger.warn(`Fallback strategy requires fallbackVersion for ${rule.package}, skipping`);
116
+ return null;
117
+ }
118
+ // Validate semver range
119
+ try {
120
+ const validRange = semver.validRange(rule.range);
121
+ if (!validRange) {
122
+ this.logger.warn(`Invalid semver range "${rule.range}" for ${rule.package}, skipping`);
123
+ return null;
124
+ }
125
+ }
126
+ catch (error) {
127
+ this.logger.warn(`Error parsing range "${rule.range}" for ${rule.package}: ${error.message}`);
128
+ return null;
129
+ }
130
+ return rule;
131
+ }).filter((rule) => rule !== null);
132
+ }
133
+ /**
134
+ * Log initialization details
135
+ * @private
136
+ */
137
+ _logInitialization() {
138
+ const features = this._getEnabledFeatures();
139
+ this.logger.logInit(features);
140
+ this.logger.debug(`[Init] Config mode: ${this.config.mode || 'not set'}`);
141
+ this.logger.debug(`[Init] Blocked versions: ${this.securityRules.blockedVersions.length}`);
142
+ this.logger.debug(`[Init] Blocked patterns: ${this.securityRules.blockedPatterns.length}`);
143
+ if (this.securityRules.versionRangeRules.length > 0) {
144
+ this.logger.info('Version range rules configured:');
145
+ this.securityRules.versionRangeRules.forEach(rule => {
146
+ const fallback = rule.strategy === 'fallback' ? ` -> ${rule.fallbackVersion}` : '';
147
+ this.logger.debug(` ${rule.package} ${rule.range} [${rule.strategy}${fallback}]`);
148
+ });
149
+ }
150
+ if (this.config.mode === 'whitelist') {
151
+ const summary = this.whitelistChecker.getSummary();
152
+ this.logger.info(`Whitelist mode: ${summary.totalPackages} packages, ${summary.totalPatterns} patterns`);
153
+ }
154
+ }
155
+ /**
156
+ * Get list of enabled features
157
+ * @private
158
+ */
159
+ _getEnabledFeatures() {
160
+ var _a, _b, _c, _d;
161
+ const features = [];
162
+ if (this.config.mode === 'whitelist')
163
+ features.push('whitelist-mode');
164
+ if ((_a = this.config.metrics) === null || _a === void 0 ? void 0 : _a.enabled)
165
+ features.push('metrics');
166
+ if ((_b = this.config.cveCheck) === null || _b === void 0 ? void 0 : _b.enabled)
167
+ features.push('cve-checking');
168
+ if (((_c = this.config.licenses) === null || _c === void 0 ? void 0 : _c.allowed) && this.config.licenses.allowed.length > 0)
169
+ features.push('license-filtering');
170
+ if ((_d = this.config.packageAge) === null || _d === void 0 ? void 0 : _d.enabled)
171
+ features.push('package-age-verification');
172
+ if (this.securityRules.versionRangeRules.length > 0)
173
+ features.push('version-range-rules');
174
+ if (this.securityRules.blockedPatterns.length > 0)
175
+ features.push('pattern-blocking');
176
+ if (this.securityRules.blockedVersions.length > 0)
177
+ features.push('version-blocking');
178
+ if (this.securityRules.blockedScopes.length > 0)
179
+ features.push('scope-blocking');
180
+ return features;
181
+ }
182
+ /**
183
+ * Check if package name matches blocked patterns
184
+ * @private
185
+ */
186
+ _isBlockedByPattern(packageName) {
187
+ return this.securityRules.blockedPatterns.some(pattern => {
188
+ const regex = new RegExp(pattern);
189
+ return regex.test(packageName);
190
+ });
191
+ }
192
+ /**
193
+ * Check if package scope is allowed
194
+ * @private
195
+ */
196
+ _isScopeAllowed(packageName) {
197
+ // If not a scoped package, check if allowedScopes is empty
198
+ if (!packageName.startsWith('@')) {
199
+ return this.securityRules.allowedScopes.length === 0;
200
+ }
201
+ const scope = packageName.split('/')[0];
202
+ // Check blacklist
203
+ if (this.securityRules.blockedScopes.includes(scope)) {
204
+ return false;
205
+ }
206
+ // Check whitelist (if exists)
207
+ if (this.securityRules.allowedScopes.length > 0) {
208
+ return this.securityRules.allowedScopes.includes(scope);
209
+ }
210
+ return true;
211
+ }
212
+ /**
213
+ * Get version range rule for a specific package and version
214
+ * @private
215
+ */
216
+ _getVersionRangeRule(packageName, version) {
217
+ return this.securityRules.versionRangeRules.find(rule => {
218
+ if (rule.package !== packageName) {
219
+ return false;
220
+ }
221
+ try {
222
+ return semver.satisfies(version, rule.range);
223
+ }
224
+ catch (error) {
225
+ this.logger.warn(`Error checking version ${version} against range ${rule.range}: ${error.message}`);
226
+ return false;
227
+ }
228
+ }) || null;
229
+ }
230
+ /**
231
+ * Register Express middleware to intercept package requests
232
+ * Intercepts both metadata and tarball requests
233
+ */
234
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
235
+ register_middlewares(app, _auth, _storage) {
236
+ this.logger.info('[Middleware] Registering security filter middleware');
237
+ this.logger.info(`[Middleware] Config mode: ${this.config.mode || 'NOT SET'}`);
238
+ // Middleware to intercept all package requests
239
+ app.use((req, res, next) => __awaiter(this, void 0, void 0, function* () {
240
+ try {
241
+ // Extract package name and version from URL
242
+ // Tarball URLs: /:package/-/:filename.tgz
243
+ // Scoped tarball URLs: /@scope/:package/-/:filename.tgz
244
+ // Metadata URLs: /:package or /@scope/:package
245
+ const urlMatch = req.url.match(/^\/(@[^/]+\/)?([^/]+)(?:\/-\/([^/]+\.tgz))?/);
246
+ if (!urlMatch) {
247
+ // Not a package-related request, pass through
248
+ this.logger.debug(`[Middleware] Non-package request: ${req.method} ${req.url}`);
249
+ return next();
250
+ }
251
+ const scope = urlMatch[1] || '';
252
+ const packageName = scope + urlMatch[2];
253
+ const tarballFile = urlMatch[3];
254
+ // Extract version from tarball filename if present
255
+ let version;
256
+ if (tarballFile) {
257
+ // This is a tarball download request - block it immediately
258
+ const versionMatch = tarballFile.match(/-([\d.]+(?:-[a-zA-Z0-9.]+)?)\.tgz$/);
259
+ version = versionMatch ? versionMatch[1] : undefined;
260
+ this.logger.info(`[Middleware] TARBALL REQUEST: ${packageName}@${version || 'unknown'}`);
261
+ // Apply all security checks for tarball downloads
262
+ const blockResult = yield this._checkPackageBlock(packageName, version);
263
+ if (blockResult.blocked) {
264
+ this.logger.warn(`[Middleware] [X] BLOCKED TARBALL: ${packageName}@${version || '*'} - ${blockResult.reason}`);
265
+ this.metrics.recordBlock(packageName, version || '*', blockResult.reason);
266
+ // Return 403 Forbidden with detailed error
267
+ res.status(403).json({
268
+ error: 'Package blocked by security filter',
269
+ package: packageName,
270
+ version: version || 'all versions',
271
+ reason: blockResult.reason,
272
+ timestamp: new Date().toISOString()
273
+ });
274
+ return;
275
+ }
276
+ this.logger.info(`[Middleware] [OK] ALLOWED TARBALL: ${packageName}@${version || 'unknown'}`);
277
+ }
278
+ else {
279
+ // This is a metadata request - intercept response and modify it
280
+ this.logger.info(`[Middleware] METADATA REQUEST: ${packageName} - will intercept response`);
281
+ // Check if package should be blocked
282
+ const blockResult = yield this._checkPackageBlock(packageName);
283
+ if (blockResult.blocked) {
284
+ // Intercept the response
285
+ const originalSend = res.send;
286
+ const originalJson = res.json;
287
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
288
+ const self = this;
289
+ // Override res.json to intercept JSON responses
290
+ res.json = function (body) {
291
+ if (body && typeof body === 'object' && body.name === packageName) {
292
+ // This is package metadata - modify it to show blocked info
293
+ self.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
294
+ self.logger.info(`[Middleware] Modifying response to show blocking info`);
295
+ const blockedResponse = {
296
+ name: packageName,
297
+ versions: {},
298
+ 'dist-tags': {},
299
+ security: {
300
+ blocked: true,
301
+ reason: blockResult.reason,
302
+ plugin: {
303
+ name: 'verdaccio-security-filter',
304
+ version: '1.0.0',
305
+ mode: self.config.mode || 'blacklist',
306
+ },
307
+ blockedAt: new Date().toISOString(),
308
+ rules: self._getAppliedRules(packageName),
309
+ message: `This package has been blocked by security filter: ${blockResult.reason}`,
310
+ contact: 'Please contact your registry administrator for more information',
311
+ },
312
+ _security: {
313
+ blocked: true,
314
+ reason: blockResult.reason,
315
+ blockedBy: 'verdaccio-security-filter',
316
+ blockedAt: new Date().toISOString(),
317
+ }
318
+ };
319
+ return originalJson.call(this, blockedResponse);
320
+ }
321
+ return originalJson.call(this, body);
322
+ };
323
+ // Override res.send for non-JSON responses
324
+ res.send = function (body) {
325
+ if (typeof body === 'string') {
326
+ try {
327
+ const parsed = JSON.parse(body);
328
+ if (parsed && parsed.name === packageName) {
329
+ self.logger.warn(`[Middleware] [X] BLOCKED METADATA: ${packageName} - ${blockResult.reason}`);
330
+ const blockedResponse = {
331
+ name: packageName,
332
+ versions: {},
333
+ 'dist-tags': {},
334
+ security: {
335
+ blocked: true,
336
+ reason: blockResult.reason,
337
+ plugin: {
338
+ name: 'verdaccio-security-filter',
339
+ version: '1.0.0',
340
+ mode: self.config.mode || 'blacklist',
341
+ },
342
+ blockedAt: new Date().toISOString(),
343
+ rules: self._getAppliedRules(packageName),
344
+ message: `This package has been blocked by security filter: ${blockResult.reason}`,
345
+ contact: 'Please contact your registry administrator for more information',
346
+ }
347
+ };
348
+ return originalSend.call(this, JSON.stringify(blockedResponse));
349
+ }
350
+ }
351
+ catch (_a) {
352
+ // Not JSON, pass through
353
+ }
354
+ }
355
+ return originalSend.call(this, body);
356
+ };
357
+ }
358
+ else {
359
+ this.logger.info(`[Middleware] [OK] ALLOWED METADATA: ${packageName}`);
360
+ }
361
+ }
362
+ // Continue to next middleware
363
+ next();
364
+ }
365
+ catch (error) {
366
+ this.logger.error(`[Middleware] Error processing request: ${error.message}`);
367
+ next(error);
368
+ }
369
+ }));
370
+ this.logger.info('[Middleware] Security filter middleware registered successfully');
371
+ }
372
+ /**
373
+ * Filter package metadata before returning to client
374
+ * This provides CVE, License, and Age checking when metadata is available
375
+ */
376
+ filter_metadata(packageInfo) {
377
+ return __awaiter(this, void 0, void 0, function* () {
378
+ var _a, _b, _c, _d, _e;
379
+ const packageName = packageInfo.name;
380
+ this.logger.info(`[filter_metadata] --> Processing: ${packageName}`);
381
+ try {
382
+ // 1. Whitelist/blacklist check (basic filtering)
383
+ const blockResult = yield this._checkPackageBlock(packageName);
384
+ if (blockResult.blocked) {
385
+ this.logger.warn(`[filter_metadata] BLOCKED: ${packageName}@* - ${blockResult.reason}`);
386
+ this.metrics.recordBlock(packageName, '*', blockResult.reason);
387
+ // Return empty versions with security field
388
+ return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
389
+ blocked: true,
390
+ reason: blockResult.reason,
391
+ plugin: {
392
+ name: 'verdaccio-security-filter',
393
+ version: '2.0.0',
394
+ mode: this.config.mode || 'blacklist',
395
+ },
396
+ blockedAt: new Date().toISOString(),
397
+ rules: this._getAppliedRules(packageName),
398
+ } });
399
+ }
400
+ // 2. CVE Check - check all versions
401
+ if ((_a = this.config.cveCheck) === null || _a === void 0 ? void 0 : _a.enabled) {
402
+ const versions = Object.keys(packageInfo.versions || {});
403
+ const vulnerableVersions = [];
404
+ for (const version of versions) {
405
+ const cveResult = yield this.cveChecker.checkPackage(packageName, version);
406
+ if (cveResult.isVulnerable) {
407
+ vulnerableVersions.push(version);
408
+ this.logger.warn(`[filter_metadata] CVE found in ${packageName}@${version}: ${cveResult.vulnerabilities.length} vulnerabilities`);
409
+ }
410
+ }
411
+ // If configured to auto-block and vulnerabilities found
412
+ if (this.config.cveCheck.autoBlock && vulnerableVersions.length > 0) {
413
+ const reason = `Package has ${vulnerableVersions.length} vulnerable version(s)`;
414
+ this.logger.warn(`[filter_metadata] CVE BLOCKED: ${packageName} - ${reason}`);
415
+ this.metrics.recordBlock(packageName, '*', reason);
416
+ return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
417
+ blocked: true,
418
+ reason,
419
+ vulnerableVersions,
420
+ plugin: {
421
+ name: 'verdaccio-security-filter',
422
+ version: '2.0.0',
423
+ },
424
+ blockedAt: new Date().toISOString(),
425
+ } });
426
+ }
427
+ }
428
+ // 3. License Check - check latest version
429
+ if (this.config.licenses && this.config.licenses.allowed && this.config.licenses.allowed.length > 0) {
430
+ const latestVersion = (_b = packageInfo['dist-tags']) === null || _b === void 0 ? void 0 : _b.latest;
431
+ if (latestVersion && ((_c = packageInfo.versions) === null || _c === void 0 ? void 0 : _c[latestVersion])) {
432
+ const versionData = packageInfo.versions[latestVersion];
433
+ const licenseResult = this.licenseChecker.checkLicense(versionData);
434
+ if (!licenseResult.allowed) {
435
+ this.logger.warn(`[filter_metadata] LICENSE BLOCKED: ${packageName} - ${licenseResult.reason}`);
436
+ this.metrics.recordBlock(packageName, '*', licenseResult.reason || 'License not allowed');
437
+ return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
438
+ blocked: true,
439
+ reason: licenseResult.reason || 'License not allowed',
440
+ license: licenseResult.license,
441
+ plugin: {
442
+ name: 'verdaccio-security-filter',
443
+ version: '2.0.0',
444
+ },
445
+ blockedAt: new Date().toISOString(),
446
+ } });
447
+ }
448
+ }
449
+ }
450
+ // 4. Package Age Check
451
+ if ((_d = this.config.packageAge) === null || _d === void 0 ? void 0 : _d.enabled) {
452
+ const ageResult = this.packageAgeChecker.checkPackageAge(packageInfo);
453
+ if (!ageResult.allowed && !ageResult.warnOnly) {
454
+ this.logger.warn(`[filter_metadata] AGE BLOCKED: ${packageName} - ${ageResult.reason}`);
455
+ this.metrics.recordBlock(packageName, '*', ageResult.reason || 'Package too new');
456
+ return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
457
+ blocked: true,
458
+ reason: ageResult.reason || 'Package too new',
459
+ ageDays: ageResult.ageDays,
460
+ plugin: {
461
+ name: 'verdaccio-security-filter',
462
+ version: '2.0.0',
463
+ },
464
+ blockedAt: new Date().toISOString(),
465
+ } });
466
+ }
467
+ else if (!ageResult.allowed && ageResult.warnOnly) {
468
+ this.logger.warn(`[filter_metadata] AGE WARNING: ${packageName} - ${ageResult.reason} (warn-only mode)`);
469
+ }
470
+ }
471
+ this.logger.info(`[filter_metadata] [OK] ALLOWED: ${packageName}`);
472
+ return packageInfo;
473
+ }
474
+ catch (error) {
475
+ this.logger.error(`[filter_metadata] Error processing ${packageName}: ${error.message}`);
476
+ const errorStrategy = ((_e = this.config.errorHandling) === null || _e === void 0 ? void 0 : _e.onFilterError) || 'fail-open';
477
+ if (errorStrategy === 'fail-closed') {
478
+ this.logger.warn(`[filter_metadata] Error handling: fail-closed - BLOCKING ${packageName}`);
479
+ this.metrics.recordBlock(packageName, '*', `Error during processing: ${error.message}`);
480
+ return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
481
+ blocked: true,
482
+ reason: `Error during security processing (fail-closed mode): ${error.message}`,
483
+ plugin: {
484
+ name: 'verdaccio-security-filter',
485
+ version: '2.0.0',
486
+ },
487
+ blockedAt: new Date().toISOString(),
488
+ } });
489
+ }
490
+ this.logger.warn(`[filter_metadata] Error handling: fail-open - ALLOWING ${packageName}`);
491
+ return packageInfo;
492
+ }
493
+ });
494
+ }
495
+ /**
496
+ * Check if package should be blocked (used by middleware)
497
+ * @private
498
+ */
499
+ _checkPackageBlock(packageName, version) {
500
+ return __awaiter(this, void 0, void 0, function* () {
501
+ // 1. Whitelist mode
502
+ if (this.config.mode === 'whitelist') {
503
+ const whitelistCheck = this.whitelistChecker.isWhitelisted(packageName, version);
504
+ if (!whitelistCheck.allowed) {
505
+ return { blocked: true, reason: whitelistCheck.reason || 'Package is not in whitelist' };
506
+ }
507
+ }
508
+ // 2. Blocked patterns
509
+ if (this._isBlockedByPattern(packageName)) {
510
+ return { blocked: true, reason: 'Package name matches blocked pattern' };
511
+ }
512
+ // 3. Scopes
513
+ if (!this._isScopeAllowed(packageName)) {
514
+ return { blocked: true, reason: 'Package scope not allowed' };
515
+ }
516
+ // 4. Exact version blocking
517
+ if (version) {
518
+ const versionKey = `${packageName}@${version}`;
519
+ if (this.securityRules.blockedVersions.includes(versionKey)) {
520
+ return { blocked: true, reason: 'Exact version match in blocklist' };
521
+ }
522
+ // 5. Version range rules
523
+ const rangeRule = this._getVersionRangeRule(packageName, version);
524
+ if (rangeRule && rangeRule.strategy === 'block') {
525
+ return { blocked: true, reason: `Version matches blocked range: ${rangeRule.range}` };
526
+ }
527
+ }
528
+ return { blocked: false, reason: '' };
529
+ });
530
+ }
531
+ /**
532
+ * Get applied security rules for a package
533
+ * @private
534
+ */
535
+ _getAppliedRules(packageName) {
536
+ const rules = {};
537
+ // Check which rules apply to this package
538
+ if (this.config.mode === 'whitelist') {
539
+ rules.mode = 'whitelist';
540
+ rules.whitelisted = false;
541
+ }
542
+ if (this._isBlockedByPattern(packageName)) {
543
+ rules.blockedPattern = this.securityRules.blockedPatterns.find(pattern => {
544
+ const regex = new RegExp(pattern);
545
+ return regex.test(packageName);
546
+ });
547
+ }
548
+ if (packageName.startsWith('@')) {
549
+ const scope = packageName.split('/')[0];
550
+ if (this.securityRules.blockedScopes.includes(scope)) {
551
+ rules.blockedScope = scope;
552
+ }
553
+ if (this.securityRules.allowedScopes.length > 0 && !this.securityRules.allowedScopes.includes(scope)) {
554
+ rules.scopeNotInAllowedList = scope;
555
+ rules.allowedScopes = this.securityRules.allowedScopes;
556
+ }
557
+ }
558
+ const versionRules = this.securityRules.versionRangeRules.filter(rule => rule.package === packageName);
559
+ if (versionRules.length > 0) {
560
+ rules.versionRangeRules = versionRules.map(rule => ({
561
+ range: rule.range,
562
+ strategy: rule.strategy,
563
+ reason: rule.reason,
564
+ }));
565
+ }
566
+ return rules;
567
+ }
568
+ }
569
+ exports.default = SecurityFilterPlugin;
570
+ // Export utility classes for testing
571
+ var logger_2 = require("./lib/logger");
572
+ Object.defineProperty(exports, "SecurityLogger", { enumerable: true, get: function () { return logger_2.SecurityLogger; } });
573
+ var metrics_2 = require("./lib/metrics");
574
+ Object.defineProperty(exports, "MetricsCollector", { enumerable: true, get: function () { return metrics_2.MetricsCollector; } });
575
+ var whitelist_checker_2 = require("./lib/whitelist-checker");
576
+ Object.defineProperty(exports, "WhitelistChecker", { enumerable: true, get: function () { return whitelist_checker_2.WhitelistChecker; } });
577
+ var cve_checker_2 = require("./lib/cve-checker");
578
+ Object.defineProperty(exports, "CVEChecker", { enumerable: true, get: function () { return cve_checker_2.CVEChecker; } });
579
+ var license_checker_2 = require("./lib/license-checker");
580
+ Object.defineProperty(exports, "LicenseChecker", { enumerable: true, get: function () { return license_checker_2.LicenseChecker; } });
581
+ var package_age_checker_2 = require("./lib/package-age-checker");
582
+ Object.defineProperty(exports, "PackageAgeChecker", { enumerable: true, get: function () { return package_age_checker_2.PackageAgeChecker; } });
583
+ __exportStar(require("./types"), exports);