Skip to content
Français
Account: not signed in, sync available My learning
Select theme
Français

Varnish Artifact Firewall: controlling dependency intake

By updated

Improve this page

An artifact firewall is a proxy that makes a policy decision (allow, hide, refuse) in front of every npm install or pip install, before the package enters your software factory. A cache such as Nexus serves whatever it is asked for; the firewall decides what has the right to come in. This lesson is for security and platform teams who already run an artifact manager and want to move from caching to control.

The example is Varnish Artifact Firewall, the Premium component of Varnish Orca, in front of Nexus. Everything is checked on 1 October 2026 against Orca 0.18.1 (firewall 0.6.0): the lesson's configuration passes the validator, but the public image carries no licence add-on (addons=[]), so the firewall enforces nothing without a trial licence. The lesson says so wherever it changes what you can observe.

What you will learn

  • Distinguish cache, artifact firewall, SCA analysis and admission control.
  • Write allow, hide and deny rules, and tune the quarantine of recent publications.
  • Set up an Orca and Nexus lab, and know what it shows without a licence.
  • Block a vulnerable version and a spoofed internal name.
  • Close bypasses, on CI runners as on workstations.
  • Produce the evidence for SOCLE-INT-GEN-3 (in French), OSS consumption through a controlled internal proxy, without unfiltered direct pulls.

Why a cache is not enough

Your developers run dozens of npm install and pip install every day. Each one downloads third-party code, often hundreds of transitive packages, and runs it on workstations and CI runners. In September 2025, the Shai-Hulud worm compromised more than 500 npm packages according to the CISA alert of 23 September: its postinstall script collected the tokens and credentials present in the environment, then republished itself with the npm tokens it found.

An artifact manager such as Nexus (in French) or a cache such as Varnish Orca (in French) solves two real problems: speed and resilience against an outage of the public registry. But it serves whatever it is asked for. If a developer installs a vulnerable version, a package with a name spoofed through dependency confusion, or a version published two hours ago by a compromised account, the cache downloads it, stores it and redistributes it to the whole team.

What is an artifact firewall?

An artifact firewall sits between your clients (npm, pip, CI runners) and your repositories, and evaluates every resolution against a set of rules. At Varnish, a rule leads to one of three actions, and a global quarantine comes on top, according to the firewall configuration documentation:

DecisionEffectUse case
allowServes the artifactExplicit exception, trusted package
hideRemoves the version from "latest version" resolutionEvict a version without breaking an existing lockfile
denyRefuses with a 403 responseForbidden package, vulnerable version
quarantine (default_quarantine_days)Blocks any version published less than N days ago, whatever the rulesSafety window on fresh publications

Quarantine is therefore not a rule action: it is a global setting, which directly answers the framework's cooldown before adoption requirement, SOCLE-INT-OSS-1 (in French). A booby-trapped version published this morning, like those of Shai-Hulud, is held back while the community spots it.

The benefit over a scanner is the moment of the decision: before ingestion. A refused dependency never enters the cache or the build. Where a scanned SBOM tells you after the fact "you installed a vulnerable component", the firewall stops the component from arriving.

Architecture of the study lab

The lab chains three components. The firewall is transparent: clients target Orca's virtual registry, and enable_firewall: true routes that registry through the firewall internally. The diagram follows a resolution:

Resolving a dependency: the npm or pip client queries the Orca virtual registry, the firewall decides allow, hide or deny; a refusal returns a 403 error to the client, otherwise the request reaches the Nexus group, which serves from its internal hosted repository or its proxy to npmjs.org and pypi.org.npm or pip clientOrca virtual registryFirewallallow, hide, deny403 responseNexus groupHosted repositoryinternal packagesPublic proxynpmjs, PyPI
Source · SVG
Esc to close, + and − to zoom, scroll to move around. 100 %

Nexus's role is to centralise: a proxy repository to the public registry, a hosted repository for your internal packages, and a group that exposes a single URL. The order of the group members (hosted before proxy) already forms a first barrier against dependency confusion; the firewall adds the per-rule decision on top.

In front of Nexus, but also in front of Pulp

The firewall is not tied to Nexus. The Varnish documentation describes Orca as an agnostic virtual registry manager: each virtual registry has a remotes.url backend, and Orca forwards the client path as is to that backend. Nothing therefore prevents pointing that backend at another manager, such as Pulp (in French), the open source artifact manager.

