Registry proxy
Install-time protection using the security-scanning npm registry proxy.
Quick config generator
Generates copy/paste snippets for client config + proxy policy mode.
.npmrc
registry=https://registry.malwarescanner.com/
Server config
Uses default port from the URL when present.
import { startProxyServer } from '@/lib/npm-proxy';
await startProxyServer({
port: 443,
policy: { mode: 'warn' },
});
npm Registry Proxy
Security-scanning npm registry proxy that performs real-time malware detection before serving packages.
Quick Start
Configure your npm/yarn/pnpm client to use the registry proxy:
# npm
npm config set registry https://malwarescanner.live/api/npm/
# Or create/edit .npmrc in your project
echo "registry=https://malwarescanner.live/api/npm/" >> .npmrc
# Yarn v2+ (.yarnrc.yml)
npmRegistryServer: "https://malwarescanner.live/api/npm/"
# pnpm (.npmrc)
registry=https://malwarescanner.live/api/npm/
That's it! All package installations will now be scanned for malware before delivery.
Public Instance: The live proxy at
malwarescanner.livescans every package in real-time. Critical threats are blocked with HTTP 403. Warnings pass through withX-Malwarescanner-Warningheaders.
Features
- Real-time scanning: Every package tarball is scanned before serving
- Policy modes: Choose between
strict,warn, orauditmodes - Auto-patching: Known malicious packages can be transparently replaced with patched versions
- Caching: Scan results are cached for fast repeat installs
- Blocking: Malicious packages return HTTP 403 with detailed threat information
Architecture
Overview
┌─────────────────┐
│ npm/yarn/pnpm │
│ Client │
└────────┬────────┘
│ .npmrc points to proxy
▼
┌────────────────────────────────────────────────────────────┐
│ Security Scanning Proxy (Port 8080) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Next.js API Routes (/api/registry/*) │ │
│ └──────────┬──────────────────────────────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────┐ ┌──────────────────┐ │
│ │ Scan Orchestrator │◄────────►│ Redis Cache │ │
│ └──────────┬───────────┘ └──────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ MalwareDetectionService (existing) │ │
│ └──────────────────────────────────────────────────────┘ │
└───────────────────────────┬────────────────────────────────┘
│
▼
┌───────────────────────┐
│ registry.npmjs.org │
└───────────────────────┘
Endpoints to Intercept
| Endpoint | Description | Action |
|---|---|---|
GET /{package} | Package metadata | Cache, optional scan trigger |
GET /{package}/{version} | Version metadata | Cache |
GET /{package}/-/{tarball}.tgz | Tarball download | Scan before serving |
GET /-/v1/search | Search | Pass-through |
Caching Strategy
Redis Cache Schema
const CACHE_KEYS = {
METADATA_FULL: 'npm:metadata:full:{package}',
METADATA_ABBREV: 'npm:metadata:abbrev:{package}',
SCAN_RESULT: 'npm:scan:{package}@{version}',
TARBALL: 'npm:tarball:{package}@{version}',
SCAN_LOCK: 'npm:scan:lock:{package}@{version}',
POLICY_DECISION: 'npm:policy:{package}@{version}',
};
TTL Strategy
| Cache Type | TTL | Rationale |
|---|---|---|
| Metadata | 2 min | npm standard, frequent updates |
| Scan Results | 30 days | Package versions are immutable |
| Tarballs | 30 days | Immutable content |
| Scan Locks | 5 min | Prevent stuck locks |
Policy Modes
type ProxyMode = 'strict' | 'warn' | 'audit';
const POLICY = {
strict: {
CLEAN: 'allow',
LOW: 'allow',
MEDIUM: 'block',
HIGH: 'block',
CRITICAL: 'block',
},
warn: {
CLEAN: 'allow',
LOW: 'allow_warn',
MEDIUM: 'allow_warn',
HIGH: 'block',
CRITICAL: 'block',
},
audit: {
CLEAN: 'allow',
LOW: 'allow',
MEDIUM: 'allow',
HIGH: 'allow_warn',
CRITICAL: 'block',
},
};
Request Flow
Tarball Request (Most Important)
Client → GET /lodash/-/lodash-4.17.21.tgz
│
▼
Check scan cache (scan:{pkg}@{ver})
│
┌──────┴──────┐
│ HIT │ MISS
▼ ▼
Apply Acquire lock
Policy Download → Scan → Cache
│ │
└──────┬──────┘
│
┌──────┴──────┐
│ ALLOW │ BLOCK
▼ ▼
Return HTTP 403
tarball with details
Block Response
{
"error": {
"code": "SECURITY_BLOCKED",
"message": "Package blocked due to security threats",
"package": "evil-pkg",
"version": "1.0.0",
"threatLevel": "HIGH",
"threatScore": 85,
"alerts": [...]
}
}
Client Configuration
npm
# .npmrc
registry=http://localhost:8080/
yarn v2+
# .yarnrc.yml
npmRegistryServer: "http://localhost:8080/"
pnpm
# .npmrc
registry=http://localhost:8080/
File Structure
src/
├── services/
│ └── proxy-orchestrator/
│ ├── index.ts # Main orchestrator
│ ├── policy.ts # Policy decision logic
│ └── cache-manager.ts # Redis cache abstraction
app/
└── api/
└── registry/
├── route.ts # Root endpoint
└── [package]/
├── route.ts # Metadata
└── -/
└── [tarball]/
└── route.ts # Tarball download
Integration with Existing Code
The proxy leverages existing infrastructure:
- MalwareDetectionService (
src/services/malware-detection/) - All scanning logic - registry-client.ts (
src/lib/npm/) - Upstream registry communication - package-fetcher.ts (
src/lib/npm/) - Tarball download with checksum verification - Redis pub/sub (
src/lib/redis/) - Existing Redis connection
Migration Path
- Week 1-2: Audit Mode - Log only, no blocking
- Week 3-4: Warn Mode - Block HIGH/CRITICAL, warn on MEDIUM
- Week 5+: Strict Mode - Block MEDIUM+
Performance
| Scenario | Latency |
|---|---|
| Cache hit (metadata + tarball) | <100ms |
| Cache hit (metadata), scan miss | 5-10s |
| Full cache miss | 10-30s |
Memory Sizing
For 10,000 cached packages:
- Abbreviated metadata: ~500MB
- Scan results: ~250MB
- Tarballs (top 500): ~100MB
- Total: ~850MB
Recommendation: 2-4GB Redis instance with LRU eviction.