DocsRegistry proxy

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.live scans every package in real-time. Critical threats are blocked with HTTP 403. Warnings pass through with X-Malwarescanner-Warning headers.

Features

  • Real-time scanning: Every package tarball is scanned before serving
  • Policy modes: Choose between strict, warn, or audit modes
  • 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

EndpointDescriptionAction
GET /{package}Package metadataCache, optional scan trigger
GET /{package}/{version}Version metadataCache
GET /{package}/-/{tarball}.tgzTarball downloadScan before serving
GET /-/v1/searchSearchPass-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 TypeTTLRationale
Metadata2 minnpm standard, frequent updates
Scan Results30 daysPackage versions are immutable
Tarballs30 daysImmutable content
Scan Locks5 minPrevent 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

  1. Week 1-2: Audit Mode - Log only, no blocking
  2. Week 3-4: Warn Mode - Block HIGH/CRITICAL, warn on MEDIUM
  3. Week 5+: Strict Mode - Block MEDIUM+

Performance

ScenarioLatency
Cache hit (metadata + tarball)<100ms
Cache hit (metadata), scan miss5-10s
Full cache miss10-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.