Admin API

The Artifact Firewall serves a small HTTP API for reloading rulesets, evaluating a single package against the loaded rules, and changing enforcement without a restart.

The API is served on the address set by api_address, which Orca sets to 127.0.0.1:6090. It is exposed nowhere else: a request for /api/* on the public listener returns 404. Setting api_address to an empty string disables the API entirely.

There is no authentication. The listen address is the access control, and the default binds loopback, so calls have to come from the node itself.

The Virtual Registry is a client of this API. In an Orca deployment it calls /api/eval to authorize each artifact download, so an API that is unreachable stops downloads from being authorized.

/api/eval

Evaluate one package against the loaded rulesets. Method GET, anything else returns 405.

The package is named either by PURL or by its parts:

  • purl: a full package URL, for example pkg:npm/lodash@4.17.20.
  • type, name: package ecosystem and name. Both are required in this form.
  • namespace, version: optional, alongside type and name.
  • registry: optional name of the virtual registry the request belongs to, which decides whether a ruleset scoped with virtual_registries applies.
curl -s '127.0.0.1:6090/api/eval?purl=pkg:npm/lodash@4.17.20'
{"action":"hide","rule_id":"GHSA-29mw-wpgm-hmr9","ruleset_id":"osv-npm","reason":"Command Injection in lodash"}

The status code carries the verdict: 200 for allow and hide, 403 for deny. A denied response adds an error field naming the rule, the ruleset and the reason. A missing or malformed target returns 400, as does a package type no transformer handles.

The verdict is the effective one, so a firewall in report mode answers 200 for a package a rule denies. The rule that matched is still reported, and the call is written to the audit log.

/api/update

Re-read every ruleset. Local paths are read again and every git source is fetched, whatever its interval has to say, then the rule engine is rebuilt and swapped in. Traffic is served throughout, and nothing restarts.

curl -s 127.0.0.1:6090/api/update

An empty 200 means the reload succeeded. A failure returns 502 with the reason, and the previously loaded rulesets stay in force.

The webhook fires after a successful reload, not after a failed one.

/api/mode

Read or change the firewall mode at runtime. GET reads it, POST changes it, anything else returns 405.

curl -s 127.0.0.1:6090/api/mode
{"mode":"normal"}

curl -s -X POST -H 'Content-Type: application/json' \
     -d '{"mode":"report"}' 127.0.0.1:6090/api/mode
{"mode":"report"}

The change takes effect on the next request. An unknown mode, or a body carrying any other field, returns 400.

The new mode is not written back to the configuration, so a restart returns to the configured value.

/api/rulesets/{ruleset_id}/rules/{rule_id}

Read a single rule, or turn it off and on again, without editing the ruleset it came from. GET reads the rule, PATCH changes it, anything else returns 405. A ruleset or rule ID that matches nothing returns 404.

curl -s 127.0.0.1:6090/api/rulesets/osv-npm/rules/GHSA-29mw-wpgm-hmr9

curl -s -X PATCH -H 'Content-Type: application/json' \
     -d '{"disabled":true}' \
     127.0.0.1:6090/api/rulesets/osv-npm/rules/GHSA-29mw-wpgm-hmr9

Both return the rule as JSON, including its disabled state. disabled is the only field a PATCH acts on, and a body naming a field that is not part of a rule returns 400.

A rule disabled here is skipped during evaluation from the next request on. The state survives a ruleset reload, including one that fetches a new revision of the ruleset, but not a restart: every rule is loaded with the disabled value its ruleset file gives it. Turning a rule off for good belongs in the ruleset.

Health endpoints

/healthz and /readyz are served on both the public listener and the admin listener, and answer 200 once the process is up.


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