verdaccio-security-filter 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -71,6 +71,13 @@ This plugin implements **two independent security layers** for complete protecti
71
71
  - **Minimum Package Age** - Require packages to exist for a minimum number of days
72
72
  - **Minimum Version Age** - Require specific versions to exist for a minimum number of days
73
73
  - **Warn-Only Mode** - Log warnings without blocking
74
+ - **Author Filtering** - Block packages based on author information
75
+ - **Block by Author Name** - Block specific author names or patterns
76
+ - **Block by Email** - Block specific email addresses or patterns
77
+ - **Block by Email Domain** - Block entire email domains (e.g., `.ru`, `yandex.ru`)
78
+ - **Block by Region** - Block authors from specific regions based on email domains
79
+ - **Maintainer & Contributor Checking** - Validates all package maintainers and contributors
80
+ - **Warn-Only Mode** - Log warnings without blocking
74
81
  - **Middleware Interception** - HTTP middleware that blocks tarball downloads for blocked packages
75
82
  - Prevents blocked packages from being installed as dependencies
76
83
  - Intercepts requests to `/:package/-/:filename.tgz` routes
@@ -467,6 +474,20 @@ middlewares:
467
474
  | `packageAge.minVersionAgeDays` | `number` | `undefined` | Minimum age for versions (days) |
468
475
  | `packageAge.warnOnly` | `boolean` | `false` | Only warn, don't block |
469
476
 
477
+ ### Author Filter Options
478
+
479
+ | Option | Type | Default | Description |
480
+ |--------|------|---------|-------------|
481
+ | `authorFilter.enabled` | `boolean` | `false` | Enable author filtering |
482
+ | `authorFilter.blockedAuthors` | `string[]` | `[]` | List of author names to block |
483
+ | `authorFilter.blockedAuthorPatterns` | `string[]` | `[]` | Regex patterns for author names |
484
+ | `authorFilter.blockedEmails` | `string[]` | `[]` | List of email addresses to block |
485
+ | `authorFilter.blockedEmailPatterns` | `string[]` | `[]` | Regex patterns for author emails |
486
+ | `authorFilter.blockedEmailDomains` | `string[]` | `[]` | Email domains to block (e.g., `.ru`, `yandex.ru`) |
487
+ | `authorFilter.blockedRegions` | `string[]` | `[]` | Region codes to block (`ru`, `cn`, `by`, `kp`, `ir`, `sy`, `cu`, `sd`) |
488
+ | `authorFilter.requireVerifiedEmail` | `boolean` | `false` | Require author email information |
489
+ | `authorFilter.warnOnly` | `boolean` | `false` | Only warn, don't block |
490
+
470
491
  ### Error Handling Options
471
492
 
472
493
  | Option | Type | Default | Description |
@@ -533,13 +554,14 @@ npm test:watch
533
554
  ```
534
555
 
535
556
  **Test Results:**
536
- - ✅ 80 tests passing
557
+ - ✅ 108 tests passing
537
558
  - ✅ 60%+ code coverage
538
559
  - ✅ SecurityFilterPlugin: 21 tests
539
560
  - ✅ PackageAgeChecker: 13 tests
540
561
  - ✅ SecurityLogger: 16 tests
541
562
  - ✅ LicenseChecker: 15 tests
542
563
  - ✅ WhitelistChecker: 15 tests
564
+ - ✅ AuthorChecker: 28 tests
543
565
 
544
566
  ## 🔧 Development
545
567
 
@@ -567,6 +589,7 @@ When metrics are enabled, the plugin tracks:
567
589
  - `cve_detected` - CVE vulnerabilities found
568
590
  - `license_blocked` - Packages blocked by license rules
569
591
  - `package_too_new` - Packages/versions blocked due to age restrictions
592
+ - `author_blocked` - Packages blocked due to author/region filtering
570
593
 
571
594
  Example metrics output:
572
595
  ```json
