Advanced security filter plugin for Verdaccio with dual-layer protection architecture combining middleware interception and metadata filtering for comprehensive package security.
This plugin implements two independent security layers for complete protection:
- ✅ Whitelist/Blacklist filtering - Control allowed packages
- ✅ Pattern-based blocking - Block by regex patterns
- ✅ Scope control - Filter by npm scopes
- ✅ Tarball interception - Block downloads even if metadata cached
- ✅ Version blocking - Block specific versions/ranges
- ✅ CVE vulnerability scanning - OSV API integration
- ✅ License compliance - SPDX validation
- ✅ Package age verification - Block newly created packages
- ✅ Full metadata access - Deep package inspection
Why two layers? Middleware catches everything at HTTP level, while filter_metadata provides deep inspection when metadata is available. See DUAL-LAYER-ARCHITECTURE.md for details.
- Version Range Blocking - Block vulnerable versions using semver ranges
- Block Strategy: Completely remove versions from registry
- Fallback Strategy: Transparently redirect to safe versions
- Exact Version Blocking - Block specific package@version combinations
- Semver Support - Full semver syntax (
^,~,>,>=,<,<=,x,*)
- OSV Database Integration - Automatic vulnerability scanning via OSV API
- Severity Filtering - Filter by severity:
low,medium,high,critical - Auto-Block Vulnerable Packages - Automatically block packages with known CVEs
- Caching System - Configurable cache with update intervals
- Multiple Database Support - Ready for OSV, Snyk, GitHub Advisory
- License Filtering - Enforce license policies with allowed/blocked lists
- SPDX Expression Support - Parse complex license expressions (OR/AND operators)
- Require License - Option to block packages without license information
- Pre-defined Lists - Common open-source and copyleft license collections
- Whitelist Mode - Only explicitly approved packages allowed
- Pattern-based Whitelisting - Regex patterns for package approval
- Version Constraints - Lock approved packages to specific version ranges
- Scope Control - Whitelist/blacklist package scopes (@scope/package)
- Enhanced Logging - Configurable log levels (
debug,info,warn,error) - Metrics Collection - Track security events (blocks, fallbacks, CVEs, license violations)
- Security Audit Trail - Detailed logging of all security decisions
- Customizable Output - Log to stdout or file in JSON format
- Pattern-based Blocking - Block suspicious packages by name patterns (regex)
- Package Age Filtering - Block packages or versions that are too new (protect against newly published malicious packages)
- Minimum Package Age - Require packages to exist for a minimum number of days
- Minimum Version Age - Require specific versions to exist for a minimum number of days
- Warn-Only Mode - Log warnings without blocking
- Author Filtering - Block packages based on author information
- Block by Author Name - Block specific author names or patterns
- Block by Email - Block specific email addresses or patterns
- Block by Email Domain - Block entire email domains (e.g.,
.ru,yandex.ru) - Block by Region - Block authors from specific regions based on email domains
- Maintainer & Contributor Checking - Validates all package maintainers and contributors
- Warn-Only Mode - Log warnings without blocking
- Middleware Interception - HTTP middleware that blocks tarball downloads for blocked packages
- Prevents blocked packages from being installed as dependencies
- Intercepts requests to
/:package/-/:filename.tgzroutes - Returns 403 Forbidden with detailed error messages
- Size Limits - Enforce minimum and maximum package sizes
- Metadata Validation - Verify package integrity and detect dangerous characters
- Checksum Enforcement - Ensure package integrity
- Fail-Safe/Fail-Closed Configuration - Choose error handling strategy
- Fail-Open (Default) - Allow packages on errors (availability priority)
- Fail-Closed - Block packages on errors (security priority)
- Granular Control - Different strategies for filter, CVE, and license checks
- Retry Logic - Automatic retry with exponential backoff for API calls
- 3 retries with increasing delays (1s, 2s, 4s)
- Rate limiting detection and handling
- Request timeout protection (10s)
- Resilient CVE Scanning - Production-ready vulnerability checking
- Network failure recovery
- API timeout protection
- Graceful degradation on errors
- Dependency Depth Limiting - Limit dependency tree depth and total count
- Circular Dependency Detection - Block packages with circular dependencies
- Rate Limiting - Detect and block suspicious download patterns
- Typosquatting Detection - AI-powered detection of similar package names
- Package Signing Verification - PGP signature validation
- Custom Validation Scripts - Execute custom security checks
- Integration with Security Scanners - npm audit, Snyk API, Sonatype
- Webhook Notifications - Real-time alerts for security events
- Web Dashboard - Visual interface for security analytics
- Email/Slack Notifications - Alert channels for critical events
- Audit Reports - Generate compliance reports (PDF, CSV, JSON)
- Multi-Registry Support - Fallback to other registries
- ML-based Anomaly Detection - Machine learning for threat detection
- Dry Run Mode - Test rules without actually blocking
- Auto-approve Criteria - Automatic approval based on npm stats (downloads, stars)
- CLI Tool - Command-line interface for rule management
- Visual Studio Code Extension - IDE integration
- GitHub Action - CI/CD integration
- Node.js >= 22.0.0
- Verdaccio >= 5.0.0
npm install -g verdaccio# Install from npm (when published)
npm install -g verdaccio-security-filter
# Or install locally for development
git clone https://github.com/ponomarenko/verdaccio-security-filter.git
cd verdaccio-security-filter
npm install
npm run build
npm linkThis plugin implements two layers of protection to block malicious packages:
When a client requests package information (e.g., npm info lodash), the plugin:
- Checks if the package is allowed (whitelist mode)
- Validates against blocked patterns, scopes, and age requirements
- Throws HTTP 404 error for completely blocked packages (patterns, scopes, whitelist violations)
- Filters out specific blocked versions from the versions list for partial blocks
Result: Blocked packages return "404 Not Found" - as if they don't exist in the registry.
When a client tries to download a tarball file (e.g., during npm install), the plugin:
- Intercepts all HTTP requests early in the middleware chain
- Extracts package name and version from the request URL
- Applies all security checks (whitelist, patterns, scopes, blocked versions, range rules)
- Returns 403 Forbidden for blocked packages with detailed error message
Result: Even if metadata is cached, tarball downloads are blocked at the HTTP level.
- Metadata filtering (404) - Makes blocked packages invisible to npm/yarn
- Middleware interception (403) - Blocks tarball downloads even with cached metadata
- Together, they provide complete protection against blocked packages being installed in any way
Example flow when a blocked package is requested:
npm install hawk
→ GET /hawk (metadata request)
→ [Security Filter] Blocked hawk@* - Package is not in whitelist
→ HTTP 404 Not Found: Package blocked by security filter: Not in whitelist
→ npm ERR! 404 Not Found - GET http://localhost:4873/hawk
npm install (with hawk as dependency)
→ GET /hawk/-/hawk-9.0.1.tgz (tarball request)
→ [Middleware] Tarball request: hawk/hawk-9.0.1.tgz
→ [Security Filter] Blocked [email protected] - Package is not in whitelist
→ HTTP 403 Forbidden: {"error": "Package blocked by security filter", "reason": "Package is not in whitelist"}
Minimal configuration using only Layer 1 (Middleware):
middlewares:
security-filter:
enabled: true
mode: whitelist
whitelist:
packages:
- lodash
- express
patterns:
- "^@types/.*"Recommended: Use both layers for maximum security:
# Enable filter_metadata (Layer 2)
packages:
'@*/*':
access: $all
publish: $authenticated
proxy: npmjs
storage: security-filter # Enable Layer 2!
'**':
access: $all
publish: $authenticated
proxy: npmjs
storage: security-filter # Enable Layer 2!
# Configure security filter
middlewares:
security-filter:
enabled: true
# Layer 1: Basic filtering (Middleware)
mode: whitelist
whitelist:
packages: [lodash, express]
patterns: ["^@types/.*"]
blockedVersions:
- "[email protected]"
# Layer 2: Deep inspection (filter_metadata)
cveCheck:
enabled: true
autoBlock: true
severity: [critical, high]
licenses:
allowed: [MIT, Apache-2.0, BSD-3-Clause]
packageAge:
enabled: true
minPackageAgeDays: 7# Layer 1: Metadata filtering
filters:
security-filter:
enabled: true
# CVE vulnerability scanning
cveCheck:
enabled: true
databases:
- osv
- github
severity:
- high
- critical
autoBlock: true
updateInterval: 24 # hours
cacheDir: ./.security-cache
# License compliance
licenses:
allowed:
- MIT
- Apache-2.0
- BSD-3-Clause
blocked:
- GPL-3.0
- AGPL-3.0
requireLicense: true
# Package age filtering
packageAge:
enabled: true
minPackageAgeDays: 7 # Packages must be at least 7 days old
minVersionAgeDays: 3 # Versions must be at least 3 days old
warnOnly: false # Block instead of just warning
# Version range rules
versionRangeRules:
- package: lodash
range: "<4.17.21"
strategy: fallback
fallbackVersion: "4.17.21"
reason: "CVE-2021-23337: Command injection"
- package: minimist
range: "<1.2.6"
strategy: block
reason: "CVE-2021-44906: Prototype pollution"
# Layer 2: Middleware interception
middlewares:
security-filter:
enabled: true# Layer 1: Metadata filtering
filters:
security-filter:
enabled: true
# Only approved packages allowed
mode: whitelist
whitelist:
packages:
- lodash
- express
- react
patterns:
- "^@mycompany/.*"
- "^@types/.*"
versions:
lodash: "^4.17.21"
express: "^4.18.0"
# Enhanced logging
logger:
level: info
enabled: true
includeTimestamp: true
# Metrics for analytics
metrics:
enabled: true
output: file
filePath: ./security-metrics.json
# Layer 2: Middleware interception
middlewares:
security-filter:
enabled: true# Layer 1: Metadata filtering
filters:
security-filter:
enabled: true
mode: whitelist
whitelist:
packages:
- lodash
- axios
versions:
lodash: "4.17.21" # Lock to exact version
axios: "1.6.0"
cveCheck:
enabled: true
databases: [osv, github, snyk]
severity: [low, medium, high, critical] # Block ALL
autoBlock: true
updateInterval: 6 # Check every 6 hours
licenses:
allowed: [MIT, Apache-2.0, BSD-3-Clause]
blocked: [GPL-3.0, AGPL-3.0, LGPL-3.0]
requireLicense: true
minPackageSize: 10000 # 10KB minimum
maxPackageSize: 10485760 # 10MB maximum
logger:
level: debug # Log everything
enabled: true
includeTimestamp: true
metrics:
enabled: true
output: file
filePath: /var/log/verdaccio/security-metrics.jsonl
# Layer 2: Middleware interception
middlewares:
security-filter:
enabled: true| Option | Type | Default | Description |
|---|---|---|---|
mode |
string |
blacklist |
Filter mode: blacklist or whitelist |
blockedVersions |
string[] |
[] |
List of package@version to block |
blockedPatterns |
string[] |
[] |
Regex patterns for package names |
allowedScopes |
string[] |
[] |
Allowed package scopes |
blockedScopes |
string[] |
[] |
Blocked package scopes |
minPackageSize |
number |
0 |
Minimum package size in bytes |
maxPackageSize |
number |
104857600 |
Maximum package size (100MB) |
enforceChecksum |
boolean |
true |
Enforce checksum validation |
| Option | Type | Default | Description |
|---|---|---|---|
cveCheck.enabled |
boolean |
false |
Enable CVE scanning |
cveCheck.databases |
string[] |
['osv'] |
Databases: osv, snyk, github |
cveCheck.severity |
string[] |
['high', 'critical'] |
Severity levels to check |
cveCheck.autoBlock |
boolean |
false |
Auto-block vulnerable packages |
cveCheck.updateInterval |
number |
24 |
Cache update interval (hours) |
cveCheck.cacheDir |
string |
./.security-cache |
Cache directory path |
| Option | Type | Default | Description |
|---|---|---|---|
licenses.allowed |
string[] |
[] |
Allowed license list |
licenses.blocked |
string[] |
[] |
Blocked license list |
licenses.requireLicense |
boolean |
false |
Require license field |
| Option | Type | Default | Description |
|---|---|---|---|
whitelist.packages |
string[] |
[] |
Approved package names |
whitelist.patterns |
string[] |
[] |
Regex patterns for approval |
whitelist.versions |
object |
{} |
Version constraints per package |
| Option | Type | Default | Description |
|---|---|---|---|
logger.level |
string |
info |
Log level: debug, info, warn, error |
logger.enabled |
boolean |
true |
Enable logging |
logger.includeTimestamp |
boolean |
false |
Include timestamps in logs |
| Option | Type | Default | Description |
|---|---|---|---|
metrics.enabled |
boolean |
false |
Enable metrics collection |
metrics.output |
string |
stdout |
Output: stdout or file |
metrics.filePath |
string |
./security-metrics.json |
Metrics file path |
| Option | Type | Default | Description |
|---|---|---|---|
packageAge.enabled |
boolean |
false |
Enable package age filtering |
packageAge.minPackageAgeDays |
number |
0 |
Minimum age for packages (days) |
packageAge.minVersionAgeDays |
number |
undefined |
Minimum age for versions (days) |
packageAge.warnOnly |
boolean |
false |
Only warn, don't block |
| Option | Type | Default | Description |
|---|---|---|---|
authorFilter.enabled |
boolean |
false |
Enable author filtering |
authorFilter.blockedAuthors |
string[] |
[] |
List of author names to block |
authorFilter.blockedAuthorPatterns |
string[] |
[] |
Regex patterns for author names |
authorFilter.blockedEmails |
string[] |
[] |
List of email addresses to block |
authorFilter.blockedEmailPatterns |
string[] |
[] |
Regex patterns for author emails |
authorFilter.blockedEmailDomains |
string[] |
[] |
Email domains to block (e.g., .ru, yandex.ru) |
authorFilter.blockedRegions |
string[] |
[] |
Region codes to block (ru, cn, by, kp, ir, sy, cu, sd) |
authorFilter.requireVerifiedEmail |
boolean |
false |
Require author email information |
authorFilter.warnOnly |
boolean |
false |
Only warn, don't block |
| Option | Type | Default | Description |
|---|---|---|---|
errorHandling.onFilterError |
'fail-open' | 'fail-closed' |
'fail-open' |
Strategy when filter errors occur |
errorHandling.onCveCheckError |
'fail-open' | 'fail-closed' |
'fail-open' |
Strategy when CVE check fails |
errorHandling.onLicenseCheckError |
'fail-open' | 'fail-closed' |
'fail-open' |
Strategy when license check fails |
Error Handling Strategies:
- fail-open (Default): Allow packages through on errors (prioritizes availability)
- fail-closed: Block packages on errors (prioritizes security)
Example Configuration:
# Production: Security-first approach
errorHandling:
onFilterError: fail-closed
onCveCheckError: fail-closed
onLicenseCheckError: fail-closed
# Development: Availability-first approach
errorHandling:
onFilterError: fail-open
onCveCheckError: fail-open
onLicenseCheckError: fail-open
# Balanced: Block only on license failures
errorHandling:
onFilterError: fail-open
onCveCheckError: fail-open
onLicenseCheckError: fail-closed{
package: string; // Package name
range: string; // Semver range
strategy: 'block' | 'fallback';
fallbackVersion?: string; // Required for fallback strategy
reason?: string; // Explanation for blocking
}See the examples directory for complete configuration examples:
- basic.yaml - Simple setup for small teams
- enterprise.yaml - Comprehensive security setup
- high-security.yaml - Maximum security configuration
# Run all tests
npm test
# Run tests with coverage
npm test:coverage
# Run tests in watch mode
npm test:watchTest Results:
- ✅ 108 tests passing
- ✅ 60%+ code coverage
- ✅ SecurityFilterPlugin: 21 tests
- ✅ PackageAgeChecker: 13 tests
- ✅ SecurityLogger: 16 tests
- ✅ LicenseChecker: 15 tests
- ✅ WhitelistChecker: 15 tests
- ✅ AuthorChecker: 28 tests
# Install dependencies
npm install
# Build
npm run build
# Lint
npm run lint
# Watch mode for development
npm run build -- --watchWhen metrics are enabled, the plugin tracks:
block- Packages blocked by version/pattern/scope rulesfallback- Versions redirected to safe alternativespublish_rejected- Publish attempts rejectedcve_detected- CVE vulnerabilities foundlicense_blocked- Packages blocked by license rulespackage_too_new- Packages/versions blocked due to age restrictionsauthor_blocked- Packages blocked due to author/region filtering
Example metrics output:
{
"timestamp": "2025-01-15T10:30:00.000Z",
"event": "cve_detected",
"packageName": "lodash",
"version": "4.17.20",
"reason": "CVE-2021-23337 (high)",
"metadata": {
"cveId": "CVE-2021-23337",
"severity": "high"
}
}versionRangeRules:
- package: lodash
range: ">=4.17.0 <4.17.21"
strategy: block
reason: "CVE-2021-23337: Command injection vulnerability"versionRangeRules:
- package: axios
range: "0.21.1"
strategy: fallback
fallbackVersion: "0.21.4"
reason: "SSRF vulnerability fix"licenses:
allowed:
- MIT
- Apache-2.0
- BSD-3-Clause
blocked:
- GPL-3.0
- AGPL-3.0
requireLicense: truemode: whitelist
whitelist:
packages:
- lodash
- express
patterns:
- "^@mycompany/.*"
versions:
lodash: "^4.17.21"cveCheck:
enabled: true
databases: [osv, github]
severity: [high, critical]
autoBlock: true
updateInterval: 12# Protect against newly published malicious packages
packageAge:
enabled: true
minPackageAgeDays: 7 # Package must exist for 7 days
minVersionAgeDays: 3 # Version must exist for 3 days
warnOnly: false # Block, don't just warn# Block packages from specific authors, emails, or regions
authorFilter:
enabled: true
# Block specific author names
blockedAuthors:
- "Suspicious Author"
- "Known Bad Actor"
# Block author names matching patterns
blockedAuthorPatterns:
- "^Bot.*"
- ".*Spammer$"
# Block specific email addresses
blockedEmails:
- "[email protected]"
# Block email patterns
blockedEmailPatterns:
- ".*@temporary-mail\\.com$"
# Block entire email domains
blockedEmailDomains:
- ".ru" # All .ru domains
- "yandex.ru" # Specific domain
- "mail.ru"
# Block by region (includes multiple common domains)
blockedRegions:
- "ru" # Russia (.ru, yandex.ru, mail.ru, etc.)
- "cn" # China (.cn, qq.com, 163.com, etc.)
- "by" # Belarus
- "kp" # North Korea
# Require author information
requireVerifiedEmail: true
# Warn-only mode (don't block, just log)
warnOnly: false- Enable CVE Scanning - Automatically detect and block vulnerable packages
- Use Whitelist Mode - For maximum security in sensitive environments
- Enforce License Compliance - Prevent legal issues with license filtering
- Enable Package Age Filtering - Block newly published packages to prevent supply chain attacks
- Enable Author Filtering - Block packages from untrusted authors or regions
- Enable Metrics - Track security events for audit and compliance
- Regular Updates - Keep the plugin and CVE database cache updated
- Test Rules - Use dry run mode (planned) before applying strict rules
- Monitor Logs - Review security logs regularly for suspicious activity
- Verify Verdaccio version >= 5.0.0
- Check plugin is listed in
config.yamlunderfilters - Ensure plugin is installed globally or linked correctly
- Check internet connectivity to OSV API (https://api.osv.dev)
- Verify
cveCheck.enabledistrue - Check cache directory permissions
- Review logs for API errors
- Ensure Node.js >= 22.0.0
- Run
npm installto update dependencies - Clear Jest cache:
npx jest --clearCache
- Check logs for the reason:
logger.level: debug - Review all active rules (patterns, scopes, CVE, license)
- In whitelist mode, ensure package is explicitly approved
MIT © Vitaliy Ponomarenko
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Run tests (
npm test) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Issues: GitHub Issues
- Documentation: README
- Examples: Configuration Examples
- Verdaccio - The awesome private npm registry
- OSV - Open Source Vulnerabilities database
- All contributors and users of this plugin
Made with ❤️ for the npm security community