Artifact Firewall

To enable the firewall for a specific virtual registry, set enable_firewall on the registry.

Within project Orca, the Supervisor always runs the firewall in integrated mode behind the Virtual Registry, which handles request routing. The firewall’s standalone deployment options (deployment, upstream registries, and their routes) therefore do not apply here.

Example:

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

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

license:
  file: /app/license.lic

address

firewall:
  address: 0.0.0.0

Type: String

Default: 127.0.0.1

Address and/or port for the Artifact Firewall proxy to listen on. The Supervisor binds it to 127.0.0.1 when omitted in integrated mode.

port

firewall:
  port: 6089

Type: Integer

Default: 6089

Port for the Artifact Firewall proxy to listen on.

api_address

firewall:
  api_address: 127.0.0.1:6090

Type: String

Default: 127.0.0.1:6090

Listen address for the admin API, in host:port form. In integrated mode the Virtual Registry calls this API to authorize per-version artifact downloads, and the Supervisor binds it to 127.0.0.1:6090 when omitted. An empty string disables the admin API.

log_level

firewall:
  log_level: info

Type: String

Default: info

Log level for Artifact Firewall output.

Options:

  • debug
  • info
  • warn
  • error

At info the firewall records what it is running as it starts: its version and the revision it was built from, the configuration file it read, and the configuration itself as every setting that differs from its default. A log therefore says what a firewall was running without its files having to be found.

INFO is reserved for state changes, so a ruleset reload names the rulesets that changed rather than counting them.

log_output

firewall:
  log_output: stdout

Type: String

Destination for Artifact Firewall process logs. This is the firewall’s own operational log, separate from the audit log.

Options:

  • stdout
  • stderr
  • A file path (e.g. /var/log/artifact-firewall.log)
  • otel: export the logs as OpenTelemetry log records under the scope artifact-firewall/log, which needs otel.logs configured. This one tees rather than replaces: the logs are written to standard error as well, so startup logs are never lost

A firewall that refuses to start writes the reason here as an ERROR record, and the line saying it stopped cleanly is written here too. Both are worth knowing about, since a process that never came up is otherwise looked for in the wrong place.

audit_log_output

firewall:
  audit_log_output: /var/log/artifact-firewall-audit.log

Type: String

Default: stdout

Destination for the audit log, which records every package the Artifact Firewall evaluated and what it decided. When empty, audit logging is disabled.

Options:

  • stdout
  • stderr
  • A file path (e.g. /var/log/artifact-firewall-audit.log)
  • otel: export the entries as OpenTelemetry log records instead of JSON, which needs otel.logs configured

default_action

firewall:
  default_action: deny

Type: String

Default: allow

Action taken when a request does not match any rule in the configured rulesets.

Options:

  • allow: Pass the request through.
  • hide: Remove version from “latest” consideration.
  • deny: Return a 403 response.

mode

firewall:
  mode: report

Type: String

Default: normal

Global mode that reshapes how matched rule actions are enforced, useful for testing rulesets before enforcing them.

Options:

  • normal: Enforce rule actions as configured.
  • hide: Downgrade deny actions to hide, so blocked versions are removed from “latest” consideration instead of being rejected outright.
  • report: Downgrade all actions to allow, so nothing is blocked. Decisions are still recorded in the audit log via the effective_action field, making this a report-only mode for evaluating a ruleset’s impact.

default_quarantine_days

firewall:
  default_quarantine_days: 2  # example value, not the default

Type: Integer

Default: 0 (disabled)

When set to a positive integer, newly published packages within this many days of their release are blocked regardless of ruleset matches. This provides a quarantine window to guard against newly introduced malicious packages.

Set to 0 to disable quarantine.

severity_deny_threshold

firewall:
  severity_deny_threshold: 9.0

Type: Number

Default: 9

Severity score at or above which a matching rule resolves to deny. Rules with a severity score at or above this threshold override their configured action and deny the request.

severity_allow_threshold

firewall:
  severity_allow_threshold: 4.0

Type: Number

Default: 4

Severity score at or below which a matching rule resolves to allow. Rules with a severity score at or below this threshold override their configured action and allow the request.

rulesets

