The Registry Is a Risk in 2025: Secure Dependencies with Private Mirrors, Vendoring, and Air‑Gapped Builds
If your builds depend on public package registries being online and trustworthy at the exact moment you compile, you have a reliability and security problem. Its 2025; outages happen, namespaces get hijacked, maintainers credentials get phished, and malicious lookalike packages arrive faster than security advisories do. The good news: you can design your supply chain so that public registries are inputs to your systems resilience rather than its single point of failure.
This article lays out a pragmatic, opinionated playbook for engineering leaders and platform teams to harden software dependency flows across JavaScript/TypeScript, Python, Java, Go, Rust, and containers. The focus is practical: adopt upstream-mirroring proxies, treat lockfiles as contracts, vendor when warranted, warm your caches, generate SBOMs, require provenance attestations, practice disaster cutovers, and enforce org-wide registry policies.
Thesis
- Public registries (npm, PyPI, Maven Central, crates.io, etc.) are essential but should be treated as unreliable and untrusted at build time.
- Resilience and integrity come from a layered strategy: mirror upstream, pin deterministically, verify cryptographically, and maintain an offline plan.
- The right posture is secure-by-default, internet-optional for builds.
Why registries are a risk in 2025
- Outages and rate limits: Registry downtime still happens; even brief service degradation can stall CI/CD across an org. The blast radius is high because almost all builds fetch at the same time.
- Tampering and compromised packages: Typosquatting, dependency confusion, namespace takeovers, compromised maintainer accounts, and malicious postinstall scripts continue across ecosystems. Industry reports from Sonatype (State of the Software Supply Chain) and OpenSSF consistently show upward trends in malicious package attempts.
- Trust-at-fetch time: Most build tools implicitly trust whatever the registry returns at fetch time unless you lock and verify. Even when registries implement additional checks, your builds are still exposed to timing and trust gaps unless you bind content to cryptographic expectations.
- Transitive sprawl: A project with 20 direct dependencies can easily pull in hundreds of transitive packages; one compromised transitive can be enough to exfiltrate secrets or inject backdoors.
- Container and base image drift: You might pin app dependencies but still pull unpinned base images or system packages during build, introducing supply chain variability.
Treat public registries as upstream sources to ingest, verify, and replicatenot as the place to reach out to during every build.
The pragmatic playbook
The following nine practices reinforce each other. Start with pinning and proxying; then layer in vendoring, attestation, and drills.
- Pin everything and attest provenance
- Commit lockfiles and enforce deterministic installs.
- Pin container images by digest.
- Record provenance with in-toto/SLSA attestations and keep SBOMs alongside artifacts.
- Upstream-mirroring proxies
- Place a private artifact proxy between builders and the internet.
- Proxy per-ecosystem registries (npm, PyPI, Maven Central, crates.io, Go proxy, Docker registries).
- Configure clients to talk only to your proxy.
- Deterministic lockfiles and hermetic builds
- Ensure installs fail if lockfiles are out of date or dependencies drift.
- Avoid running arbitrary install-time scripts unless necessary and reviewed.
- Strive for hermetic builds that dont depend on ambient network access.
- Vendoring rules
- Vendor source or tarballs when your uptime or compliance needs demand it, especially for long-lived releases and air-gapped deployments.
- Cache warming and offline readiness
- Pre-fetch and stage the exact artifacts referenced by lockfiles into your proxy and caches.
- SBOMs and SLSA-based controls
- Generate SBOMs (SPDX/CycloneDX) and publish provenance attestations for every build.
- Disaster cutover drills
- Practice building with the internet disabled. Measure RTO and fix gaps.
- Org-wide registry policies
- Standardize on a single proxy per ecosystem, block direct egress, and codify exceptions.
- Continuously monitor and rotate
- Track usage, rotate credentials, and keep the proxy and signing stack updated.
The rest of this article goes deep on the how.
1) Pin everything and attest provenance
Pinning binds your builds to known content, not just names and versions.
-
JavaScript/TypeScript
- Commit package-lock.json, pnpm-lock.yaml, or yarn.lock.
- Enforce install flags that refuse to modify the lock:
- npm:
npm ci
(notnpm install
), optionally--ignore-scripts
when possible. - Yarn:
yarn install --immutable
(Berry) or--frozen-lockfile
(Classic). - pnpm:
pnpm install --frozen-lockfile
orpnpm fetch
+pnpm install --offline
.
- npm:
-
Python
-
Use pip-tools to generate fully pinned
requirements.txt
with hashes:bashpip-compile --generate-hashes -o requirements.txt requirements.in pip install --require-hashes -r requirements.txt
-
Poetry users should commit
poetry.lock
and usepoetry install --sync
.
-
-
Java/Maven & Gradle
- Maven: Use dependencyManagement to pin versions; add Enforcer rules for dependency convergence and plugin version pinning.
- Gradle: Use version catalogs or lockfiles (
gradle --write-locks
), and set reproducible build flags.
-
Go
- Commit
go.mod
andgo.sum
. Enforce-mod=readonly
in CI.
- Commit
-
Rust
- Commit
Cargo.lock
. Usecargo build --locked
in CI.
- Commit
-
Containers
- Pin by digest:
FROM alpine@sha256:<digest>
rather than tags. Mirror base images to your private registry and use those internal URLs.
- Pin by digest:
Attest provenance and integrity
- Generate SBOMs (CycloneDX or SPDX) for every build.
- Produce SLSA provenance attestations that tie artifacts to source, builder, and inputs.
- Sign and store both alongside artifacts in your repository/registry.
- Verify attestations at deploy time.
Example: generate and attach an SBOM for a container image using Syft and Cosign:
bash# Build image (pinned base image) docker build -t registry.company.example/app:1.2.3 . # Generate SBOM (CycloneDX JSON) syft registry.company.example/app:1.2.3 -o cyclonedx-json > sbom.cdx.json # Sign image and attach SBOM attestation cosign sign --key cosign.key registry.company.example/app:1.2.3 cosign attest \ --key cosign.key \ --type cyclonedx \ --predicate sbom.cdx.json \ registry.company.example/app:1.2.3
Store attestations in your registry and verify in CI/CD. For GitHub-hosted builds, use keyless signing with OIDC identities and Rekor transparency logs via Sigstore.
2) Upstream-mirroring proxies (private registries)
Put a controlled, auditable proxy in front of public registries. Options:
- General-purpose: Sonatype Nexus, JFrog Artifactory, AWS CodeArtifact, Azure Artifacts, Google Artifact Registry, Harbor (containers)
- Ecosystem-specific: Verdaccio (npm), devpi (PyPI), Athens (Go), a private crates index for Rust
Best practices
- Repository per ecosystem with remote (upstream) proxy + local hosted repos for internal packages.
- Quarantine new artifacts for scanning before promotion.
- Retention policies and immutability for released artifacts.
- TLS termination with your corporate CA; short-lived, scoped credentials for CI/CD.
Client configuration examples
Node.js (.npmrc)
registry=https://registry.company.example/npm/
always-auth=true
@your-scope:registry=https://registry.company.example/npm/
audit=false
fund=false
engine-strict=true
fetch-retries=5
fetch-retry-factor=2
fetch-retry-mintimeout=1000
fetch-retry-maxtimeout=60000
Python (pip.conf)
[global]
index-url = https://pypi.company.example/simple
retries = 5
timeout = 30
trusted-host = pypi.company.example
[install]
require-hashes = true
Poetry
bashpoetry config repositories.company https://pypi.company.example/simple poetry config http-basic.company token <YOUR_TOKEN> poetry install --sync --no-root
Maven (settings.xml) system- or user-level, not in repo
xml<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd"> <mirrors> <mirror> <id>central-proxy</id> <name>Company Central Proxy</name> <url>https://maven.company.example/repository/maven-all/</url> <mirrorOf>central,*,!company-releases,!company-snapshots</mirrorOf> </mirror> </mirrors> </settings>
Gradle (settings.gradle)
groovypluginManagement { repositories { maven { url 'https://maven.company.example/repository/maven-all/' } } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { maven { url 'https://maven.company.example/repository/maven-all/' } } }
Go
bash# Prefer internal proxy, then public, then direct fallback if policy allows export GOPROXY=https://athens.company.example,https://proxy.golang.org,direct # Respect checksum database for public modules export GOSUMDB=sum.golang.org # Dont use SUMDB for internal domains export GONOSUMDB=company.example/*
Rust (.cargo/config.toml)
toml[source.crates-io] replace-with = "company-crates" [source.company-crates] registry = "https://crates.company.example/index" [net] git-fetch-with-cli = true
Containers (Docker/OCI)
- Mirror upstream base images to an internal registry (e.g., Harbor, Artifactory).
- In CI, enforce pulling only from internal registry, and pin digests.
bashdocker pull registry.company.example/library/alpine@sha256:<digest> # Use internal URL in Dockerfile FROM registry.company.example/library/alpine@sha256:<digest>
Policy: block direct egress from CI and developer networks to public registries. All traffic flows through your proxy.
3) Deterministic lockfiles and hermetic builds
Make the lockfile the contract. Builds should fail if the lockfile is inconsistent or changes would be needed.
Ecosystem-specific enforcement
- npm
npm ci
rather thannpm install
. Add--ignore-scripts
for non-runtime packages if possible.
- Yarn
- Yarn Berry:
yarn install --immutable
; Classic:--frozen-lockfile
.
- Yarn Berry:
- pnpm
pnpm install --frozen-lockfile
orpnpm fetch
+pnpm install --offline
.
- Python
pip-compile --generate-hashes
andpip install --require-hashes
to ensure exact artifacts by hash.
- Go
go build -mod=readonly
or setGOFLAGS=-mod=readonly
in CI.
- Rust
cargo build --locked
.
- Maven
- Use Enforcer to require plugin versions and dependency convergence.
- Gradle
- Enable lockfiles (
--write-locks
initially) and--offline
mode for outage drills.
- Enable lockfiles (
Hermetic techniques
- Use Bazel/Pants/Buck/Nix where appropriate to isolate builds from the host and control all inputs; or simulate hermeticity in existing toolchains by:
- Disabling network during build steps after dependency fetch.
- Building inside minimal containers with only the proxy reachable.
- Freezing environment variables, toolchain versions, and locale/timezone.
- Avoid install-time code execution:
- Node: beware of postinstall scripts; consider
--ignore-scripts
in CI and whitelist required packages. - Python: prefer wheels over sdists; build wheels in a controlled builder stage and reuse.
- Node: beware of postinstall scripts; consider
4) Vendoring rules
Vendoring means bringing dependency artifacts (sources or wheels/tarballs) into your repo or an internal, immutable store so builds dont need the internet.
When to vendor
- Release trains that must build reproducibly years later.
- Regulated, air-gapped environments.
- Critical systems where the cost of registry unavailability is high.
Strategies by ecosystem
-
Go
-
go mod vendor
creates avendor/
directory. Enforce with-mod=vendor
in CI.bashgo mod vendor export GOFLAGS=-mod=vendor go build ./...
-
-
Rust
-
Use
cargo vendor
for a local source mirror, then--offline
builds.bashcargo vendor vendor > .cargo/config.toml cargo build --offline
-
Alternatively, point to an internal crates index via
.cargo/config.toml
.
-
-
Python
-
Pre-download wheels and point pip at a local directory.
bashpip download -r requirements.txt -d vendor pip install --no-index --find-links vendor -r requirements.txt
-
-
JavaScript/TypeScript
- Yarn Berry zero-install (commit
.yarn/cache
) or pnpms content-addressable store usingpnpm fetch
and committing the store in exceptional cases. Purely committingnode_modules
is fragile and large; prefer an internal proxy or a curated tarball artifact for releases.
- Yarn Berry zero-install (commit
-
Java
- Prefer proxying rather than vendoring. For offline builds, stage a local Maven repo snapshot as part of the build cache artifact.
Trade-offs
- Pros: Strongest defense against outages, deterministic inputs, simplifies air-gapped builds.
- Cons: Repository bloat, update friction, license scanning burden shifts into your repo.
Use vendoring selectively where the resilience and compliance wins justify it.
5) Cache warming and offline readiness
The time to fetch is before the outage.
-
Pre-fetch dependencies referenced by lockfiles into your proxy and caches on a schedule (e.g., nightly):
- npm/pnpm/Yarn: run CI tasks that do
npm ci --ignore-scripts
orpnpm fetch
to populate the proxy. For Yarn Berry, ensure.yarn/cache
is current. - Python:
pip download -r requirements.txt -d <cache>
anddevpi
/Artifactory prefetch. - Maven:
mvn -o dependency:go-offline
to resolve transitive deps into the local cache, then publish a cache artifact for hermetic build jobs. - Gradle: use dependency verification and build cache warms via a bot job; Gradle Enterprise or a self-hosted remote cache helps.
- Go:
go mod download all
with your GOPROXY set to the internal proxy. - Rust:
cargo fetch
against your mirror or aftercargo vendor
.
- npm/pnpm/Yarn: run CI tasks that do
-
Keep an panic button cache artifact:
- Produce a tarball/zip of pre-fetched artifacts that correspond exactly to a releases lockfiles.
- Store it in your artifact repository; during a registry incident, switch CI to use that artifact and offline flags.
-
Warm container layers and base images
- Mirror commonly used base images and OS packages (apk/deb/rpm repos) internally.
- Build your own minimal base images from pinned packages and publish internally.
-
TTL and eviction
- Tune proxy retention to keep exactly what your supported releases need; keep LTS lines longer.
6) SBOMs and SLSA-based controls
Integrate SBOMs and provenance into every build.
SBOM generation
- Containers: Syft, Trivy, Anchore, cdxgen can emit CycloneDX or SPDX.
- Java: CycloneDX Maven plugin (
cyclonedx:makeAggregateBom
) or Gradle CycloneDX plugin. - Node: cdxgen or
npm ls --json
piped into SBOM generators. - Python: pip-audit can enumerate; combine with cdxgen for CycloneDX.
- Go/Rust: cdxgen supports these; additional tools exist for cargo ecosystems.
Example Maven snippet to generate CycloneDX:
xml<plugin> <groupId>org.cyclonedx</groupId> <artifactId>cyclonedx-maven-plugin</artifactId> <version>2.8.0</version> <executions> <execution> <phase>package</phase> <goals> <goal>makeAggregateBom</goal> </goals> </execution> </executions> </plugin>
Provenance and SLSA
- Use SLSA provenance attestations to record who built what, from which source and dependencies. Many CI platforms can emit in-toto attestations.
- Sign attestations with Sigstore Cosign. For GitHub Actions or GitLab, use OIDC-based keyless flows to avoid long-lived keys.
- Store attestations alongside artifacts in your registry and verify at deploy with a policy engine (e.g., OPA/Conftest) that checks:
- artifact digest matches the attestation
- builder identity is from your org
- source repository and ref match expected
- timestamps are recent enough and within allowed windows
Align with frameworks
- NIST SSDF (SP 800-218): codify internal controls around dependency management and build integrity.
- SLSA levels: target Level 2+ for provenance and tamper-resistant builds; Level 3+ for hardened, isolated builders.
7) Disaster cutover drills
Practice outages on your schedule, not the registrys.
-
Simulate an outage
- Temporarily block egress to public registries at the network layer for a test project or environment.
- Alternatively, point registry hostnames to a blackhole IP in a test VPC or via
/etc/hosts
on a runner.
-
Run the build with offline flags
- npm: test with
npm ci
against your proxy; optionally use--prefer-offline
or--offline
with pnpm/yarn when stores are pre-populated. - pip:
--no-index --find-links
pointing to a local directory or rely solely on your proxy. - Maven:
mvn -o package
with your proxy populated. - Gradle:
gradle --offline build
. - Go: ensure GOPROXY resolves internally; if fully offline, vendor and
-mod=vendor
. - Rust:
cargo build --offline
with vendor or internal index.
- npm: test with
-
Measure
- RTO: time to complete a clean build without internet.
- Coverage: fraction of services that can build offline.
- Gaps: missing transitive artifacts, base images, or misconfigured tooling.
-
Remediate
- Add prefetch steps to CI to populate proxy/local caches.
- Mirror missing images.
- Fix client configs to eliminate direct egress.
-
Document the runbook and repeat quarterly.
8) Org-wide registry policies
Write policy so engineers dont have to guess.
-
Single source of truth per ecosystem
- One internal registry endpoint per ecosystem (npm, PyPI, Maven, Go proxy, crates index, Docker registry).
- Disallow direct use of public registries in CI and developer environments (enforced via egress controls).
-
Mandatory lockfiles and deterministic installs
- Reject PRs that modify lockfiles without human review.
- CI must use
npm ci
,yarn --immutable
,pip --require-hashes
,cargo --locked
, etc.
-
Quarantine and scanning
- New upstream artifacts pass through malware scanning and license checks before promotion to blessed repos.
-
Attestations required for deploy
- Images and release archives must have valid SBOMs and SLSA provenance.
-
Exceptions process
- Document a time-bound exception path to fetch an urgent dependency from upstream, with follow-up to mirror and pin.
-
Identity and access
- Short-lived tokens for CI/CD via OIDC or workload identity.
- 2FA for maintainers of internal packages.
-
Audit and metrics
- Track registry hits, cache hit rates, and package adoption. Alert on direct egress attempts.
9) Reference architecture: registry airgap without developer misery
A practical layout that balances control with developer experience:
-
Control plane
- Artifact manager (Nexus/Artifactory/Harbor) with repositories per ecosystem: remote proxies + hosted local.
- Scanning and policy gates (malware, license, provenance checks) on ingress to local repos.
-
Data plane
- CI runners in subnets that can reach only the artifact manager and internal Git; no egress to the internet.
- Builders use per-ecosystem configs that point exclusively to the proxy.
-
Developer experience
- Dev machines talk to the proxy by default (read-only), with higher egress limits than CI but still blocked from direct registry access.
- Fast feedback via warmed caches; local development unaffected during upstream incidents.
-
Release path
- Build pipeline: fetch (from proxy) build generate SBOM and provenance sign push to internal registry promotion.
- Deploy pipeline verifies signatures and provenance before rollout.
-
Air-gapped sites
- Periodic replication of blessed repos and base images to the sites local artifact manager.
- Builds run fully offline using vendored or mirrored artifacts.
10) Rolling adoption plan (Day 0 Day 90)
Day 0 7: Quick wins
- Create internal proxies for npm, PyPI, Maven Central, and a private container registry.
- Update CI images to include client configs pointing to proxies.
- Enforce lockfile usage and deterministic install flags in CI.
- Pin container base images by digest and pull from the internal registry.
Day 8 30: Stabilize
- Add prefetch/cache-warm jobs keyed to lockfiles.
- Mirror commonly used base images and OS repos.
- Enable scanning/quarantine flows in the artifact manager.
- Generate SBOMs for all builds and publish alongside artifacts.
- Start signing images; pilot provenance attestations on a few services.
Day 31 60: Drill and enforce
- Cutover drill: block public registries for a representative service set; measure and fix.
- Tighten CI egress policies.
- Add Maven Enforcer and Gradle dependency verification; adopt
pip-compile --generate-hashes
in Python repos.
Day 61 90: Harden and scale
- Roll out organization-wide policies and guardrails.
- Require provenance for deploy.
- Vendor selectively for critical systems and air-gapped sites.
- Automate exception workflows and artifact promotion.
11) Examples: end-to-end per ecosystem
Node (npm/pnpm/Yarn)
- CI stage
bash# npm npm ci --ignore-scripts # yarn berry yarn install --immutable # pnpm pnpm fetch pnpm install --offline --frozen-lockfile
- Disaster drill
bash# Block egress in CI job or network, then: pnpm install --offline --frozen-lockfile
Python
bash# Resolve and lock with hashes pip-compile --generate-hashes -o requirements.txt requirements.in # Prefetch and test offline install pip download -r requirements.txt -d vendor pip install --no-index --find-links vendor --require-hashes -r requirements.txt
Maven/Gradle
bash# Maven offline resolution mvn -o dependency:go-offline mvn -o -DskipTests package # Gradle lockfiles and offline build gradle --write-locks # once, to create locks gradle --offline build
Go
bash# Resolve with internal proxy and vendor for offline go mod tidy go mod vendor GOFLAGS=-mod=vendor go build ./...
Rust
bash# Vendor and build offline cargo vendor vendor > .cargo/config.toml cargo build --offline
Containers
Dockerfile# Use internal, pinned base image FROM registry.company.example/library/alpine@sha256:<digest>
bash# Prefetch and cache base images docker pull registry.company.example/library/alpine@sha256:<digest>
12) Common objections and responses
-
Mirrors add complexity.
- They add a control point that pays for itself the first time a registry hiccups or a malicious package slips through. With templates and shared configs, developer overhead is minimal.
-
Vendoring is heavy.
- Agreed; use it selectively. For most teams, proxies + lockfiles + cache warming is enough. Vendor only for air-gapped or high-criticality systems.
-
We already run
npm audit
andpip-audit
.- Useful, but thats vulnerability detection, not integrity or availability. You still need pinning, provenance, and mirrors.
-
Reproducible builds are hard.
- Full reproducibility can be, but you can achieve deterministic dependency resolution and hermeticity quickly with the flags and configs above.
13) Secure-by-default defaults you can adopt now
- Enforce lockfile presence and deterministic install flags in CI.
- Route all dependency traffic through your internal proxies; block direct egress.
- Pin base images by digest; mirror them internally.
- Generate SBOMs and sign artifacts; store provenance.
- Warm caches nightly for active branches/releases.
- Run a monthly offline drill for at least one service per language ecosystem.
14) References and further reading
- NIST SP 800-218 (SSDF): https://csrc.nist.gov/publications/detail/white-paper/2022/02/04/ssdf/release
- SLSA Framework: https://slsa.dev
- Sigstore (Cosign, Fulcio, Rekor): https://sigstore.dev
- in-toto: https://in-toto.io
- CycloneDX: https://cyclonedx.org
- SPDX: https://spdx.dev
- Sonatype State of the Software Supply Chain: https://www.sonatype.com/state-of-the-software-supply-chain
- OpenSSF Scorecards: https://securityscorecards.dev
- Bazel Reproducible Builds: https://bazel.build
Conclusion
Public registries remain an invaluable commons, but they should no longer be your build-time dependency. In 2025, the resilient posture is to ingest from upstream under your control, pin deterministically, verify cryptographically, and be able to build with the internet turned off. With upstream-mirroring proxies, deterministic lockfiles, sensible vendoring, warmed caches, SBOMs and SLSA attestations, and routine disaster cutover drills, you transform dependency management from a gamble into a system.
Adopt the simplest version of this playbook now: set up proxies, enforce lockfile-only installs, and pin base images. Then layer in attestation, offline drills, and selective vendoring. Your future self and your incident response team will thank you the next time a registry wobbles or an attacker tries to slip something into your transitive graph.