@@ -656,16 +679,54 @@ packageAge:
656
679
  warnOnly: false # Block, don't just warn
657
680
  ```
658
681
 
682
+ ### 7. Block Packages by Author/Region
683
+
684
+ ```yaml
685
+ # Block packages from specific authors, emails, or regions
686
+ authorFilter:
687
+ enabled: true
688
+ # Block specific author names
689
+ blockedAuthors:
690
+ - "Suspicious Author"
691
+ - "Known Bad Actor"
692
+ # Block author names matching patterns
693
+ blockedAuthorPatterns:
694
+ - "^Bot.*"
695
+ - ".*Spammer$"
696
+ # Block specific email addresses
697
+ blockedEmails:
698
+ - "malicious@example.com"
699
+ # Block email patterns
700
+ blockedEmailPatterns:
701
+ - ".*@temporary-mail\\.com$"
702
+ # Block entire email domains
703
+ blockedEmailDomains:
704
+ - ".ru" # All .ru domains
705
+ - "yandex.ru" # Specific domain
706
+ - "mail.ru"
707
+ # Block by region (includes multiple common domains)
708
+ blockedRegions:
709
+ - "ru" # Russia (.ru, yandex.ru, mail.ru, etc.)
710
+ - "cn" # China (.cn, qq.com, 163.com, etc.)
711
+ - "by" # Belarus
712
+ - "kp" # North Korea
713
+ # Require author information
714
+ requireVerifiedEmail: true
715
+ # Warn-only mode (don't block, just log)
716
+ warnOnly: false
717
+ ```
718
+
659
719
  ## 🔐 Security Best Practices
660
720
 
661
721
  1. **Enable CVE Scanning** - Automatically detect and block vulnerable packages
662
722
  2. **Use Whitelist Mode** - For maximum security in sensitive environments
663
723
  3. **Enforce License Compliance** - Prevent legal issues with license filtering
664
724
  4. **Enable Package Age Filtering** - Block newly published packages to prevent supply chain attacks
665
- 5. **Enable Metrics** - Track security events for audit and compliance
666
- 6. **Regular Updates** - Keep the plugin and CVE database cache updated
667
- 7. **Test Rules** - Use dry run mode (planned) before applying strict rules
668
- 8. **Monitor Logs** - Review security logs regularly for suspicious activity
725
+ 5. **Enable Author Filtering** - Block packages from untrusted authors or regions
726
+ 6. **Enable Metrics** - Track security events for audit and compliance
727
+ 7. **Regular Updates** - Keep the plugin and CVE database cache updated
728
+ 8. **Test Rules** - Use dry run mode (planned) before applying strict rules
729
+ 9. **Monitor Logs** - Review security logs regularly for suspicious activity
669
730
 
670
731
  ## 🐛 Troubleshooting
671
732
 
package/build/index.d.ts CHANGED
@@ -20,6 +20,7 @@ export default class SecurityFilterPlugin implements IPluginMiddleware<SecurityC
20
20
  private readonly cveChecker;
21
21
  private readonly licenseChecker;
22
22
  private readonly packageAgeChecker;
23
+ private readonly authorChecker;
23
24
  /**
24
25
  * Creates a new SecurityFilterPlugin instance
25
26
  * @param {SecurityConfig} config - Plugin configuration
@@ -83,4 +84,5 @@ export { WhitelistChecker } from './lib/whitelist-checker';
83
84
  export { CVEChecker } from './lib/cve-checker';
84
85
  export { LicenseChecker } from './lib/license-checker';
85
86
  export { PackageAgeChecker } from './lib/package-age-checker';
87
+ export { AuthorChecker } from './lib/author-checker';
86
88
  export * from './types';
package/build/index.js CHANGED
@@ -45,7 +45,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
45
45
  });
46
46
  };
47
47
  Object.defineProperty(exports, "__esModule", { value: true });
48
- exports.PackageAgeChecker = exports.LicenseChecker = exports.CVEChecker = exports.WhitelistChecker = exports.MetricsCollector = exports.SecurityLogger = void 0;
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
50
  const logger_1 = require("./lib/logger");
51
51
  const metrics_1 = require("./lib/metrics");
@@ -53,6 +53,7 @@ const whitelist_checker_1 = require("./lib/whitelist-checker");
53
53
  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
+ const author_checker_1 = require("./lib/author-checker");
56
57
  /**
57
58
  * Security Filter Plugin for Verdaccio
58
59
  * Works with both middleware and filter interfaces for complete protection
@@ -88,6 +89,7 @@ class SecurityFilterPlugin {
88
89
  this.cveChecker = new cve_checker_1.CVEChecker(this.config.cveCheck);
89
90
  this.licenseChecker = new license_checker_1.LicenseChecker(this.config.licenses);
90
91
  this.packageAgeChecker = new package_age_checker_1.PackageAgeChecker(this.config.packageAge);
92
+ this.authorChecker = new author_checker_1.AuthorChecker(this.config.authorFilter);
91
93
  // Security rules configuration
92
94
  this.securityRules = {
93
95
  blockedVersions: this.config.blockedVersions || [],
@@ -135,6 +137,7 @@ class SecurityFilterPlugin {
135
137
  * @private
136
138
  */
137
139
  _logInitialization() {
140
+ var _a;
138
141
  const features = this._getEnabledFeatures();
139
142
  this.logger.logInit(features);
140
143
  this.logger.debug(`[Init] Config mode: ${this.config.mode || 'not set'}`);
@@ -151,13 +154,17 @@ class SecurityFilterPlugin {
151
154
  const summary = this.whitelistChecker.getSummary();
152
155
  this.logger.info(`Whitelist mode: ${summary.totalPackages} packages, ${summary.totalPatterns} patterns`);
153
156
  }
157
+ if ((_a = this.config.authorFilter) === null || _a === void 0 ? void 0 : _a.enabled) {
158
+ const summary = this.authorChecker.getSummary();
159
+ this.logger.info(`Author filtering enabled: ${summary.blockedAuthors} authors, ${summary.blockedEmails} emails, ${summary.blockedRegions} regions blocked`);
160
+ }
154
161
  }
155
162
  /**
156
163
  * Get list of enabled features
157
164
  * @private
158
165
  */
159
166
  _getEnabledFeatures() {
160
- var _a, _b, _c, _d;
167
+ var _a, _b, _c, _d, _e;
161
168
  const features = [];
162
169
  if (this.config.mode === 'whitelist')
163
170
  features.push('whitelist-mode');
@@ -169,6 +176,8 @@ class SecurityFilterPlugin {
169
176
  features.push('license-filtering');
170
177
  if ((_d = this.config.packageAge) === null || _d === void 0 ? void 0 : _d.enabled)
171
178
  features.push('package-age-verification');
179
+ if ((_e = this.config.authorFilter) === null || _e === void 0 ? void 0 : _e.enabled)
180
+ features.push('author-filtering');
172
181
  if (this.securityRules.versionRangeRules.length > 0)
173
182
  features.push('version-range-rules');
174
183
  if (this.securityRules.blockedPatterns.length > 0)
@@ -375,7 +384,7 @@ class SecurityFilterPlugin {
375
384
  */
376
385
  filter_metadata(packageInfo) {
377
386
  return __awaiter(this, void 0, void 0, function* () {
378
- var _a, _b, _c, _d, _e;
387
+ var _a, _b, _c, _d, _e, _f, _g, _h;
379
388
  const packageName = packageInfo.name;
380
389
  this.logger.info(`[filter_metadata] --> Processing: ${packageName}`);
381
390
  try {
@@ -468,12 +477,48 @@ class SecurityFilterPlugin {
468
477
  this.logger.warn(`[filter_metadata] AGE WARNING: ${packageName} - ${ageResult.reason} (warn-only mode)`);
469
478
  }
470
479
  }
480
+ // 5. Author Filter Check
481
+ if ((_e = this.config.authorFilter) === null || _e === void 0 ? void 0 : _e.enabled) {
482
+ const latestVersion = (_f = packageInfo['dist-tags']) === null || _f === void 0 ? void 0 : _f.latest;
483
+ if (latestVersion && ((_g = packageInfo.versions) === null || _g === void 0 ? void 0 : _g[latestVersion])) {
484
+ const versionData = packageInfo.versions[latestVersion];
485
+ const authorResult = this.authorChecker.checkAuthor(versionData);
486
+ if (!authorResult.allowed && !this.config.authorFilter.warnOnly) {
487
+ this.logger.warn(`[filter_metadata] AUTHOR BLOCKED: ${packageName} - ${authorResult.reason}`);
488
+ this.metrics.record({
489
+ timestamp: new Date().toISOString(),
490
+ event: 'author_blocked',
491
+ packageName,
492
+ version: latestVersion,
493
+ reason: authorResult.reason || 'Author not allowed',
494
+ metadata: {
495
+ blockedBy: authorResult.blockedBy,
496
+ authorInfo: authorResult.authorInfo,
497
+ },
498
+ });
499
+ return Object.assign(Object.assign({}, packageInfo), { versions: {}, 'dist-tags': {}, security: {
500
+ blocked: true,
501
+ reason: authorResult.reason || 'Author not allowed',
502
+ blockedBy: authorResult.blockedBy,
503
+ authorInfo: authorResult.authorInfo,
504
+ plugin: {
505
+ name: 'verdaccio-security-filter',
506
+ version: '2.0.0',
507
+ },
508
+ blockedAt: new Date().toISOString(),
509
+ } });
510
+ }
511
+ else if (!authorResult.allowed && this.config.authorFilter.warnOnly) {
512
+ this.logger.warn(`[filter_metadata] AUTHOR WARNING: ${packageName} - ${authorResult.reason} (warn-only mode)`);
513
+ }
514
+ }
515
+ }
471
516
  this.logger.info(`[filter_metadata] [OK] ALLOWED: ${packageName}`);
472
517
  return packageInfo;
473
518
  }
474
519
  catch (error) {
475
520
  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';
521
+ const errorStrategy = ((_h = this.config.errorHandling) === null || _h === void 0 ? void 0 : _h.onFilterError) || 'fail-open';
477
522
  if (errorStrategy === 'fail-closed') {
478
523
  this.logger.warn(`[filter_metadata] Error handling: fail-closed - BLOCKING ${packageName}`);
479
524
  this.metrics.recordBlock(packageName, '*', `Error during processing: ${error.message}`);
@@ -580,4 +625,6 @@ var license_checker_2 = require("./lib/license-checker");
580
625
  Object.defineProperty(exports, "LicenseChecker", { enumerable: true, get: function () { return license_checker_2.LicenseChecker; } });
581
626
  var package_age_checker_2 = require("./lib/package-age-checker");
582
627
  Object.defineProperty(exports, "PackageAgeChecker", { enumerable: true, get: function () { return package_age_checker_2.PackageAgeChecker; } });
628
+ var author_checker_2 = require("./lib/author-checker");
629
+ Object.defineProperty(exports, "AuthorChecker", { enumerable: true, get: function () { return author_checker_2.AuthorChecker; } });
583
630
  __exportStar(require("./types"), exports);
@@ -0,0 +1,44 @@
1
+ import { Version } from '@verdaccio/types';
2
+ import { AuthorFilterConfig, AuthorCheckResult } from '../types';
3
+ /**
4
+ * AuthorChecker validates package authors and maintainers
5
+ * against configured security policies.
6
+ */
7
+ export declare class AuthorChecker {
8
+ private readonly config?;
9
+ private readonly blockedAuthors;
10
+ private readonly blockedAuthorPatterns;
11
+ private readonly blockedEmails;
12
+ private readonly blockedEmailPatterns;
13
+ private readonly blockedEmailDomains;
14
+ private readonly regionDomainMap;
15
+ constructor(config?: AuthorFilterConfig);
16
+ /**
17
+ * Check if package author/maintainer is allowed
18
+ */
19
+ checkAuthor(versionData: Version): AuthorCheckResult;
20
+ /**
21
+ * Extract author information from package version data
22
+ */
23
+ private _extractAuthors;
24
+ /**
25
+ * Normalize author data to AuthorInfo format
26
+ */
27
+ private _normalizeAuthor;
28
+ /**
29
+ * Check a single author against all blocking rules
30
+ */
31
+ private _checkSingleAuthor;
32
+ /**
33
+ * Get summary of configuration
34
+ */
35
+ getSummary(): {
36
+ enabled: boolean;
37
+ blockedAuthors: number;
38
+ blockedAuthorPatterns: number;
39
+ blockedEmails: number;
40
+ blockedEmailPatterns: number;
41
+ blockedEmailDomains: number;
42
+ blockedRegions: number;
43
+ };
44
+ }
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthorChecker = void 0;
4
+ /**
5
+ * Region to TLD mapping for email domain filtering
6
+ */
7
+ const REGION_DOMAINS = {
8
+ 'ru': ['.ru', 'yandex.ru', 'mail.ru', 'rambler.ru', 'ya.ru', 'bk.ru', 'list.ru'],
9
+ 'cn': ['.cn', 'qq.com', '163.com', '126.com', 'sina.com', 'sohu.com'],
10
+ '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
16
+ };
17
+ /**
18
+ * AuthorChecker validates package authors and maintainers
19
+ * against configured security policies.
20
+ */
21
+ class AuthorChecker {
22
+ constructor(config) {
23
+ this.config = config;
24
+ this.blockedAuthors = new Set(((config === null || config === void 0 ? void 0 : config.blockedAuthors) || []).map(a => a.toLowerCase().trim()));
25
+ this.blockedAuthorPatterns = ((config === null || config === void 0 ? void 0 : config.blockedAuthorPatterns) || [])
26
+ .map(pattern => new RegExp(pattern, 'i'));
27
+ this.blockedEmails = new Set(((config === null || config === void 0 ? void 0 : config.blockedEmails) || []).map(e => e.toLowerCase().trim()));
28
+ this.blockedEmailPatterns = ((config === null || config === void 0 ? void 0 : config.blockedEmailPatterns) || [])
29
+ .map(pattern => new RegExp(pattern, 'i'));
30
+ this.blockedEmailDomains = new Set(((config === null || config === void 0 ? void 0 : config.blockedEmailDomains) || []).map(d => d.toLowerCase().trim()));
31
+ this.regionDomainMap = new Map();
32
+ ((config === null || config === void 0 ? void 0 : config.blockedRegions) || []).forEach(region => {
33
+ const domains = REGION_DOMAINS[region.toLowerCase()] || [];
34
+ this.regionDomainMap.set(region.toLowerCase(), new Set(domains));
35
+ });
36
+ }
37
+ /**
38
+ * Check if package author/maintainer is allowed
39
+ */
40
+ checkAuthor(versionData) {
41
+ var _a;
42
+ if (!((_a = this.config) === null || _a === void 0 ? void 0 : _a.enabled)) {
43
+ return { allowed: true };
44
+ }
45
+ const authors = this._extractAuthors(versionData);
46
+ if (authors.length === 0) {
47
+ if (this.config.requireVerifiedEmail) {
48
+ return {
49
+ allowed: false,
50
+ reason: 'No author information available',
51
+ blockedBy: 'email',
52
+ };
53
+ }
54
+ return { allowed: true };
55
+ }
56
+ for (const author of authors) {
57
+ const result = this._checkSingleAuthor(author);
58
+ if (!result.allowed) {
59
+ return result;
60
+ }
61
+ }
62
+ return { allowed: true };
63
+ }
64
+ /**
65
+ * Extract author information from package version data
66
+ */
67
+ _extractAuthors(versionData) {
68
+ const authors = [];
69
+ if (versionData.author) {
70
+ authors.push(this._normalizeAuthor(versionData.author));
71
+ }
72
+ if (Array.isArray(versionData.maintainers)) {
73
+ versionData.maintainers.forEach(maintainer => {
74
+ authors.push(this._normalizeAuthor(maintainer));
75
+ });
76
+ }
77
+ if (Array.isArray(versionData.contributors)) {
78
+ versionData.contributors.forEach(contributor => {
79
+ authors.push(this._normalizeAuthor(contributor));
80
+ });
81
+ }
82
+ return authors;
83
+ }
84
+ /**
85
+ * Normalize author data to AuthorInfo format
86
+ */
87
+ _normalizeAuthor(author) {
88
+ if (typeof author === 'string') {
89
+ const emailMatch = author.match(/<([^>]+)>/);
90
+ const nameMatch = author.match(/^([^<]+)/);
91
+ return {
92
+ name: nameMatch ? nameMatch[1].trim() : undefined,
93
+ email: emailMatch ? emailMatch[1].trim() : undefined,
94
+ };
95
+ }
96
+ return {
97
+ name: author.name,
98
+ email: author.email,
99
+ url: author.url,
100
+ };
101
+ }
102
+ /**
103
+ * Check a single author against all blocking rules
104
+ */
105
+ _checkSingleAuthor(author) {
106
+ // Check blocked author names
107
+ if (author.name) {
108
+ const normalizedName = author.name.toLowerCase().trim();
109
+ if (this.blockedAuthors.has(normalizedName)) {
110
+ return {
111
+ allowed: false,
112
+ reason: `Author name "${author.name}" is blocked`,
113
+ blockedBy: 'name',
114
+ authorInfo: author,
115
+ };
116
+ }
117
+ for (const pattern of this.blockedAuthorPatterns) {
118
+ if (pattern.test(author.name)) {
119
+ return {
120
+ allowed: false,
121
+ reason: `Author name "${author.name}" matches blocked pattern`,
122
+ blockedBy: 'name',
123
+ authorInfo: author,
124
+ };
125
+ }
126
+ }
127
+ }
128
+ // Check blocked emails
129
+ if (author.email) {
130
+ const normalizedEmail = author.email.toLowerCase().trim();
131
+ if (this.blockedEmails.has(normalizedEmail)) {
132
+ return {
133
+ allowed: false,
134
+ reason: `Author email "${author.email}" is blocked`,
135
+ blockedBy: 'email',
136
+ authorInfo: author,
137
+ };
138
+ }
139
+ for (const pattern of this.blockedEmailPatterns) {
140
+ if (pattern.test(author.email)) {
141
+ return {
142
+ allowed: false,
143
+ reason: `Author email "${author.email}" matches blocked pattern`,
144
+ blockedBy: 'email',
145
+ authorInfo: author,
146
+ };
147
+ }
148
+ }
149
+ // Check blocked email domains
150
+ for (const domain of this.blockedEmailDomains) {
151
+ if (normalizedEmail.endsWith(domain) || normalizedEmail.includes(`@${domain}`)) {
152
+ return {
153
+ allowed: false,
154
+ reason: `Author email domain "${domain}" is blocked`,
155
+ blockedBy: 'domain',
156
+ authorInfo: author,
157
+ };
158
+ }
159
+ }
160
+ // Check blocked regions
161
+ for (const [region, domains] of this.regionDomainMap.entries()) {
162
+ for (const domain of domains) {
163
+ if (normalizedEmail.endsWith(domain) ||
164
+ normalizedEmail.endsWith(`@${domain}`) ||
165
+ normalizedEmail.includes(`@${domain}`)) {
166
+ return {
167
+ allowed: false,
168
+ reason: `Author email from blocked region "${region.toUpperCase()}"`,
169
+ blockedBy: 'region',
170
+ authorInfo: author,
171
+ };
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return { allowed: true };
177
+ }
178
+ /**
179
+ * Get summary of configuration
180
+ */
181
+ getSummary() {
182
+ var _a;
183
+ return {
184
+ enabled: ((_a = this.config) === null || _a === void 0 ? void 0 : _a.enabled) || false,
185
+ blockedAuthors: this.blockedAuthors.size,
186
+ blockedAuthorPatterns: this.blockedAuthorPatterns.length,
187
+ blockedEmails: this.blockedEmails.size,
188
+ blockedEmailPatterns: this.blockedEmailPatterns.length,
189
+ blockedEmailDomains: this.blockedEmailDomains.size,
190
+ blockedRegions: this.regionDomainMap.size,
191
+ };
192
+ }
193
+ }
194
+ exports.AuthorChecker = AuthorChecker;
@@ -186,6 +186,7 @@ class MetricsCollector {
186
186
  cve_detected: 0,
187
187
  license_blocked: 0,
188
188
  package_too_new: 0,
189
+ author_blocked: 0,
189
190
  };
190
191
  this.metricsBuffer.forEach(metric => {
191
192
  summary[metric.event] = (summary[metric.event] || 0) + 1;
package/build/types.d.ts CHANGED
@@ -45,6 +45,17 @@ export interface PackageAgeConfig {
45
45
  minVersionAgeDays?: number;
46
46
  warnOnly?: boolean;
47
47
  }
48
+ export interface AuthorFilterConfig {
49
+ enabled: boolean;
50
+ blockedAuthors?: string[];
51
+ blockedAuthorPatterns?: string[];
52
+ blockedEmails?: string[];
53
+ blockedEmailPatterns?: string[];
54
+ blockedEmailDomains?: string[];
55
+ blockedRegions?: string[];
56
+ requireVerifiedEmail?: boolean;
57
+ warnOnly?: boolean;
58
+ }
48
59
  export interface SecurityConfig extends Config {
49
60
  blockedVersions?: string[];
50
61
  blockedPatterns?: string[];
@@ -61,6 +72,7 @@ export interface SecurityConfig extends Config {
61
72
  logger?: LoggerConfig;
62
73
  metrics?: MetricsConfig;
63
74
  packageAge?: PackageAgeConfig;
75
+ authorFilter?: AuthorFilterConfig;
64
76
  errorHandling?: {
65
77
  onFilterError?: 'fail-open' | 'fail-closed';
66
78
  onCveCheckError?: 'fail-open' | 'fail-closed';
@@ -111,9 +123,20 @@ export interface CVECheckResult {
111
123
  }
112
124
  export interface MetricsData {
113
125
  timestamp: string;
114
- event: 'block' | 'fallback' | 'publish_rejected' | 'cve_detected' | 'license_blocked' | 'package_too_new';
126
+ event: 'block' | 'fallback' | 'publish_rejected' | 'cve_detected' | 'license_blocked' | 'package_too_new' | 'author_blocked';
115
127
  packageName: string;
116
128
  version?: string;
117
129
  reason: string;
118
130
  metadata?: Record<string, any>;
119
131
  }
132
+ export interface AuthorInfo {
133
+ name?: string;
134
+ email?: string;
135
+ url?: string;
136
+ }
137
+ export interface AuthorCheckResult {
138
+ allowed: boolean;
139
+ reason?: string;
140
+ blockedBy?: 'name' | 'email' | 'domain' | 'region';
141
+ authorInfo?: AuthorInfo;
142
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verdaccio-security-filter",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Advanced security filter plugin for Verdaccio with version range blocking and fallback strategies",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",