Because npm install shouldn’t feel like Russian roulette.
NoPackageMalware is a self‑hosted security gate in front of the public npm registry, built on Verdaccio. It combines:
- a strict fail‑closed security policy,
- LLM‑assisted malware detection,
- per‑org policies and budgets, and
- a ready‑to‑deploy Docker/Helm stack with high‑availability options.
It is designed for platform and security teams who want to keep using the public npm ecosystem without accepting blind npm install risk.
- Drop‑in npm proxy: front your developers’
npm,yarnorpnpmwith two Verdaccio endpoints (strict & lenient). - Fail‑closed by design: if the decision engine or workers are down, new or suspicious versions are not installable.
- Two‑stage enforcement:
- Manifest filter (
verdaccio-security-gate-filter) hides known BLOCKED / ERROR / PENDING versions and decorates risky ones withdeprecatedwarnings andsecuritymetadata. - Download guard (
verdaccio-download-guard-middleware) intercepts tarball downloads and calls/api/security/scan-downloadto allow/block each exact version.
- Manifest filter (
- Static heuristics run on the tarball (Node.js package contents):
- Detects install‑time hooks (
preinstall,install,postinstall,prepare) and other suspicious signals. - Produces an initial risk score and summary.
- Detects install‑time hooks (
- LLM‑based analysis (OpenAI GPT via
services/api/src/llm.ts& workers):- Enriches heuristics with a deeper semantic review (when enabled).
- Combines heuristics + LLM into a single final risk score, risk level, and flags.
- Configurable GPT usage:
- Bring‑your‑own OpenAI API key (via Kubernetes Secret or
.env). - GPT can be disabled for air‑gapped / cost‑sensitive setups (heuristics‑only mode).
- Bring‑your‑own OpenAI API key (via Kubernetes Secret or
- Org & token model stored in MongoDB (
services/api/src/db.ts):OrgDoc– organizations.TokenDoc– hashed tokens linked to orgs.SecurityPolicyDoc– named policies per org (strict,lenient, …).PolicyOverrideDoc– per‑package allow/block overrides.
- Token‑based auth for Verdaccio (
verdaccio-security-token-auth):npm/yarntokens are validated by/api/auth/token/validate.- Maps tokens to orgs and policies for downstream decisions and budgets.
- Fine‑grained policies per org:
- Risk thresholds (
warn_threshold,block_threshold). - Default action (
blockvswarn). - Optional scan budgets and GPT budgets.
- Risk thresholds (
- Daily scan budgets per org (
services/api/src/budget.ts+ Redis):- Limit how many new package versions an org can trigger per day.
- When exceeded, new versions are marked with
ERRORand a clear summary.
- Per‑token audit rate limiting (
/api/security/scan-download+verdaccio-download-guard-middleware):- 24‑hour and 7‑day sliding windows, stored in Redis.
- Prevents a single token from abusing the scanning infrastructure.
- GPT budgets per org/day (workers):
- Workers track how many GPT calls an org has used (
max_gpt_calls_per_day). - When exhausted, workers fall back to heuristics‑only and record that GPT was skipped.
- Workers track how many GPT calls an org has used (
- Two Verdaccio instances out of the box:
- Strict gate – aggressive thresholds, safest defaults.
- Lenient gate – more permissive, suitable for exploratory/dev installs.
- Policy is enforced via headers (
X-Security-Policy) and per‑org configuration, not hard‑coded at the plugin level. - Both instances share the same storage, Mongo and Redis, but differ in policy.
- Security reports dashboard (
services/app/src/App.tsx):- Filters by package name, status and risk level.
- Paginated list of reports with risk scores and summaries.
- Detail pane with human‑readable explanations and last scan time.
- Access‑admin dashboard & request‑access flow:
- Admin and request‑access SPAs are wired from the same frontend bundle.
- Access‑admin API exposes:
/api/access-requests(create access request),/api/admin/*(login, list/approve/reject access requests, manage tokens and rate limits),/request-accessredirect to the public request‑access portal.
- Runtime‑configurable URLs (
services/app/public/runtime-config.js):- Point dashboards and portals to any API/registry endpoints without rebuilding the app.
- Docker images (single
Dockerfile):no-package-malware-api– unified API + workers (rootlessUSER 1000).no-package-malware-app– SPA served via nginx‑unprivileged (USER 101).no-package-malware-registry– Verdaccio strict/lenient stack (USER 1001).
- One‑command Docker Compose stack:
secure-registry/infra/docker-compose.app.ymlruns Mongo, Redis, API, APP, strict & lenient registries.
- Kubernetes Helm chart with HA options (
charts/no-package-malware):- Optional in‑cluster MongoDB subchart (with replicaSet toggles).
- Optional in‑cluster Valkey (Redis‑compatible) subchart (standalone or sentinel).
ha.enabledto bump replicas for API, APP and Verdaccio:- e.g.
api=2,app=2,verdaccioStrict=2,verdaccioLenient=2.
- e.g.
- Ingress templates for API, dashboard and registry endpoints.
- Secrets and values for OpenAI, admin credentials and Mongo connectivity.
- Non‑root by default:
- All application containers declare an explicit numeric
USERfor compliance with restrictive clusters.
- All application containers declare an explicit numeric
- Health & readiness probes:
- API:
/health/live,/health/ready,/health. - Workers:
/health/live,/health/readyon a dedicated port.
- API:
- Malware fixtures & fake registry (
examples/malware-fixtures):- Benign packages, env exfiltration examples, install‑script malware, etc.
- Local “malware” Verdaccio instance for safe experimentation.
- End‑to‑end tests (
secure-registry/tests/e2e):- Cover GPT flows and malware reports scenarios.
- Example consumer app (
examples/consumer-app):- Minimal Node app that resolves dependencies via the strict registry.
- Shows how an existing project can plug into the gate using
.npmrc.
At a high level, the stack consists of:
- Verdaccio registries (strict, lenient, and a local malware playground):
- Front door for all npm/yarn/pnpm traffic.
- Apply security filters and middlewares.
- Unified security API (
services/api):- Security decision API (bulk report, scan‑download, reports listing).
- Access‑admin API and request‑access endpoints.
- Health endpoints and background worker startup.
- Security workers (
services/api/src/workers.ts):- Consume scan jobs from the
security-scanqueue. - Download and unpack tarballs.
- Run heuristics + optional LLM analysis.
- Update
security_reportsin MongoDB.
- Consume scan jobs from the
- Data stores:
- MongoDB – security reports, orgs, tokens, policies, overrides.
- Redis / Valkey – scan budgets, GPT budgets, per‑token audit rate‑limits, HTTP rate limiting, and job queues (via BullMQ).
- SPA:
- Dashboards for security reports and access‑admin.
- Request‑access UX for onboarding users/teams.
For more details, see:
- Architecture notes (conceptual):
- Malware fixtures & fake registry docs:
- A client (
npm,yarn,pnpm) installs from the strict or lenient Verdaccio endpoint. - Verdaccio fetches the package manifest (
GET /<name>) and passes it through theverdaccio-security-gate-filterplugin. - The filter calls
POST /api/security/bulk-report/:namewith the list of versions present in the manifest. - The API does not create scans here; it only:
- fetches existing reports from MongoDB,
- returns a version → report mapping.
- The filter:
- removes versions with status
BLOCKED,PENDING, orERROR, - keeps
APPROVEDandAPPROVED_WITH_WARNINGSversions, - adds a
deprecatedmessage andsecuritymetadata forAPPROVED_WITH_WARNINGS.
- removes versions with status
- Verdaccio responds to the client with a filtered manifest.
- When the client picks a version, Verdaccio serves the tarball route
(
GET /<name>/-/<name>-<version>.tgz). This request is intercepted byverdaccio-download-guard-middleware, which calls:POST /api/security/scan-download/:name/:version
- The
scan-downloadendpoint:- Enforces per‑org scan budgets and per‑token audit rate limits.
- Creates or updates a
security_reportsentry for the exact version (initiallyPENDINGwith a “triggered by download” summary if first‑seen). - Enqueues a
ScanJobinto the BullMQ queue when allowed. - Waits (blocking) for the report to move out of
PENDING, up to a configurable timeout.
- Workers pick up the job, download the tarball from the public registry,
run heuristics + optional GPT analysis, and update the report to one of:
APPROVED,APPROVED_WITH_WARNINGS,BLOCKED,- or
ERROR.
- Once the report is updated (or timeout hits), the
scan-downloadAPI returns the effective status, risk score and summary to the middleware. - The middleware either:
- lets the tarball download proceed (
APPROVEDorAPPROVED_WITH_WARNINGS), or - blocks the download with clear JSON explaining why (
BLOCKED,PENDING,ERROR).
- lets the tarball download proceed (
If the decision API or workers are unavailable, the middleware fails closed:
- tarball downloads are blocked with a 5xx JSON error,
- so “unknown” isn’t silently treated as “safe”.
The unified API models organizations and security policies in MongoDB
(via services/api/src/db.ts):
- Token validation (
POST /api/auth/token/validate):- Verdaccio auth plugin calls this endpoint with the raw npm
_authToken. - On success, the API responds with the org and token ids.
- Verdaccio auth plugin calls this endpoint with the raw npm
- Policy resolution:
- Respects
X-Security-Policyheaders (strict/lenient/ custom). - If a valid security token is present, resolves an org‑specific policy.
- Falls back to built‑in defaults when none are configured.
- Respects
- Risk → status mapping (
mapRiskToStatusForPolicy):- Threshold‑based mapping using the active
EffectivePolicy. - Produces the final status exposed to Verdaccio and the dashboard.
- Threshold‑based mapping using the active
- Scan budgets per org (decision API):
- Cap the number of new version scans an org can trigger per day.
- Exceeded budgets result in
ERRORreports with a clear summary.
- Audit rate limits per token (download guard):
- Use Redis counters keyed by token id.
- Separate limits for the current 24h window and last 7 days.
- GPT budgets per org/day (workers):
- Workers look up policies for the job’s org and enforce a maximum number of GPT calls per day.
- GPT failures can be configured to result in
ERRORto preserve fail‑closed behavior.
- Security dashboard (
services/app):- Calls
/api/security/reportsto list reports. - Lets you filter by package, status and risk level.
- Calls
- Access‑admin & request‑access:
- Expose public
/api/access-requestsand admin/api/admin/*endpoints. - Support a bootstrap default admin (
admin/admin) for local/dev use and Secrets for production. - Access‑admin redirects
/request-accessto a configurable SPA URL (set viaACCESS_REQUEST_PORTAL_URLor Helm values).
- Expose public
cp .env.example .env
# Edit .env to set at least OPENAI_API_KEY (or set GPT_ENABLED=false to disable GPT)
pnpm install
pnpm -r buildpnpm devThis will:
- Start MongoDB and Redis via
secure-registry/infra/docker-compose.yml. - Run the unified API (security decision + access‑admin + workers) in dev mode on
http://localhost:4000usingservices/api. - Start Verdaccio instances:
- Strict gate at
http://localhost:${VERDACCIO_STRICT_PORT:-4873}usingsecure-registry/verdaccio/strict/config.yaml. - Lenient gate at
http://localhost:${VERDACCIO_LENIENT_PORT:-4874}usingsecure-registry/verdaccio/lenient/config.yaml. - Local malware playground at
http://localhost:${VERDACCIO_MALWARE_PORT:-4875}usingsecure-registry/verdaccio/malware/config.yaml.
- Strict gate at
- Start the unified web app in
services/appwith Vite dev server onhttp://localhost:5174:http://localhost:5174/dashboard/security– security reports dashboardhttp://localhost:5174/dashboard/access-admin– access admin dashboardhttp://localhost:5174/request-access– public request‑access portal
You can also use the Taskfile if you’re on Devbox:
# from the project root
task devpnpm --filter @secure-registry/security-decision-api seedThis script:
- Ensures an org (default
demo-org) exists. - Creates or reuses a demo token.
- Upserts
strictandlenientpolicies with sensible defaults.
It logs the token you should use as your npm _authToken.
Strict instance:
npm set always-auth true
npm set registry http://localhost:4873/
npm set //localhost:4873/:_authToken=<your-token>Lenient instance:
npm set always-auth true
npm set registry http://localhost:4874/
npm set //localhost:4874/:_authToken=<your-token>From there you can:
- Observe
WARN deprecatedmessages forAPPROVED_WITH_WARNINGSversions. - See installs fail when a version is
BLOCKED,PENDINGorERROR. - Trigger scan budgets and rate‑limits and inspect the resulting reports.
For a production‑like single‑host setup you can use the provided multi‑stage Dockerfile and Docker Compose files.
From the repo root:
# Unified API (Node, rootless USER 1000)
docker build -t no-package-malware-api --target api-runtime .
# Unified SPA (nginx-unprivileged, rootless USER 101)
docker build -t no-package-malware-app --target app-runtime .
# Verdaccio registry (strict/lenient, rootless USER 1001)
docker build -t no-package-malware-registry --target registry-runtime .A single compose file defines the full stack (Mongo, Redis, API, APP, strict & lenient registries):
cd secure-registry/infra
# Start Mongo, Redis, unified API, unified SPA and strict/lenient registries
docker compose -f docker-compose.app.yml upAvailable endpoints:
- Unified API:
http://localhost:4000 - Unified SPA:
http://localhost:4173 - Verdaccio strict gate:
http://localhost:4873 - Verdaccio lenient gate:
http://localhost:4874
All application containers are rootless and declare an explicit numeric USER so Kubernetes and platform policies can enforce non‑root execution.
The charts/no-package-malware chart packages the full stack for Kubernetes.
cd charts/no-package-malware
helm dependency update
helm install npm-gate .You’ll typically also:
- Create a Secret with your OpenAI API key and set
openai.apiKeySecret.secretName. - Configure MongoDB connection (
mongo.uri) or enable the in‑cluster MongoDB subchart. - Configure ingress hosts for API, app and registries.
In your values file:
ha:
enabled: true
mongodb:
enabled: true
replicaSet:
enabled: true
secondaries: 2
valkeyha:
enabled: true
architecture: standaloneThis will:
- Run multiple replicas of the API, APP, and strict/lenient registries.
- Deploy MongoDB and Valkey in‑cluster (or let you point to existing services).
- Let you wire readiness probes to your service mesh / ingress controller.
See charts/no-package-malware/values.yaml for all available knobs.
An example consumer app is provided in examples/consumer-app.
- Start the secure registry stack as described above.
- Seed at least one org, token and policies.
- Update
examples/consumer-app/.npmrcwith your strict registry URL and token. - Run:
cd examples/consumer-app
npm install
npm startThe app performs a simple require('left-pad') via the secure registry.
Depending on the selected version’s status under your policy, installs will
succeed, warn, or fail.
Apache‑2.0 (see package.json).