Keep bad packages out
Allow lists and block lists for every package type
Every package your developers and pipelines pull goes past one set of rules. Whitelist mode lets through only what somebody approved. Blacklist mode lets through everything except what you name. Either way the rule is enforced on the server, for every request.
A rule is a name, a kind (allow or deny), an optional version range and a priority. The name can be exact (lodash), a scope (@acme/*) or a wildcard. The range takes anything npm itself understands: 1.1.1, 1.2.*, ^1.2.0, ~1.2.0, >=4.17.21 or 1.2.3 || 1.4.5. PyPI rules take the specifiers pip uses, and image rules take tags, globs like 1.25.*, or digests.
The other types each take their own spelling, and a bare version always means exactly that version:
- NuGet: ids ignore case, so
Microsoft.Extensions.*is a rule. Ranges take NuGet's brackets ([13.0,14.0)), floating versions (13.*) and comparators (>=13.0 <14). - Maven: names are
groupId:artifactId, likeorg.apache.logging.log4j:log4j-coreororg.apache.maven.plugins:*, with Maven's brackets such as[2.17,2.18). - RubyGems and CocoaPods: gem requirements like
~> 4.0or>= 1.2, < 2. - Composer:
vendor/nameorsymfony/*, with composer.json constraints like^3.0. - Swift: identities like
apple.swift-logorapple.*, with semver ranges. - RPM and APT: exact package names, and exact versions or comparators like
>=3.0.7, compared the way rpm and dpkg compare them.
When two rules match, the higher priority wins, then the narrower scope, then the exact name over a pattern, then deny over allow. If nothing matches, whitelist mode blocks and blacklist mode allows. It is the same order every time, and the Rules page says so at the top.
The part that makes this work in practice is the metadata. When npm asks which versions of a package exist, the versions a rule does not allow are removed from the answer and latest moves back to the newest allowed one. Every other type works the same way: the PyPI project page, the NuGet version list, maven-metadata.xml, the gem info file, the pod shards, the Composer and Swift release lists are all trimmed to what the rules allow. RPM and APT mirrors do this when set to a filtered index, and otherwise refuse the download. So npm install lodash quietly picks an allowed version rather than failing. A lockfile that names a blocked version by hand gets a 403 with the reason in it.
Case is handled on purpose. A deny matches whatever the case, because a block you can dodge by typing Event-Stream is not a block. An allow matches exactly, because approving JSONStream must not also approve jsonstream, which is a different package by a different author.
In short
- Whitelist or blacklist, with the whitelist as the default
- Exact names, scopes like
@acme/*, wildcards and each type's own version ranges - Blocked versions are removed from every type's version list or index before the client sees them
- Rules can be limited to one application, one environment, or both
- Audit only learning mode serves everything and opens a request for whatever no rule covers
- Import and export as JSON or CSV with no limit on size, in one transaction
- Cache the ticked rules downloads approved versions now, so the kill switch has something to serve
In the documentation
- Whitelist, blacklist and audit mode Administrator Guide
- Writing rules Administrator Guide
- Applications, environments and token scope Administrator Guide
Goes well with
- Requests and auto approve A blocked install opens a request. Clean ones can approve themselves, risky ones wait for a person.
- Dry run Replay up to 90 days of real downloads against a rule before anybody switches it on.
- Tokens, apps and environments Every download is tied to a token, an application and an environment. Rules can be scoped to them.
One container, about two minutes
A Linux box with Docker, or one without it, and a reverse proxy for TLS. The installer does the rest and it is safe to run twice. Free, MIT licensed, nothing to sign up for.