Product

Overview

How it works Watch it work All 35 features Screenshots How ForgeRepo™ is secured

Keep bad packages out

Malware scanning Typosquat detection Dependency confusion protection Cooling off new releases Allow lists and block lists

When something goes wrong

Kill switch Who has it Lockdown and degraded modes Vulnerability monitoring

Developers and approvals

Requests and auto approve Check, walk and review Waivers Dry run
Formats

Languages

Private npm registry PyPI proxy and private index Private NuGet feed and proxy Maven repository proxy RubyGems mirror Composer and Packagist proxy

Apple, containers and Linux

CocoaPods CDN mirror Swift package registry Docker registry mirror RPM mirror for dnf and yum APT mirror for Debian and Ubuntu

Use it as

An npm firewall Artifacts, SBOMs and lifecycle More than one node SSO, roles and allow lists
Learn

Guides

Learn secure development Secure coding Secure pipelines Unsafe vs safe Security and development

Supply chain

Supply chain attacks, 2016 to 2026 Software supply chain security
Compare Side by side, all four ForgeRepo™ vs JFrog ForgeRepo™ vs Sonatype ForgeRepo™ vs Cloudsmith
Docs

Start

Getting started Download All documentation Questions

For developers

npm setup pip setup docker login Push a Docker image CI basics

For administrators

First setup Rules and their order Six incidents, walked through Backup and upgrade
Pricing Install it

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.

  1. Kill switch. Beats everything, including allow rules and audit only mode.
  2. Rules for this token. Whitelist or blacklist, scoped by the token's application and environment.
  3. Lookalike names. Warns about, or refuses, names that imitate popular packages.
  4. Fetch or use the cache. Reserved names are never fetched from outside. Degraded and lockdown modes limit what is fetched.
  5. Holds and policies. Quarantine, safe version resolution for known vulnerabilities, cooling off for brand new releases, and licenses.
  6. Last checks on the file. A kill by file hash, and an optional malware or image scan before the file is served.
npm, pip ordocker asks1. Kill switch2. Rules forthis token3. Lookalike name check4. Fetch or use the cache(reserved names, registrymode)5. Quarantine, vulnerabilities,cooling off, licenses6. File hash kill and scanbefore serve403 with the reason, maybea requestServedany check says no
Metadata requests go through the same checks and simply leave out versions that would fail.

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.

Higher priorityNarrower scope(app, env)Exact name, thenlonger patternDeny beforeallowWinner decidesNo rule matches: whitelist blocks,blacklist allowstietietie

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.

Laptops: npm, pip,dockerCI pipelines andKubernetesThe portal in abrowsernginx or anyTLS proxyForgeRepo™ containerNode app, checks andportalMariaDB insideBlob cache on /datanpmjs, PyPI, DockerHub, GHCR, your ownOSV, CISA KEV, FIRSTEPSSClamAV, optional,same compose fileOptional: shared MariaDB or RDS, S3 or Azurebucket, SIEM, OIDChttpsfetch if allowed
Everything in the dark box is one container. Everything on the right is optional.

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.

npm install@acme/widgetsForgeRepo™Is @acme/*reserved?Serve the copypublished herePublic npm is neveraskedyesyes

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.

NormalFetches anything the rulesallowDegradedNo brand new names arefetched. Names already heldstill refreshLockdownNothing is fetched. Onlyfiles on disk are offered.Quarantine acts strictraiseraise

Five roles, checked on every request

Permissions are enforced by the API. The portal only hides what you cannot use.

RoleWhat it can do
viewerRead the rules, packages and vulnerabilities, review an uploaded file, and export the rules.
developerThe above, plus ask for packages, walk dependency trees and manage their own tokens.
publisherThe above, plus publish npm and PyPI packages, push images and push NuGet packages, under the reserved names. Usually a CI account.
approverThe above, plus edit rules, decide requests and waivers, purge packages and use the kill switch.
adminEverything, 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.

1. Kill it, with areason2. Who pulled it(last 30 days)3. Consumers: appsand pipelines4. Fix, then liftthe kill

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.