Block vulnerable and freshly published NPM packages before they ever reach your builds using Varnish’s auto-generated OSV ruleset and a single Docker command.
This tutorial walks you through 3 steps:
npm commands.npm client is installed on your machine.Create a project folder and add a file called example-config.yaml:
varnish:
http:
- port: 80
virtual_registry:
registries:
- name: npmjs
default: true
enable_firewall: true
remotes:
- url: https://registry.npmjs.org
firewall:
address: localhost
default_action: allow
default_quarantine_days: 7
rulesets:
- git:
name: osv-npm
url: https://github.com/varnish/osv-rules.git
sub_path: rulesets/npm/all.yaml
license:
file: /app/license.lic
Two things power the firewall here:
rulesets.git pulls the auto-generated OSV ruleset from Varnish’s public repo, refreshed every hour. Every NPM vulnerability in the OSV database becomes a firewall rule, with no manual maintenance.default_quarantine_days: 7 hides any package version published in the last 7 days from latest resolution, giving you a buffer against newly-published malicious packages before they get a chance to spread.osv-rulegen Docker image to produce an equivalent OSV ruleset locally, then point a rulesets.path entry at the output. This is useful if you want to vet or pin the ruleset rather than fetching it over the network.
From the folder containing example-config.yaml, run:
docker run --rm -p 80:80 -p 6090:6090 --name orca \
-v $(pwd)/example-config.yaml:/app/config.yaml:ro \
-v $(pwd)/license.lic:/app/license.lic:ro \
varnish/orca --config /app/config.yaml
| Flag | Purpose |
|---|---|
-p 80:80 -p 6090:6090 |
Exposes virtual registry on port 80 and artifact firewall on port 6090 (default) |
-v $(pwd)/example-config.yaml:/app/config.yaml:ro |
Mounts your config file into the container (read-only) |
-v $(pwd)/license.lic:/app/license.lic:ro |
Mounts your license file to use with Orca Firewall |
--config /app/config.yaml |
Tells Orca which config file to use |
You should see startup logs confirming the firewall is enabled, then the OSV ruleset loading a few seconds later. The key lines to look for:
time=... level=INFO msg="License: Firewall enabled"
time=... level=INFO msg="Loading VCL Group into Varnish" name=npmjs
time=... level=INFO msg="Varnish is ready to receive traffic"
time=... level=INFO msg="Loaded Firewall ruleset" id=osv-npm rules=218717
time=... level=INFO msg="Rulesets loaded" default_action=allow rulesets=1 rules=218717
time=... level=INFO msg="Starting Firewall" address=localhost:6090
The rules=218717 figure is the full set of npm vulnerability rules from the OSV database, every npm advisory becomes a firewall rule. Your number may differ slightly since the ruleset is regenerated hourly.
In a new terminal, point npm at the firewall by setting it as the default registry:
export NPM_CONFIG_REGISTRY=http://localhost
This way you don’t need to append --registry=... to every npm command. The --dry-run flag means nothing actually downloads; it just resolves the request through the firewall. The --prefer-online forces npm to actually hit the registry path more reliably.
A package with no known vulnerabilities resolves normally:
npm --verbose pack minimist --dry-run --prefer-online
npm http fetch GET 200 http://localhost/minimist 104ms (cache updated)
npm http fetch GET 200 http://localhost/minimist/-/minimist-1.2.8.tgz 18ms (cache revalidated)
npm notice 📦 minimist@1.2.8
...
lodash has several known CVEs in older versions. When you ask for lodash without specifying a version, the firewall transparently steers you to the latest safe version:
npm --verbose pack lodash --dry-run --prefer-online
# Audit-Logs: {"purl":"pkg:npm/lodash@4.18.1","rule_id":"default","ruleset_id":"default","action":"allow","effective_action":"allow"}
The exact version may differ depending on what’s currently latest on npmjs.com/package/lodash; the firewall steers your unpinned install to the highest version not flagged by OSV. As long as the action is allow and the rule_id is default, the OSV ruleset is working as intended.
If you explicitly pin to a vulnerable version, the audit log shows it was matched by an OSV rule:
npm --verbose pack lodash@4.17.20 --dry-run --prefer-online
# Audit-Logs: {"purl":"pkg:npm/lodash@4.17.20","rule_id":"GHSA-29mw-wpgm-hmr9","ruleset_id":"osv-npm","action":"hide","effective_action":"hide"}
The rule_id is a real GHSA identifier, look up the CVE details on osv.dev. Every rule in the osv-npm ruleset links back to its origin advisory this way.
By default, OSV severity scores below the deny threshold map to hide: the version stays installable if pinned, but is excluded from latest. To turn hide into a hard 403 block, lower the deny threshold (see the Firewall Configuration Reference or add a custom action: deny rule, as described in Writing Custom Firewall Rules.
With default_quarantine_days: 7, any version published in the last 7 days is hidden from latest resolution. Developers running npm install <pkg> get the latest non-quarantined version automatically, protecting against typo-squatting and newly-published malicious packages that haven’t been flagged yet by OSV.
In the audit log, quarantined versions appear with effective_action: hide even when no explicit rule matches.