How it works
A registry that asks questions first
ForgeRepo™ answers npm, pip, docker, dotnet, Maven, Bundler, Composer, CocoaPods, SwiftPM, dnf and apt at one address. It fetches from the public registries, keeps a copy, and serves a package only when every check agrees. Nothing is installed straight from the internet, and nothing is refused without a reason.
The path of one request
These run in this order, on the server, for every request. The portal hiding a button is only tidiness: the API makes the decision.
- Kill switch. Beats everything, including allow rules and audit only mode.
- Rules for this token. Whitelist or blacklist, scoped by the token's application and environment.
- Lookalike names. Warns about, or refuses, names that imitate popular packages.
- Fetch or use the cache. Reserved names are never fetched from outside. Degraded and lockdown modes limit what is fetched.
- Holds and policies. Quarantine, safe version resolution for known vulnerabilities, cooling off for brand new releases, and licenses.
- Last checks on the file. A kill by file hash, and an optional malware or image scan before the file is served.
Two ways a version is left out
This is the part that makes a whitelist livable.
In the metadata
When npm or pip asks which versions exist, blocked, killed, too new, quarantined in
strict mode and vulnerable versions are removed from the answer, and latest moves back
to the newest allowed one. The client picks an allowed version on its own, so
npm install lodash just works, on a version you approved. The cached copy is never
edited, the trimming happens on the way out.
On download
A lockfile asks for an exact file. That request runs every check again and gets a
403 with the reason, like blocked by rule lodash in production or the
kill switch reason you typed. Blocked installs open a request by themselves, so the approver
already knows.
Which rule wins
For any package the rules are sorted and the first match wins. Higher priority first. Then a rule scoped to the token's application or environment over one for everyone. Then an exact name over a pattern, and a longer pattern over a shorter one. Then deny before allow. If nothing matches, whitelist mode blocks and blacklist mode allows.
The order you wrote rules in never decides anything, which is what makes a list of a few thousand of them predictable.
Architecture
One container, one data directory
The app, its MariaDB and the cache all live in one image. The only outside piece is a reverse proxy for TLS. No Node, no database and no npm on the host.
Where the data lives
One place, set by DATA_PATH: the database, the blob cache and room for
backups. A named Docker volume by default, or a plain directory you can back up with ordinary tools.
Nothing is written into the container layer, so the image is disposable.
How files are kept
Every cached file, whatever its format, is stored once under its SHA-256. The digest a file is first seen with is the one it keeps, and a changed upstream file raises an integrity alert instead of replacing it.
What it calls out to
The upstream registries you configure, osv.dev for advisories, CISA and FIRST for KEV and EPSS, GitHub or GitLab for the source archives CocoaPods, Swift and Composer releases point at, and api.github.com if you let it fetch runner ranges. The known malicious check sends names and versions to osv.dev, never files. Turn upstream off and it never calls out again. There is no telemetry.
Reserved names stop dependency confusion
A reserved scope or prefix is never fetched from a public registry, and every other scope routes to exactly one upstream by pattern, with no silent fallback. A public package with your internal name simply cannot be installed through here.
Normal, degraded, lockdown
For the week an ecosystem is on fire. Degraded stops fetching names the box has never seen. Lockdown stops fetching anything at all and resolves every range among what is already cached, so builds keep working from what you hold. Raising the mode takes an approver and a reason. Lowering it takes an admin.
Five roles, checked on every request
Permissions are enforced by the API. The portal only hides what you cannot use.
| Role | What it can do |
|---|---|
| viewer | Read the rules, packages and vulnerabilities, review an uploaded file, and export the rules. |
| developer | The above, plus ask for packages, walk dependency trees and manage their own tokens. |
| publisher | The above, plus publish npm and PyPI packages, push images and push NuGet packages, under the reserved names. Usually a CI account. |
| approver | The above, plus edit rules, decide requests and waivers, purge packages and use the kill switch. |
| admin | Everything, including users, settings, access control, traffic and the audit trail. Settings are admin only to read as well as write. |
When it goes wrong
Responding to a malicious release
Kill it, find who pulled it, find the applications, fix, lift.
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.