This setup with Pulp was checked in a lab prior to this lesson: the virtual registry loads, the PyPI index and the download of artifacts go through Orca. Two points of attention came out of it:

  • The most direct setup targets the container registry: Pulp exposes its OCI API on /v2/ at the root, like the public registry Orca already knows how to cache.
  • For PyPI, Pulp emits absolute download links through its CONTENT_ORIGIN setting; Orca rewraps them in its /redirectz endpoint, and CONTENT_ORIGIN must match the URL exposed by Orca.

Putting Orca in front of Pulp is interesting because Pulp has no native quarantine: the firewall brings the missing decision layer, on entirely open source storage.

Setting up the lab

The lab fits in a docker-compose.yml (Nexus and Orca) and a few configuration files. Images are pinned by digest; versions are those of 1 October 2026.

  1. Declare Nexus and Orca

    docker-compose.yml
    services:
    nexus:
    image: sonatype/nexus3:3.96.4@sha256:5f48f9085096e96f4111ec07d79d57ca5a4a1da5a2003a3028a0b60c62bea7d1
    ports: ["8081:8081"]
    volumes: ["nexus-data:/nexus-data"]
    orca:
    image: varnish/orca:0.18.1@sha256:44df08bdb5843ef1173bda327adb487c2bd101681b24559482c10bf6d8f1429d
    platform: linux/amd64 # Orca is distributed for linux/amd64
    depends_on: [nexus]
    ports:
    - "8080:80" # virtual registries (client endpoint)
    - "9090:9090" # firewall metrics (Premium)
    volumes:
    - ./orca/orca.yaml:/etc/varnish-supervisor/default.yaml:ro
    - ./orca/rulesets:/etc/orca/rulesets:ro
    volumes:
    nexus-data:
  2. Create the Nexus repositories (proxy, hosted, group) for npm and PyPI through the REST API. The npm-all group aggregates npm-private-hosted then npm-public-proxy, and that is what the firewall will see.

    Nexus Community Edition serves no artifact until its EULA is accepted. Measured on 3.96.4: a download returns 403 You must accept the End User License Agreement (EULA), then 200 after acceptance through the API, which expects the body returned by GET, with "accepted": true.

    Terminal window
    curl -s -u "admin:$NEXUS_PASSWORD" http://localhost:8081/service/rest/v1/system/eula \
    | jq '.accepted = true' \
    | curl -s -u "admin:$NEXUS_PASSWORD" -H 'Content-Type: application/json' \
    -X POST http://localhost:8081/service/rest/v1/system/eula -d @-
    # 204 response, no body
  3. Configure the Orca firewall

    orca/orca.yaml
    virtual_registry:
    registries:
    - name: npm-nexus
    default: true
    enable_firewall: true
    remotes:
    - url: http://nexus:8081/repository/npm-all/
    - name: pypi-nexus
    enable_firewall: true
    remotes:
    - url: http://nexus:8081/repository/pypi-all/
    firewall:
    default_action: allow # observe in a POC; deny in production
    default_quarantine_days: 2
    severity_deny_threshold: 9
    severity_allow_threshold: 4
    audit_log_output: stdout
    metrics_address: ":9090"
    rulesets:
    - path: /etc/orca/rulesets/npm-rules.yaml
    - path: /etc/orca/rulesets/pypi-rules.yaml

    The binary embeds a validator. Measured on Orca 0.18.1, it answers configuration is valid for this file, and refuses an unknown key (field champ_inconnu not found in type firewall.FirewallConfig) or a rulesets entry written as a plain string instead of a path: object.

    Terminal window
    docker run --rm --platform linux/amd64 \
    -v "$PWD/orca/orca.yaml":/c.yaml:ro -v "$PWD/orca/rulesets":/etc/orca/rulesets:ro \
    varnish/orca:0.18.1@sha256:44df08bdb5843ef1173bda327adb487c2bd101681b24559482c10bf6d8f1429d \
    varnish-supervisor -validate -config /c.yaml
    # configuration is valid
  4. Point the clients at the firewall

    Orca forwards the client path as is: include the Nexus group path.

    .npmrc
    registry=http://npm-nexus.localhost:8080/repository/npm-all/
    pip.conf
    [global]
    index-url = http://pypi-nexus.localhost:8080/repository/pypi-all/simple
    trusted-host = pypi-nexus.localhost

