Redirect public package traffic to Varnish Virtual Registry with Zscaler

Developers and AI agents fetch open source packages straight from the public registries: npm install goes to registry.npmjs.org, go get goes to proxy.golang.org, and so on. Pointing every client, laptop and agent at an internal registry one by one is hard to enforce, and anything that is missed bypasses your security policy.

If your organization already sends outbound traffic through Zscaler Internet Access (ZIA) with SSL inspection, you can enforce this on the network instead. Zscaler intercepts requests to the public registries and redirects them to Varnish Virtual Registry, which fetches the package, checks it against your Artifact Firewall policy, caches it, and serves it. Nothing changes on the client side.

This is the same mechanism as JFrog’s Package Traffic Controller, so the Zscaler configuration has the same shape. The difference is the redirect target.

How it works

Here is what happens when a developer runs npm install express:

  1. npm requests https://registry.npmjs.org/express. The request goes through Zscaler.
  2. Zscaler decrypts the request (SSL inspection), matches registry.npmjs.org against a URL category, and answers with an HTTP 307 redirect to https://vr.example.com/package-reroute?url=https%3A%2F%2Fregistry.npmjs.org%2Fexpress. The original URL is percent-encoded and appended as the url query parameter.
  3. npm follows the redirect.
  4. Varnish Virtual Registry decodes the url parameter and looks up its host, registry.npmjs.org, among the remotes of its configured registries. A host that is not a configured remote is refused with a 403, and nothing is fetched.
  5. The request then goes through the normal pipeline of the matching registry. It is classified, checked by the Artifact Firewall, and served from cache, or fetched from registry.npmjs.org on a cache miss.

Package downloads that the metadata points to, such as npm tarballs, are rewritten to point at the Virtual Registry directly, so they do not go through the Zscaler redirect a second time.

When the firewall blocks a package, the client receives a 403 with the reason:

npm error 403 403 Forbidden - GET https://vr.example.com/package-reroute?url=https%3A%2F%2Fregistry.npmjs.org%2Flodash - package blocked by firewall (rule "deny-lodash", ruleset "security")

Prerequisites

  • Varnish Virtual Registry 0.18.0 or later, with a license that includes the Artifact Firewall.
  • A hostname for the Virtual Registry, reachable from the developer machines, with a TLS certificate those machines trust. See the TLS tutorial.
  • Outbound HTTPS from the Virtual Registry to the public registries.
  • Zscaler ZIA with SSL inspection on the developer machines, and administrative access to the ZIA Admin Portal.

Configure Varnish Virtual Registry

Enable the /package-reroute endpoint with package_reroute, and add a registry whose remote is the public registry you want to capture, registry.npmjs.org in this example. The host of the redirected URL is what matches a request to this registry.

The registry is also marked as the default. The Virtual Registry rewrites download links, such as npm tarballs, to point at its own hostname, and the default registry is what receives those follow-up requests.

The firewall block loads the rulesets that decide what is blocked. This example uses the npm rules from the public osv-rules, generated from the OSV database. See the Artifact Firewall configuration for all the options.

firewall:
  default_action: allow
  rulesets:
  - git:
      name: osv-rules
      url: https://github.com/varnish/osv-rules.git
      sub_path: rulesets/npm/all.yaml

virtual_registry:
  package_reroute:
    enabled: true
  registries:
  - name: npm-public
    default: true
    enable_firewall: true
    remotes:
    - url: https://registry.npmjs.org

The registries that serve your internal packages, such as an Artifactory or Nexus registry, stay as they are.

To capture other ecosystems the same way, or if another registry is already your default, replace default: true with a route on the ecosystem, available from 0.20.0, and add one registry per public registry:

  - name: npm-public
    enable_firewall: true
    routes:
    - ecosystems: [npm]
    remotes:
    - url: https://registry.npmjs.org
  - name: go-proxy
    enable_firewall: true
    routes:
    - ecosystems: [go]
    remotes:
    - url: https://proxy.golang.org

Configure Zscaler ZIA

