The Artifact Firewall enforces rules on eight package types: npm, PyPI, Maven, NuGet, Hex, Go, Conda and RPM. The Virtual Registry classifies each request and routes only those eight to the firewall, so a rule written against any other ecosystem matches nothing, whatever enable_firewall says.
Enforcement takes two forms, and most ecosystems use both:
deny becomes a 403 carrying the rule’s reason. This is what stops a client that pins a version.The difference between deny and hide is the download: both keep a version away from a client resolving “latest”, and only deny blocks a request that names the version outright.
The packument is filtered. Denied and hidden versions stay listed, and the latest dist-tag is re-pointed at the highest version no rule objects to, so a plain npm install resolves to that one. A client that pins a blocked version therefore gets an error saying it is blocked, rather than one saying the version does not exist.
Tarball downloads (/<package>/-/<package>-<version>.tgz) are authorized. A denied version returns 403, a hidden one is served.
Quarantine windows work here without further configuration, since the packument carries a publish time for every version.
The simple index is filtered, in both its HTML form and its PEP 691 JSON form. A denied version’s files are removed from the index. A hidden version’s files are marked yanked with the rule’s reason, which is the mechanism PyPI itself uses for a withdrawn release: pip skips a yanked file unless the requirement pins that exact version.
Wheel and sdist downloads are authorized as well. A client that already holds the URL, out of a lock file or an index it cached earlier, is checked against the ruleset rather than served on the strength of what it read then. A denied version returns 403, a hidden one is served.
Quarantine windows on the JSON index use the publish times it carries. The HTML index carries none, so a lookup against the PyPI JSON API is needed, configured with pypi.api_url. Without it, quarantine is not applied to clients using the HTML index.
maven-metadata.xml is filtered, for releases and for snapshots. Denied and hidden versions are removed from the version list, so a build resolving a range or a LATEST never sees them.
Downloads of a .jar, .war, .aar or .zip below the version directory are authorized, and a denied version returns 403.
Two kinds of file stream through untouched whatever the verdict:
.pom and Gradle’s .module. Maven reads the POM of every candidate version while resolving a conflict, including versions it then discards, so denying one would fail the whole resolve rather than the single artifact it describes. A build that pins a denied version reads the POM and is then blocked on the artifact..sha1, .md5, .sha256, .sha512 and .asc. A client has to be able to verify what it did download.Quarantine windows need publish times, which Maven repositories do not carry in their metadata. They are looked up through Maven Central’s Solr endpoint, configured with maven.solr_url, falling back to a HEAD request against the POM when Solr answers nothing.
The registration pages, the flat container index.json, the service index, and the query and autocomplete responses are all filtered. Denied and hidden versions are removed from the version lists.
.nupkg downloads are authorized, and a denied version returns 403.
Quarantine windows use the published timestamp on the registration catalog entry. The flat container index carries no timestamps, so a client resolving through it is not quarantined.
The Hex package manifest cannot be filtered. It is a protobuf payload signed by the repository, and the client verifies that signature against a key in its repository configuration before trusting the contents, so any edit makes hex_core reject the response outright rather than fall back to the unedited one.
Enforcement is on the download instead. A tarball request (/tarballs/<name>-<version>.tar) is authorized and a denied version returns 403. A rule that denies a package as a whole, with no version, also blocks the manifest request, which fails the resolve before a version is chosen.
Documentation tarballs are not package content and are not routed through the firewall.
Every GOPROXY resource except the module zip is buffered and evaluated, because resolving a module graph can ask for a .mod on its own without ever fetching the matching .info.
Only @v/list is rewritten, with denied and hidden versions dropped from the version list. The rest are evaluated but never altered in transit, so an allowed .mod or zip reaches the client byte for byte and GOSUMDB verification keeps working through the firewall.
| Path | Enforcement |
|---|---|
@v/list |
Filtered. Denied and hidden versions are dropped from the list. |
@latest |
Gated. Deny, hide and quarantine all return 404. |
@v/<version>.info |
Gated. Deny returns 403, hide and quarantine return 404. The quarantine window is read from the response’s own Time field. |
@v/<version>.mod |
Gated. Deny returns 403, hide returns 404. It carries no Time field, so quarantine is not checked. |
@v/<version>.zip |
Authorized before the download starts. A denied version returns 403. |
/sumdb/... |
Passed through. The checksum database is not a module resource. |
A hide here answers a request that names the version, which it does nowhere else. The 404 is what the go command reads as “not found here”, leaving it to resolve from the filtered @v/list, so the version is absent rather than refused.
GOPROXY must name the Virtual Registry alone, with no ,direct and no second proxy after it.
The go command falls through to the next entry of a comma-separated GOPROXY whenever one answers 404 or 410. @latest returns 404 on a deny by design, so that the go command’s own “not found here” fallback sends it to @v/list, which is filtered to the versions no rule objects to. That fallback stays safe only while the firewall is the last word the go command gets. Append ,direct and a denied module is reachable again straight from its version control, past the rule that blocked it.
The requirement is what makes hide and quarantine work at all, not only deny. Both answer 404, and the go command cannot tell either apart from “not found here, try the next entry”. A .info or .mod deny returns 403 rather than 404 for the same reason, since a single denied version has no safe fallback target and has to be a dead end.
repodata.json is filtered, including its .zst and .gz variants. Denied and hidden versions are dropped from it, so a solver never picks one. A .conda or .tar.bz2 download is authorized, and a denied version returns 403.
Two repodata variants cannot be filtered and are answered 404 rather than passed through: .bz2, for which no re-encoder exists, and CEP-16 sharded repodata (repodata_shards.msgpack.zst and the per-shard files). Clients read the 404 as “variant not offered” and fall back to repodata.json.zst or plain repodata.json, both of which are filtered.
Repodata that cannot be parsed or re-encoded fails open. The upstream body is streamed through unfiltered and the error is logged, so a corrupt index does not take a whole channel down. A denied version in such an index is still blocked when a client tries to download it.
channeldata.json and notices.json stream through, since neither is used for solving.
The channel is carried in the purl namespace, so a rule reads pkg:conda/<channel>/<name>@<version>. An upstream that serves a channel at its root, such as https://conda.anaconda.org/<channel> configured as a remote, yields an empty namespace, and a rule meant to match every channel needs namespace: "*". Build strings such as py311h38be061_0 are not part of the coordinate, so denying a version denies every build of it.
A channel whose repodata sets a CEP-15 base_url pointing at another host moves the downloads off the firewall entirely. Filtering still governs what the solver can pick, but the per-download check is bypassed, so proxy such a channel with its base_url in mind.
Reading a conda-forge-scale index needs max_manifest_size raised above its 64 MiB default.
primary.xml is filtered, with denied and hidden versions dropped from the package list, and repomd.xml is filtered to match, since it carries the checksum of primary.xml. A .rpm download is authorized, and a denied version returns 403.
Rewriting repomd.xml invalidates the detached signature the repository publishes for it, so a client that verifies repository metadata rejects the filtered document. Such a client needs repo_gpgcheck=0. Per-package signature verification, gpgcheck=1, is unaffected and should stay on, since the packages themselves are never modified.
Where that trade is not acceptable, preserve_manifest keeps the upstream signature intact and gives up filtering. Downloads are still authorized, so a denied version is blocked when a client fetches it rather than kept out of the index it resolves from.