On this page · 12 sections
- What shipped, and where the docs and the announcement diverge
- The first fail-open path: classification
- The second fail-open path: unparseable versions defeat !=
- The dashboard is not the API
- Range syntax is not supported
- TLS decryption is the deployment gate
- Who is affected, and what to do
- India-specific considerations
- What is still unknown
- FAQ
- How eCorpIT can help
- References
Summary. Cloudflare shipped package registry security to Gateway on 13 August 2026, letting teams write HTTP policies that allow or block software package downloads across seven ecosystems: npm, PyPI, RubyGems, Cargo, Go, Maven and NuGet. Five pkg.* selectors are available, with 2 policy actions and 6 version comparison operators. The documentation, last updated 14 August 2026, carries two statements the announcement does not. First, detection is deliberately fail-open: "A classification failure will never block or break a package install." Second, and more consequentially, "When a version string cannot be parsed by the ecosystem's versioning rules, or when the detected ecosystem does not match the comparison context, the comparison returns no match. This includes !=, meaning an unparseable version does not match anything." A Block policy built on a not-equals version test therefore lets unparseable versions through. The feature also requires TLS decryption, 1 of the 5 selectors cannot be used in the dashboard at all, and selecting more than one ecosystem in the dashboard disables every package field beneath it. Retrieved on 23 August 2026, that documentation page is 306,347 bytes as HTML and 17,477 bytes as markdown.
What shipped, and where the docs and the announcement diverge
The changelog entry "Detect and control software package downloads with package registry security" carries a pubDate of Thursday, 13 August 2026. The product page at /cloudflare-one/traffic-policies/http-policies/package-registry-security/ was last updated 14 August 2026, a day later, and it is the longer of the two by a wide margin.
Gateway classifies a request by the shape of the URL path, not by hostname. In Cloudflare's words, it "recognizes the API contract of each supported registry protocol, not the hostname serving the traffic." That is the design's best property: the same policy catches registry.npmjs.org, a self-hosted mirror, and a corporate Artifactory or Nexus proxy, with no host list to maintain.
Both the changelog and the docs list the seven ecosystems. Only the docs publish the artifact patterns Gateway matches on and the versioning standard each ecosystem is parsed with.
| Ecosystem | Artifact pattern matched | Namespace field | Versioning standard used for comparisons |
|---|---|---|---|
| npm | /{package}/-/{package}-{version}.tgz |
Scope, for example @babel |
SemVer |
| PyPI | /packages/{hash}/{hash}/{hash}/{package}-{version}.whl |
None | PEP 440 |
| RubyGems | /gems/{package}-{version}.gem |
None | Gem::Version |
| Cargo | /crates/{package}/{package}-{version}.crate |
None | SemVer |
| Go | /{module}/@v/{version}.zip |
Module path | Go module versions |
| Maven | /maven2/{group}/{artifact}/{version}/{artifact}-{version}.jar |
Group ID | Maven version ordering |
| NuGet | /{package}/{version}/{package}.{version}.nupkg |
None | NuGet normalization and ordering |
The first fail-open path: classification
The docs state the behaviour plainly: "Detection is fail-open. If the URL cannot be classified as a package download, the request proceeds as normal non-package traffic. A classification failure will never block or break a package install."
For a rollout that is the right default. Nobody wants a Gateway upgrade to take out every CI pipeline in the company at once. But it changes what a Block policy means. pkg.ecosystem == "npm" does not block npm traffic; it blocks npm traffic that Gateway recognised as an npm download. Anything served on a path shape Gateway does not match is unaffected, and there is no published signal in the docs for how often classification misses.
The narrower version of the same limit is the operation scope. Only download is detected. The docs are explicit that this is the initial release behaviour and give the reason: "Download is the one operation where the package name, version, and ecosystem are all derivable from the URL path across every supported registry. Other operations such as resolve (metadata lookups) and publish use different endpoints, hosts, or HTTP methods that require additional signal beyond the URL path."
That matters for what you can observe. A dependency resolution against a registry's metadata endpoint is invisible to these policies, and so is a publish. Blocking the tarball still stops the install, so the control works for its main purpose. What you do not get is visibility into what a build asked for versus what it actually fetched.
The second fail-open path: unparseable versions defeat !=
This is the one to read twice. The pkg.version selector supports six operators — ==, !=, >, >=, <, <= — with ecosystem-aware semantics rather than string comparison. Then the docs add:
When a version string cannot be parsed by the ecosystem's versioning rules, or when the detected ecosystem does not match the comparison context, the comparison returns no match. This includes !=, meaning an unparseable version does not match anything.Read that against how people write deny rules. The natural way to express "this package is only allowed at the version we pinned" is a Block policy with pkg.name == "some-package" and pkg.version != "1.4.2". Under the documented behaviour, any version string the ecosystem's parser rejects returns no match, the Block rule does not fire, and the download proceeds.
The second clause in that sentence widens it further. A mismatch between the detected ecosystem and the comparison context also returns no match, so a rule written against npm semantics that encounters traffic classified as something else fails the same way.
Positive matching does not have this property. pkg.version < "4.17.21" fires only on versions the parser can order below 4.17.21, so an unparseable version escapes the block, but the rule was never a complete deny to begin with. The trap is specifically the negation, because a reader reasonably expects != to be the complement of == and here it is not. The two are both false for the same input.
The practical rule: build package policies as allowlists, not denylists. Write an Allow policy for the exact ecosystem, name and version you sanction at higher priority, then a broad Block on the ecosystem beneath it. Cloudflare's own worked examples use exactly this two-policy shape for the sanctioned-mirror case, and it is the shape that survives an unparseable version, because the fallthrough lands on the Block rather than past it.
The dashboard is not the API
Five selectors exist. The dashboard does not expose all five, and it constrains how the rest combine.
| Constraint | Dashboard | API (wirefilter) |
|---|---|---|
pkg.purl selector |
Not available | Available |
Multiple ecosystems via in / not in |
Nested name, version and namespace fields are disabled | Selectors combine freely |
| Ordering | Must select an ecosystem before any other package field appears | No ordering requirement |
| Version constraint | Requires a package name to be added first | No prerequisite |
| Namespace field | Shown only for npm, Maven and Go, relabelled Scope, Group ID or Module namespace | Available wherever the ecosystem supports one |
Cloudflare states the difference directly: "When using the API directly, these selectors can be combined freely in wirefilter expressions without these constraints."
The multi-ecosystem restriction is the one that will bite. A policy such as "block lodash below 4.17.21 wherever it appears" cannot be written as a single dashboard rule across npm and Cargo, because the moment you use in to match two ecosystems, the package name and version fields go grey. You either write one rule per ecosystem in the dashboard or move to the API. Teams standardising on Terraform or the API will not notice this at all, which is exactly why it tends to be discovered late, by whoever inherits the dashboard.
pkg.purl being API-only is the smaller gap but the more annoying one for tooling, because Package URL is the identifier most software bill of materials formats already carry. If your SBOM pipeline emits pkg:npm/lodash@4.17.21 strings, you can feed them straight into a wirefilter expression and not into the dashboard.
Range syntax is not supported
The docs carry a second note that will surprise anyone porting existing constraints: "Ecosystem-specific range syntax such as npm ^1.2.3, PyPI ~=1.4, or Maven interval notation is not supported."
So a caret range out of a package.json, a compatible-release clause out of a requirements.txt, or a Maven interval cannot be pasted into a policy. They have to be expanded by hand into the comparison operators. ^1.2.3 becomes a pair of bounds, >= 1.2.3 and < 2.0.0, expressed as two conditions. That is mechanical, but it is also where a hand-translated range quietly drifts from the range your build actually enforces.
TLS decryption is the deployment gate
The docs open with it: "Package registry security requires TLS decryption to be turned on for the traffic you want to inspect."
Package downloads run over HTTPS, and without decryption Gateway sees a hostname and nothing else, which is the state the feature exists to improve on. For organisations that already run Gateway with TLS decryption this is a policy change and nothing more. For organisations that do not, it is a much larger project involving certificate distribution to every developer machine and CI runner, plus a list of applications that pin certificates and will break. Treat the rollout as a decryption programme with a package-policy payoff at the end, not as a rule you turn on this afternoon.
Who is affected, and what to do
You are affected if you run Cloudflare Gateway with TLS decryption and pull packages through it. The value is real: this is registry-protocol-aware blocking that does not depend on a hostname allowlist, which is the control most teams actually want against a compromised or typosquatted dependency. We wrote about the failure mode it targets in defending CI against AI-hallucinated package names, and a name-level block at the proxy is a genuinely useful second layer there.
Four concrete steps. Audit every existing draft policy for pkg.version != and rewrite it as an Allow-then-Block pair. Expand any range syntax by hand and record the expansion next to the rule, so the next person can check it against the manifest. Decide now whether the dashboard or the API is your source of truth, because the two have different expressive power and a rule written in one cannot always be represented in the other. And do not report package registry security as complete coverage in a control register, because the classification layer beneath it is documented as fail-open.
On that last point, this is the same reporting problem we described for Cloudflare CASB auto-remediation: a control that is described as automatic, and is, right up to the boundary where its detection layer stops. Both belong in a register with their exclusions written next to them, the discipline covered in our corporate IT compliance auditing guide.
India-specific considerations
Indian services firms running builds for multiple clients out of one network have a specific version of the dashboard problem. If different clients sanction different versions of the same library, the per-ecosystem, one-name-at-a-time dashboard shape does not scale, and the API is the only practical route. Budget for that rather than discovering it after the first twenty rules.
The TLS decryption prerequisite also has a documentation consequence under the Digital Personal Data Protection Act 2023. Turning on decryption for developer traffic means the organisation is inspecting content it previously could not see, which is a processing change worth recording in the register alongside the security benefit. The mapping approach is in our DPDP Act engineering playbook.
What is still unknown
Cloudflare has not published a classification hit rate, so there is no public figure for how often a real package download goes unrecognised. It has not said whether resolve and publish operations are planned, only that they need signal beyond the URL path. And there is no published list of which version strings each ecosystem parser rejects, which is the document that would tell you how large the != gap actually is in your dependency tree.
FAQ
How eCorpIT can help
We design and operate proxy-layer supply chain controls for teams that already run Zero Trust networking, including the TLS decryption rollout the feature depends on and the certificate distribution that goes with it. Our engineers translate manifest ranges into policy expressions that hold, and document the exclusions so a control register reflects what is actually enforced. eCorpIT is CMMI Level 5 and ISO 27001:2022 certified. Book a supply chain policy review to see what your current rules would let through.
References
Last updated: 23 August 2026. All quotations are taken verbatim from the Cloudflare documentation pages listed above, retrieved on 23 August 2026.