firewall:
  rulesets:
  - path: /etc/orca/rulesets

Type: List

List of ruleset sources. Each entry sets exactly one of path, for a ruleset on local disk, or git, for one kept in a remote repository.

path

firewall:
  rulesets:
  - path: /etc/orca/rulesets

Type: String

Path to a ruleset file or a directory containing ruleset files. Files ending in .yaml or .yml are read as rulesets, and a directory is scanned non-recursively.

git

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

Fetch rulesets from a remote Git repository. The repository is cloned locally and refreshed on the configured interval.

The clone is a bare repository holding only the latest revision of the rulesets it was asked for, so it stays at roughly the size of one compressed ruleset however long it runs.

A ruleset republished with no change other than its top-level date field is fetched but not reloaded.

Take every ruleset wanted from a repository in a single entry, using sub_paths. Each entry is a clone of its own, so naming the same repository in six entries fetches and stores it six times over.

name

Type: String

Unique name for this Git ruleset source. Used to identify the source in logs and as a subdirectory name under work_dir.

url

Type: String

URL of the Git repository.

ref

firewall:
  rulesets:
  - git:
      url: https://github.com/example/rulesets.git
      ref: main

Type: String

Default: main

Branch or tag to track.

sub_path

firewall:
  rulesets:
  - git:
      url: https://github.com/example/rulesets.git
      sub_path: policies/npm

Type: String

A single ruleset file, or a directory of them, within the repository. A directory contributes every .yaml and .yml file under it, at any depth. This is the one place a directory is searched recursively, a local path entry is not.

Exactly one of sub_path or sub_paths is required. A source that matches no ruleset fails the load rather than enforcing less than was asked for.

sub_paths

firewall:
  rulesets:
  - git:
      name: osv
      url: https://github.com/varnish/osv-rules.git
      sub_paths:
      - rulesets/npm/all.yaml
      - rulesets/pypi

Type: List

Several of the above, from one clone of the repository. Each entry is either a ruleset file or a directory.

A repository that files its rulesets per ecosystem can be named whole, and ecosystems added to a tracked directory later are picked up without a configuration change.

interval

firewall:
  rulesets:
  - git:
      url: https://github.com/example/rulesets.git
      interval: 30m

Type: String

Default: 1h

How often to pull updates from the remote repository. Accepts duration strings such as 30m or 2h.

fetch_timeout

firewall:
  rulesets:
  - git:
      url: https://github.com/example/rulesets.git
      fetch_timeout: 60s

Type: String

Default: 30s

Timeout for each fetch operation. Accepts duration strings such as 30s or 2m.

auth

firewall:
  rulesets:
  - git:
      url: https://github.com/example/rulesets.git
      auth:
        token_env: GITHUB_TOKEN

Credentials for authenticating with the Git remote. At most one of token_env or github_app can be set.

token_env

Type: String

Name of the environment variable that holds the authentication token, for example a GitHub personal access token. The token is passed as an HTTP Bearer token when fetching from the remote, and is read on every sync, so rotating it needs no restart.

github_app
firewall:
  rulesets:
  - git:
      url: https://github.com/example/rulesets.git
      auth:
        github_app:
          client_id: Iv23liABCDEFGHIJKLMN
          private_key_path: /etc/orca/rulesets-app.pem

Authenticate as a GitHub App. The firewall signs a JWT with the App private key, exchanges it for a short-lived installation access token, and refreshes that token before it expires.

Fields:

  • client_id: GitHub App client ID. Mandatory. GitHub recommends the client ID over the numeric App ID.
  • private_key_path: Path to the App private key in PEM form.
  • private_key_env: Name of an environment variable holding the App private key in PEM form.
  • api_base_url: GitHub REST API base URL, https://api.github.com by default. Set it for GitHub Enterprise Server.

Exactly one of private_key_path or private_key_env is required. The key is re-read on each token mint, so rotating it needs no restart, and it is validated at startup, so a missing or malformed key fails the firewall immediately rather than at the first sync.

There is no installation ID to configure. The firewall derives the owner and repository from url and looks the installation up itself, so url has to be an HTTPS URL of the form https://host/owner/repo.git. An SSH remote cannot be used with github_app, because the credential is sent as an HTTP header.