Writing rules

A rule describes what to recognise (match) and what to decide (action). Rules are grouped in a file identified by an id, and sorted by priority. This demonstration npm rule set covers the three actions.

orca/rulesets/npm-rules.yaml
id: npm-demo-rules
rules:
# Hard block of a specific package.
- id: block-left-pad
priority: 100
action: deny
severity: 9.5
reason: "Package bloqué pour la démonstration."
match:
- type: npm
name: left-pad
# Anti dependency confusion: an internal scope never comes from the public registry.
- id: block-internal-scope
priority: 90
action: deny
reason: "Le scope @company ne doit pas être résolu depuis le registre public."
match:
- type: npm
name: "@company/*"
# Hide old versions without breaking an existing lockfile.
- id: hide-lodash-old
priority: 50
action: hide
reason: "Les versions de lodash < 4.17.21 ne sont pas proposées."
match:
- type: npm
name: lodash
version: "vers:npm/<4.17.21"

On the Python side, the same grammar blocks a vulnerable version. The version range uses the VERS notation (vers:pypi/<2.32.0), a standardised format for version ranges per ecosystem.

orca/rulesets/pypi-rules.yaml
id: pypi-demo-rules
rules:
- id: block-old-requests
priority: 100
action: deny
severity: 9.0
reason: "Les versions de requests < 2.32.0 sont interdites."
match:
- type: pypi
name: requests
version: "vers:pypi/<2.32.0"

How severity interacts with the thresholds

A rule can carry a severity score, which overrides its action. According to the Varnish documentation, a rule whose severity is at or above severity_deny_threshold (9 by default) leads to deny, a rule at or below severity_allow_threshold (4 by default) leads to allow, and in between, the rule's action applies. In the npm example, block-left-pad (9.5) would be refused even if its action were hide.

This mechanism lets you plug in community rules, for instance the rule sets derived from OSV data, and steer their effect with two thresholds, without rewriting each rule. It also carries a trap: a deny rule of severity 3 is allowed.

Dynamic rules from a git repository

Loading rules from local files (path:) requires restarting Orca to take them into account. Dynamic mode goes through a git source, which the firewall refreshes at a regular interval, without a restart. The following excerpt passes the 0.18.1 validator.

orca/orca.yaml (firewall excerpt)
firewall:
mode: report # decisions logged, nothing is blocked
rulesets:
- git:
name: npm-osv-rules
url: https://github.com/varnish/osv-rules.git
sub_path: rulesets/npm/all.yaml
ref: main # followed branch: rules change without local review
interval: 30m # refresh frequency
webhook:
url: http://collector.interne/reloaded # notified after each reload

The varnish/osv-rules repository exists and publishes rulesets/npm/all.yaml. Following its main branch is a deliberate choice: rules update on their own, at the price of trusting the publisher. For your own rules, an internal repository, versioned and reviewed through code review, gives a real GitOps approach. report mode is the right starting point: every decision is brought down to allow, but stays written in the audit log, which lets you measure the effect of a policy before enabling it.

Studying the firewall in practice

With a trial licence mounted, the firewall enforces decisions. The most telling study scenario compares two installations. The following steps were not replayed for this lesson, for lack of a licence: they describe the documented behaviour.

  1. Install an allowed dependency

    Terminal window
    npm install is-number # no rule matches: allow
  2. Try a blocked dependency

    Terminal window
    npm install left-pad # block-left-pad rule: deny, 403 response
  3. Read the audit log

    Terminal window
    docker compose logs orca | grep -i deny

    The log carries the decision, the package and the rule's reason (reason). That is what you will route to a SIEM in production.

  4. Change a rule and replay

    Remove block-left-pad, restart Orca, run the installation again: it goes through. The policy drives the decision.

What the firewall does not protect

An artifact firewall closes one door: the ungoverned intake of dependencies. Presenting it as global protection would be a mistake, and three limits show it.

  • It does not read the code of an allowed package: a dependency that is sound today can be compromised by an update, hence the quarantine.
  • It protects neither a compromised CI runner, nor secret exfiltration, nor an image already built and unsigned.
  • It produces neither an SBOM nor provenance: those are other components of this module.

