Set one registry or mirror setting per ecosystem in a file that CI and developers share: the registry option for npm, the index setting for pip, a mirror entry in settings.xml for Maven, source replacement for Cargo, and the GOPROXY variable for Go. Then verify with a test that direct access fails.
The setting for each ecosystem
| Ecosystem | Where the setting lives | What it controls |
|---|---|---|
| npm | npmrc, the registry option | Base URL of the registry, default is the public one |
| pip | pip config or environment, the index URL | Primary index; extra indexes are searched with no priority |
| Maven | settings.xml, a mirror element with mirrorOf | Which repository ID the mirror replaces |
| Cargo | config.toml, source replacement | Replaces crates.io with another source via replace-with |
| Go | GOPROXY environment variable | Ordered list of module proxies, with direct and off as special values |
Each of these is documented by the project that owns the tool, and the sources are listed below.
Traps worth knowing
npm lockfiles carry URLs. The npm config documentation describes a replace-registry-host option. By default, npm replaces package URLs from the default registry with your configured registry when it resolves a lockfile. Check that this behaviour matches what you expect, because a lockfile with hardcoded public URLs can otherwise send requests around the mirror.
Maven allows only one mirror per repository. The Maven mirror guide notes that there can be at most one mirror for a given repository, and Maven will not aggregate mirrors; it picks the first match. Use a single group endpoint on your repository manager instead of listing several mirrors.
Cargo can replace a source wholesale. The source replacement reference shows crates.io replaced by naming another source with replace-with. That includes a vendored directory, so the same mechanism supports both a mirror and an offline copy.
Go has fallbacks. The Go modules reference describes GOPROXY as a list. Entries can be proxy URLs or special values: direct tells the go command to talk to version control systems, and off allows no network communication. A trailing direct quietly reopens the door you meant to close.
A rollout in four steps
- Publish a standard config file per ecosystem in a shared repository, owned by the platform team.
- Bake the file into your CI images and developer onboarding scripts.
- Block direct egress to the public registries from build networks, so a missed config fails loudly.
- Add a CI job that fails if any lockfile, Dockerfile or script references a public registry host directly.
Prove it works
Run a clean build on a runner with the mirror disabled and the public hosts blocked. If the build still succeeds, something is resolving from a cache or another path, and you should find it before an attacker does. Keep the result as evidence, with the date and the config version.
Where Safeguard fits
Safeguard's asset discovery and dependency scanning can show you which projects and build pipelines exist, which is the list you need before rolling out shared config. Its policies can then check what each build pulled in. The free scan is a way to start with one repository.
Quick check
Is one mirror product needed for all ecosystems?
Not necessarily. Many teams use a single repository manager that speaks several protocols, but separate mirrors work if each one is governed by the same intake rules.
What if a developer needs a package the mirror does not have?
They file an intake request. The request path must be fast enough that bypassing it is not tempting.
Sources
- npm config documentation
- pip install reference
- Maven guide to mirror settings
- Cargo source replacement
- Go modules reference
Last reviewed: 19 October 2026.