These steps are done in the ZIA Admin Portal. Menu labels can vary by version and tenant. JFrog’s Zscaler ZIA guide follows the same steps.

Before you start, make sure the developer machines trust the Zscaler root certificate for SSL inspection. Without it, package managers fail with certificate errors as soon as inspection is turned on.

Create a URL category

  1. Go to Policies > Internet & SaaS > URL Categories and click Add URL Category.
  2. Name: for example Public_Package_Registries.
  3. URLs: the hostnames of the remotes you configured, registry.npmjs.org in this example.
  4. Click Save.

Do not add the Virtual Registry hostname to this category.

Inspect the traffic

  1. Go to Policies > Common Configuration > SSL/TLS Inspection and click Add SSL/TLS Inspection Rule.
  2. URL Categories: Public_Package_Registries.
  3. Action: Inspect.
  4. Rule Order: above any default rule that bypasses inspection.
  5. Click Save.

Create the redirect rule

  1. Go to Policies > Internet & SaaS > URL Filtering and click Add URL Filtering Rule.
  2. URL Categories: Public_Package_Registries.
  3. Protocols: HTTPS.
  4. Request Methods: GET and HEAD. The /package-reroute endpoint only accepts these two methods.
  5. User Agent: Other. This captures package managers and leaves browsers alone.
  6. Action: Block, with Redirect URL set to https://vr.example.com/package-reroute, without a trailing slash. Zscaler appends the original URL itself.
  7. Groups or Locations: the developers, CI runners and AI agents you want to capture.
  8. Click Save, then Activate Change.

Changes can take a few minutes to propagate.

Exclude the Virtual Registry

The Virtual Registry fetches from the same public hosts that the rule captures. If its outbound traffic goes through Zscaler and matches the redirect rule, its own fetches are redirected back to itself and fail.

Scope the redirect rule to user groups, or exclude the location or IP address of the Virtual Registry from it. If its outbound traffic is inspected, the Virtual Registry host must also trust the Zscaler root certificate.

Verify the setup

From a machine in scope of the redirect rule, check that Zscaler redirects:

curl -sI https://registry.npmjs.org/express

The response is a 307 with a Location header that points to https://vr.example.com/package-reroute?url=....

Then follow the redirect:

curl -sL -o /dev/null -w '%{http_code}\n' https://registry.npmjs.org/express

This prints 200.

Finally, run your package managers without changing their configuration, for example npm install express. The requests show up in the Virtual Registry logs as /package-reroute?url=..., followed by the download requests. A package that your firewall policy denies is refused with a 403.

Clients outside Zscaler

Machines whose traffic does not go through Zscaler, such as cloud CI runners, are not redirected. Point their clients at the Virtual Registry directly, as described in the tutorials for each client, for example npm or Go.

Do not override the public hostnames, such as registry.npmjs.org, in DNS to point at the Virtual Registry. The Virtual Registry cannot present a valid certificate for them, so clients reject the connection.

Troubleshooting

Symptom Cause
403 package-reroute host not allowed The host in url is not a remote of any registry. Add a registry for it.
400 package-reroute: ... The url parameter is missing or malformed. Check the redirect URL in Zscaler.
404 on /package-reroute package_reroute is not enabled.
404 on downloads after the metadata succeeded The registry is neither the default nor routed by ecosystems, so the rewritten download links do not reach it.
The client times out after the redirect The client cannot reach the Virtual Registry hostname. A hostname that resolves to a private address is not reachable through Zscaler Internet Access alone.
The redirect loops The Virtual Registry hostname is in the redirect URL category.
Cached packages work, new ones fail with a 5xx or time out The Virtual Registry cannot reach the public registry: it has no outbound access, its outbound traffic is caught by the redirect rule, or Zscaler inspects it and the Virtual Registry does not trust the Zscaler root certificate. It needs direct or transparent outbound access, not an explicitly configured proxy.
Certificate errors on the client The Virtual Registry certificate, or the Zscaler root certificate, is not trusted by the client.

®Varnish Software, Wallingatan 12, 111 60 Stockholm, Organization nr. 556805-6203