Above all, the registry configuration encourages going through the firewall but does not enforce it: a developer or a job can always write npm config set registry https://registry.npmjs.org. Closing this bypass is a topic in its own right.

Preventing bypass (CI and developer workstations)

A firewall only protects what goes through it. As long as clients can reach registry.npmjs.org or pypi.org directly, anyone can short-circuit it with a single command. The rule fits in one sentence: configuration encourages, the network enforces.

On CI runners: lock down egress

On the CI side, the bypass closes cleanly because you control the environment. Put the runners in a network with no outbound route to the Internet: only the artifact firewall is reachable. An attempt to run npm config set registry https://registry.npmjs.org then fails for lack of a route. On Kubernetes, an egress NetworkPolicy that only allows the firewall service has the same effect, and ephemeral runners prevent any persistent local configuration.

On developer workstations: the hard case

The developer's workstation is harder to constrain: the developer often has administrator rights on it, so no application configuration is tamper-proof. Pushing an .npmrc, a pip.conf or a GOPROXY through MDM or configuration management reduces friction, but can still be bypassed with a --registry. The robust control is, once again, the network:

SolutionEffectRobustness
Cut direct egress to npmjs, pypi, Docker HubThe public registry has no route any moreHigh
Internal DNS: registry.npmjs.org resolved to the firewallEven a hard-coded public URL lands on the firewallHigh, with egress closed
Workstation without a direct Internet routeEverything goes through the internal proxyMaximum
Remote development environment (dev container, VDI)You control the environment's networkHigh
Managed configuration (.npmrc, pip.conf, GOPROXY)Steers the defaultLow on its own

No single row is enough on a machine where the developer is an administrator. The combination that holds: no direct route to public registries, plus a DNS that brings public names back to the firewall. Managed configuration adds comfort, not security.

Catching and detecting

The network closes the door; detection checks that it stays closed. Add a CI check on lockfiles: a resolved entry (npm) or an index URL pointing anywhere other than the firewall fails the build. OSV-Scanner (in French) spots on its side the poisoned dependencies that bypassed the firewall, and the network proxy logs flag any attempt at direct access to public registries. These bypasses extend the techniques described in Attacks through package managers (in French).

Where should it sit in your chain?

The firewall is not a substitute for the module's other controls: it holds the furthest upstream position. A mature chain combines them, each answering a different question.

StepComponentRole
Dependency intakeVarnish Artifact FirewallDecides before ingestion
CentralisationNexus (in French)Caches and exposes artifacts
InventorySBOMLists the shipped components
ExploitabilityVEXDocuments what is really exploitable
SigningCosignSigns and verifies the image
ProvenanceSLSAAttests the origin of the build

The firewall reduces the noise downstream: fewer dubious components come in, fewer vulnerabilities to sort afterwards. It does not replace defence in depth, it starts it.

Key points

  • A cache speeds things up and withstands outages; an artifact firewall decides what has the right to come in.
  • Three rule actions, allow, hide and deny, plus a global quarantine (default_quarantine_days) on recent publications.
  • Severity overrides the action: above the deny threshold, deny; below the allow threshold, allow.
  • report mode logs decisions without blocking anything: the right start to measure a policy.
  • -validate checks the configuration's structure, not the content of the rules.
  • Without a network egress rule, the registry configuration can be bypassed.
  • At Varnish, the firewall is Premium: the public 0.18.1 image starts with addons=[] and filters nothing.
  • The evidence expected by SOCLE-INT-GEN-3 is the internal proxy configuration and proof of the absence of unfiltered direct pulls.

Knowledge check

Check that the essentials of this lesson are understood. The questions only cover what has just been explained here.

Knowledge check

Check what you have learned with this interactive quiz

6 questions
6 min
70% to pass

How it works

  • The timer starts when you click Start
  • Multiple choice, true/false and short answer questions
  • You can move between questions
  • Detailed results are shown at the end

Starts the quiz and the timer

Is this project useful to you?

The guides, courses, quizzes and labs stay free. If you use them regularly, you can contribute to their upkeep and development.

Become a supporting member (new tab) One-time support (new tab) Learn more

Follow the project on LinkedIn