A polyglot organisation publishes to and consumes from several package registries, and each one offers different account, publishing, signing and namespace controls, so a single "secure our packages" policy is applied unevenly or not at all. npm, PyPI, Maven Central, NuGet, crates.io, RubyGems.org and the Go module ecosystem have converged on some ideas, notably OIDC-based trusted publishing and provenance, but they differ in what is mandatory, what is optional and what does not exist. This guide gives one comparative view for security architects, platform teams and open source programme offices: the controls each registry offers as checked on 27 October 2026, how to set a common baseline for publishing, how to configure consumption safely, and what to monitor. Registry features change often, so confirm the current documentation before relying on a specific detail.
Key takeaways
- Trusted publishing is now the common baseline. npm, PyPI, NuGet, RubyGems.org and crates.io accept short-lived OIDC credentials from CI instead of long-lived API tokens; Maven Central and the Go ecosystem work differently.
- Old tokens undo new controls. Turning on trusted publishing does not revoke existing API tokens; you must disable or delete them, as npm's "disallow tokens" setting is designed to do.
- Signing models differ by ecosystem. Maven Central requires PGP signatures on every file, NuGet supports author and repository X.509 signatures, npm and PyPI use Sigstore-based provenance and attestations, and Go relies on a checksum database rather than signatures.
- Namespace control is uneven. Maven Central verifies domain or account ownership, NuGet offers ID prefix reservation and npm has scopes; PyPI, RubyGems and crates.io use a flat name space where typosquatting defences are the registry's job.
- Consumers carry half the risk. Hash-pinned lockfiles, an internal proxy or mirror, and verification of provenance or signatures are what protect you when a maintainer account is taken over.
A common yardstick: the OpenSSF principles
The OpenSSF Securing Software Repositories Working Group publishes Principles for Package Repository Security (v0.1, last updated February 2024), which grade registries from Level 0 to Level 3 across four tracks. It is the best neutral benchmark for comparing registries and for deciding what your own internal registry should offer.
| Track | Level 1 | Level 2 | Level 3 |
|---|---|---|---|
| Authentication | Verified email, documented recovery, TOTP MFA at minimum, alerts on security changes, brute force protection | Phishing-resistant MFA (WebAuthn) supported, MFA required for critical packages, leaked credential checks, abandoned email domain detection | Passkeys, MFA required for all maintainers, phishing-resistant MFA for critical packages |
| Authorisation | API tokens scoped to packages, registry-specific token prefix | Role-based access for maintainers | Short-lived tokens via OIDC (trusted publishers), tokens in secret scanning programmes, build provenance |
| General | Vulnerability disclosure policy, typosquatting defences | Unpublish policy preventing version and name reuse, malware reporting and scanning, vulnerability warnings | Periodic security reviews, event transparency log, malicious package advisories in OSV format |
| CLI tooling | Install pinned by hash or version | Warn on known vulnerabilities at install | Produce SBOMs, automated remediation, reachability analysis |
Registry comparison: publishing controls
The table summarises publishing-side controls from each registry's own documentation, checked on 27 October 2026. "Trusted publishing" means the registry exchanges a CI provider's OIDC token for a short-lived upload credential.
| Registry | Trusted publishing | Account and MFA controls | Publish-time integrity |
|---|---|---|---|
| npm | Yes: GitHub Actions (GitHub-hosted runners), GitLab.com shared runners, CircleCI cloud; self-hosted runners not supported. Needs npm CLI 11.5.1+ and Node 22.14.0+. Up to 10 trusted publishers per package | 2FA; per-package setting "Require two-factor authentication and disallow tokens"; staged publishing (npm stage publish) requires a maintainer to approve with 2FA | Provenance attestations signed through Sigstore, generated automatically with trusted publishing from GitHub Actions and GitLab (not CircleCI, not private source repositories) |
| PyPI | Yes: GitHub Actions, GitLab CI/CD, Google Cloud, ActiveState; mints a short-lived project-scoped API token; "pending" publishers can create new projects | 2FA required for all accounts; project-scoped API tokens | Digital attestations (PEP 740) using in-toto predicates SLSA Provenance and PyPI Publish, signed by trusted publisher identities |
| RubyGems.org | Yes: GitHub Actions | MFA with WebAuthn recommended; MFA level "UI and API" covers push and owner changes; gems can set rubygems_mfa_required so every later version needs MFA | No registry-wide signing requirement |
| crates.io | Yes (RFC 3691): GitHub Actions first, with other providers planned in the RFC; access tokens valid for well under an hour | Sign-in via GitHub account; scoped API tokens | No registry signing requirement; checksums recorded in Cargo.lock |
| NuGet (nuget.org) | Yes: GitHub Actions (NuGet/login action) and GitLab CI/CD; temporary API key valid for one hour, one OIDC token per key; new policies are provisional for 7 days until first publish binds repository and owner IDs | Scoped API keys; organisations | Repository signature applied by nuget.org; optional author signatures with X.509 code signing certificates |
| Maven Central | No OIDC trusted publishing documented; publishing uses Central Portal user tokens | Namespace must be verified before publishing | Every file must carry a .asc GPG/PGP signature plus .md5 and .sha1 checksums (.sha256/.sha512 optional); sources and javadoc JARs required |
| Go modules | Not applicable: no central upload; modules are fetched from source repositories through proxy.golang.org | Depends on the source host (for example GitHub account security) | Checksum database records module hashes; versions are immutable once seen; authors can retract bad versions |
Go is the outlier: there are no registry accounts to compromise, so the publishing risk moves entirely to the source repository and its tags. The Go Modules Reference explains that the go command verifies downloaded modules against go.sum and, for hashes not yet recorded, against the checksum database unless the module matches GOPRIVATE or GONOSUMDB. See the Go Vulnerability Management Guide.
Registry comparison: namespaces and consumption
| Registry | Namespace control | Consumer integrity tools | Signature or provenance verification |
|---|---|---|---|
| npm | Scopes (@org/name) owned by users or organisations | package-lock.json with integrity hashes; npm ci | npm audit signatures checks registry signatures and provenance attestations |
| PyPI | Flat names; registry handles typosquatting and malware reports | pip install --require-hashes with hash-pinned requirements; lock tools | Attestations visible on PyPI and verifiable with Sigstore tooling |
| RubyGems.org | Flat names | Gemfile.lock | No registry-wide verification path |
| crates.io | Flat names | Cargo.lock checksums | None at registry level |
| NuGet | ID prefix reservation: packages using a reserved prefix are rejected unless from its owners, with a visual indicator for reserved-prefix owners | packages.lock.json with content hashes | signatureValidationMode=require with trustedSigners for authors or repositories, optionally restricted to listed owners; tampered content fails with NU3008 |
| Maven Central | Verified namespaces: DNS TXT record on the exact domain behind the groupId, or a code-hosting account (for example io.github.<user>) | No native lockfile in Maven; Gradle dependency verification metadata | PGP signature checks by build plugins; trust depends on key management |
| Go modules | Module path is the source repository URL | go.sum plus checksum database | Transparency of the checksum database rather than signatures |
For deeper per-registry guidance, see the npm Trusted Publishing and Provenance Guide, the PyPI API Tokens and Trusted Publishing Security Guide, the Maven Central and Gradle Supply Chain Security Guide, the NuGet Supply Chain Security Guide, the crates.io and Cargo Supply Chain Guide and the RubyGems Security Guide.
Setting one publishing baseline across registries
The aim is the same everywhere: no human-held or long-lived credential can publish, every release comes from a known pipeline, and consumers can verify where it came from.
- Publish only from CI with trusted publishing: bind each package to one repository, one workflow file and, where supported, a protected deployment environment that requires approval. The OpenSSF guidance stresses that the OIDC token's audience must be registry specific (for example
pypiornpm:registry.npmjs.org). - Remove the old path: after the first trusted publish, revoke all API tokens for the package and, on npm, select "Require two-factor authentication and disallow tokens". On registries without such a switch, delete the tokens and alert on any new token creation.
- Use human approval where offered: npm staged publishing and protected CI environments add a second person between a merged change and a live release.
- Phishing-resistant MFA for maintainers: WebAuthn security keys or passkeys for every owner account, with recovery codes stored securely.
- Least privilege on owners: keep two to three owners per package, use organisation accounts and teams rather than personal ownership, and remove departed staff on the leaver date.
- Provenance on by default: npm and PyPI generate provenance or attestations with trusted publishing from supported CI; for Maven, keep the signing key in an HSM or secrets manager used only by the release job. See the SLSA and Build Provenance Guide.
- Harden the pipeline itself: trusted publishing moves the target to CI, so pin actions by commit, restrict
id-token: writeto the release job, and never let untrusted pull request input reach the publish job. See the GitHub Actions OIDC Federation Hardening Guide.
Consuming safely from every registry
- Route through an internal proxy: point all package managers at an internal repository manager that caches approved upstream packages, which gives you one place to block, quarantine and log.
- Prevent dependency confusion: reserve your internal names publicly where the registry allows (npm scope, NuGet prefix, Maven namespace), and configure clients so internal names resolve only from the internal source. See the Malicious Package and Dependency Confusion Guide.
- Commit hash-pinned lockfiles: install with the lockfile-enforcing command (
npm ci, hash-checked pip installs,cargo build --locked, NuGet locked mode) in CI. - Verify what can be verified: run
npm audit signatures, enable NuGetsignatureValidationMode=requirefor a defined set of trusted signers, keep Go checksum verification on and restrictGONOSUMDBto genuinely private modules. - Delay new releases: a cooldown before adopting a newly published version lets registries and researchers remove malicious releases first. See the Dependency Update Automation Guide.
Rolling out the baseline
- Inventory: list every package, registry, owner account and existing token, including packages published years ago by individuals.
- Pilot one package per registry: configure trusted publishing, publish a pre-release from the pipeline, and confirm provenance or attestations appear on the registry page.
- Cut over: publish the next real release through the new path, then revoke tokens and switch on token restrictions.
- Test the failure case: attempt a publish with an old token or from a different workflow and confirm the registry rejects it.
- Roll back safely: if the pipeline breaks before a critical fix, issue a single short-lived, package-scoped token for that release and revoke it immediately after, rather than restoring the old long-lived token.
- Extend: repeat across all packages, then add consumer controls in CI and the internal proxy.
Decision guidance
- Small team publishing a few packages: switch on trusted publishing, delete every token, enforce security key MFA on owners and stop there; that removes the most common takeover path.
- Large organisation with many packages: manage registry organisations centrally, keep an inventory of every package and owner across registries, require trusted publishing by policy, and monitor registry audit events.
- Registry without trusted publishing (Maven Central): store the portal token and signing key in a secrets manager, scope them to the release job, rotate on staff change, and alert on publishes outside the pipeline.
- Choosing an internal registry product: score it against the OpenSSF levels, especially OIDC publishing, immutable versions and audit logs.
Detection and measurement
- Unexpected publishes: compare each new version on the public registry against a pipeline run; a version without a matching run, or without provenance where you always generate it, is an incident trigger.
- Account and token events: watch registry security emails and audit logs for MFA changes, new tokens, new owners and new trusted publisher configurations.
- Name squatting: monitor registries for new packages similar to yours.
- Consumer side: log proxy downloads of versions published within the last few days and block known malicious packages from OSV feeds.
Metrics:
- Percentage of your published packages using trusted publishing with tokens disabled or deleted.
- Number of active long-lived publishing tokens across all registries (target zero where trusted publishing exists).
- Percentage of package owners with phishing-resistant MFA.
- Percentage of releases with provenance, attestations or required signatures.
- Percentage of builds installing from hash-pinned lockfiles through the internal proxy.
Framework mapping
| Framework | Reference | Registry control |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-2, IA-5 | Maintainer MFA, removal of long-lived publishing authenticators |
| NIST SP 800-53 Rev. 5 | SR-4, SR-11, CM-14 | Provenance, component authenticity, signed components |
| NIST CSF 2.0 | PR.AA-05, GV.SC-07 | Least privilege on package ownership; supplier risk from upstream packages |
| OWASP ASVS 5.0 | V15.1.2, V15.2.4 | Inventory from trusted repositories; transitive dependencies from expected repositories, no dependency confusion |
| MITRE ATT&CK | T1195.001 Compromise Software Dependencies and Development Tools; T1078 Valid Accounts; T1528 Steal Application Access Token | Malicious releases from hijacked maintainer accounts or stolen publishing tokens |
Common failure patterns
- Trusted publishing beside a live token: in the Ultralytics PyPI compromise, two malicious versions came through the project's trusted publishing workflow after a crafted branch name injected code, and two more were uploaded with a long-lived API token never revoked after the move to trusted publishing.
- Account takeover with no second factor on publish: the ua-parser-js npm hijack shows how one maintainer account can push malware to millions of weekly installs.
- Registry account creation as an attack surface: in the GemStuffer RubyGems campaign, mass-created accounts and API keys issued to unverified accounts allowed thousands of malicious uploads in two days.
- Unverified PGP signatures: Maven signatures prove only that someone with a key signed the file; consumers that never check which key signed it gain little.
- Disabling Go checksum verification broadly: setting
GONOSUMDBorGOFLAGSto skip verification for whole public hosts removes the main Go integrity control. - Same policy text, different reality: a policy saying "MFA on all registries" without per-registry evidence usually hides at least one registry where it is optional and off.
Practitioner checklist
- Inventory every package you publish, on every registry, with owners and publishing method.
- Enable trusted publishing wherever the registry supports it, bound to one workflow and a protected environment.
- Revoke all long-lived publishing tokens afterwards and use npm's "disallow tokens" setting where available.
- Require phishing-resistant MFA for every package owner and keep owner lists short.
- Generate provenance or attestations on npm and PyPI, and protect Maven PGP keys in a secrets manager or HSM.
- Reserve namespaces and prefixes for internal names on npm, NuGet and Maven Central.
- Consume through an internal proxy with hash-pinned lockfiles and a release cooldown.
- Verify signatures or provenance at install where tooling exists (
npm audit signatures, NuGet trusted signers, Go checksum database). - Alert on publishes without a matching pipeline run and on new tokens or owners.
Standards and references
- OpenSSF: Principles for Package Repository Security
- OpenSSF: Trusted Publishers for All Package Repositories
- npm Docs: Trusted publishing for npm packages
- PyPI Docs: Publishing to PyPI with a Trusted Publisher
- Microsoft Learn: Trusted publishing on nuget.org
- Sonatype: Maven Central publishing requirements
- Go: Go Modules Reference
- Rust RFC 3691: Trusted publishing for crates.io
Related NHI Mgmt Group resources: npm Tokens Security Guide · RubyGems, NuGet and Crates.io Publishing Tokens Guide · Software Supply Chain Security Guide · Sigstore Guide