A GitHub App adds an outbound dependency on the GitHub API host named by api_base_url, which is a different host from the one the clone talks to.

webhook

firewall:
  webhook:
    url: https://example.com/hooks/ruleset-reloaded

Optional webhook called after rulesets are successfully reloaded. When url is not set, no webhook is sent.

Unavailable when using Artifact Firewall with Virtual Registry, as the webhook is used for manifest cache invalidation.

url

Type: String

URL to send an HTTP request to when rulesets are reloaded.

method

firewall:
  webhook:
    url: https://example.com/hooks/ruleset-reloaded
    method: POST

Type: String

Default: GET

HTTP method to use for the webhook request.

headers

firewall:
  webhook:
    url: https://example.com/hooks/ruleset-reloaded
    headers:
      Authorization: "Bearer my-secret-token"

Type: Map

Custom HTTP headers to include in the webhook request. A credential set here is kept out of the log when the configuration is recorded at startup.

preserve_manifest

firewall:
  preserve_manifest: true

Type: Boolean

Default: false

Pass manifests through to the client without any transformation. When disabled, the firewall may rewrite manifest content.

max_manifest_size

firewall:
  max_manifest_size: 67108864

Type: Integer

Default: 67108864 (64 MiB)

Maximum manifest body size in bytes that the firewall will read and process. Manifests larger than this limit are rejected.

A conda repodata.json is the one manifest that regularly runs past the default. Filtering a channel the size of conda-forge needs this raised. See Conda enforcement.

work_dir

firewall:
  work_dir: /var/cache/artifact-firewall

Type: String

Working directory used to store cached data for remote ruleset sources (e.g., cloned Git repositories). Defaults to a temporary directory if not set.

storage

firewall:
  storage:
    path: /var/lib/varnish-artifact-firewall/storage
    size: 10G

Persistent storage for the ruleset cache — cloned Git repositories and downloaded ruleset files. When path is set, it takes precedence over work_dir for these caches.

path

Type: String

Directory where the ruleset cache (cloned Git repositories and downloaded ruleset files) is stored. Overrides work_dir for these caches when set.

size

Type: String

Declared storage budget for the ruleset cache, such as 10G. When both path and size are set, the firewall enforces it as a growth limit: it stops growing cloned repositories once usage reaches size, preventing the cache from filling the disk over time. It also warns when usage reaches 80% of size, and warns at startup if the filesystem hosting path is significantly smaller than size.

Sizes follow Varnish conventions: a bare integer is bytes, while the suffixes K, M, G, and T (case-insensitive) are binary multipliers (for example 10G is 10 × 2^30 bytes).

metrics_address

Type: String

Deprecated since Artifact Firewall 0.4.0, and ignored. A configuration that still sets it loads and logs a warning.

Firewall metrics are exported through the same top-level otel block as the rest of Orca, and appear under the instrumentation scope artifact-firewall.

preserve_order

firewall:
  preserve_order: true

Type: Boolean

Default: false

Preserve the original JSON map key order in manifests. Enabling this is significantly slower and is primarily useful for debugging or diffing manifest transformations.

maven

firewall:
  maven:
    solr_url: https://search.maven.org/solrsearch/select

Maven-specific configuration, used when a virtual registry proxying a Maven repository has the firewall enabled.

solr_url

Type: String

Default: https://search.maven.org/solrsearch/select

Solr search endpoint used to look up Maven artifact publish timestamps for the quarantine window (see default_quarantine_days). When empty, Solr lookups are disabled and the firewall falls back to HEAD requests to determine timestamps.

pypi

firewall:
  pypi:
    api_url: https://pypi.org/pypi

PyPI-specific configuration, used when a virtual registry proxying a PyPI repository has the firewall enabled.

api_url

Type: String

Default: empty (disabled)

PyPI JSON API root (for example https://pypi.org/pypi) used to look up package publish timestamps for the quarantine window (see default_quarantine_days) on the PEP 503 HTML simple index, where the upstream response does not carry a reliable timestamp. When empty, this lookup is disabled and quarantine is not enforced on the HTML simple index.


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