verdaccio-security-filter 1.0.6

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