On 23 October 2026, npm hosted about 6.0 million packages, half of all packages across the eight largest language registries, and carried 87% of the GitHub Advisory Database's 125,031 malware advisories. Composer (Packagist) and Maven carry the most reviewed vulnerability advisories per 1,000 packages. Size, risk and publishing controls differ sharply by ecosystem, so one policy rarely fits all eight.
This post puts the numbers for npm, PyPI, Maven Central, crates.io, the Go module proxy, NuGet, RubyGems and Packagist side by side. Every figure comes from a registry's own statistics page, a cross registry index, or the GitHub Advisory Database and OSV, read on 23 October 2026. Where two sources disagree, both numbers are shown with the reason.
Key takeaways
- npm is in a size class of its own. ecosyste.ms lists 6,001,436 npm packages and 80.5 million versions.
- Registries and indexes disagree by 1% to 10%. PyPI's package count is 944,170 on ecosyste.ms and 889,796 on pypistats. Packagist's own page says 475,338; ecosyste.ms says 524,487. Removed and spam packages explain most of the gap.
- Reviewed advisories are not spread evenly. Per 1,000 packages, Packagist has 12.55 GitHub reviewed advisories and Maven 9.84, while npm has 1.32 and NuGet 1.36.
- Malicious publishing is overwhelmingly an npm problem. npm accounts for 109,264 of 125,031 GitHub malware advisories (87.4%). One supply chain vendor's 2026 report puts npm's share of 2025 open source malware above 99%.
- Trusted publishing is now available on npm, PyPI, crates.io, NuGet and RubyGems. None of them make it mandatory for every package.
- Download counts are not comparable across registries. Each counts differently and CI inflates them all.
- Remediation mechanics matter as much as risk counts. Lockfiles, override fields, yanking rules and immutable versions decide how fast you can move off a bad version.
The eight registries at a glance
The table below is the baseline. Package and version counts come from the ecosyste.ms packages index, which crawls each registry with one method, so it is the only source that gives all eight on the same day.
Table 1. Packages, versions and versions per package, ecosyste.ms, read 23 October 2026
| Registry | Packages | Versions | Versions per package | Share of packages across the eight |
|---|---|---|---|---|
| npm (npmjs.org) | 6,001,436 | 80,541,257 | 13.4 | 49.2% |
| Go module proxy (proxy.golang.org) | 2,541,048 | 27,338,830 | 10.8 | 20.8% |
| PyPI (pypi.org) | 944,170 | 10,209,513 | 10.8 | 7.7% |
| NuGet (nuget.org) | 871,218 | 15,757,959 | 18.1 | 7.1% |
| Maven Central (repo1.maven.org) | 730,975 | 20,469,338 | 28.0 | 6.0% |
| Packagist (packagist.org) | 524,487 | 5,211,043 | 9.9 | 4.3% |
| crates.io | 363,572 | 3,347,485 | 9.2 | 3.0% |
| RubyGems (rubygems.org) | 213,489 | 1,870,408 | 8.8 | 1.8% |
| Eight registries combined | 12,190,395 | 164,745,833 | 13.5 | 100% |
Source: packages.ecosyste.ms. Versions per package and shares are our calculation.
ecosyste.ms reports 16,080,570 packages and 191,788,737 versions across every registry it tracks, so these eight registries account for about 76% of tracked packages and 86% of tracked versions.
Two figures in this table deserve a second look.
Maven Central's 28 versions per package is double the average. Java libraries tend to live for many years, publish many patch releases, and keep old releases available forever because Maven Central does not delete. That long tail is one reason Maven carries so many reviewed advisories: old vulnerable versions remain resolvable and in use.
NuGet's 18 versions per package reflects heavy release automation in the .NET world, including large SDK and workload packages that ship on every runtime update.
How many packages does each registry host, and how fast is each growing?
Most registries do not publish a growth rate, and the ones with charts do not publish the numbers behind them. Here is what we could confirm on 23 October 2026.
- npm has no public statistics page with a package total, and the registry root endpoint that once returned a document count now returns an empty JSON object. ecosyste.ms is the best available count: 6,001,436 packages, close to the combined total of the other seven registries. That count includes packages later found to be spam or malicious.
- PyPI shows 944,170 packages on ecosyste.ms and 889,796 on pypistats, which covers packages "from PyPI (updated daily)".
- Maven Central has no live public total that we could find. ecosyste.ms counts 730,975 groupId and artifactId pairs. Academic studies seen in search summaries counted 2.8 million versioned artifacts in 2018 and 658,078 artifacts in 2025, which shows how much the unit of counting matters.
- crates.io's summary API returned 359,402 crates and 506,164,233,979 all time downloads. The Lib.rs statistics page returned an access error, so we do not cite its growth multiple.
- Go has no central registry. Modules live in version control, and the Go team runs a mirror (proxy.golang.org), an index of newly seen versions (index.golang.org, with entries from 10 April 2019) and a checksum database (sum.golang.org). The 2,541,048 module paths on ecosyste.ms are everything seen through the mirror, including forks and experiments, not a count of maintained modules.
- NuGet's statistics page, last updated 22 October 2026, shows weekly downloads but no package total.
- RubyGems' stats page reports 197,876 gems, 251,956 users and 272,455,719,437 total downloads.
- Packagist's statistics page reports 475,338 packages, 5,968,112 versions and 207,628,936,283 installs since 13 April 2012.
The defensible growth signals are few. Packagist's 15 year anniversary post on 29 September 2026 said it listed more than 469,000 packages; 24 days later its stats page showed 475,338. NuGet weekly downloads rose from 6,974,433,238 (7 to 14 September 2026) to 8,202,433,206 (12 to 19 October 2026), up 17.6% in five weeks, which partly reflects release timing and should not be annualised. One supply chain vendor's 2026 report said downloads across Maven Central, npm, PyPI and NuGet reached 9.8 trillion in 2025, up 67%, with 86% of Maven Central traffic coming from cloud providers. That is the vendor's own telemetry, which we saw in search coverage of its press materials. For a real growth series, snapshot the same sources on the same day each month.
Where do registry counts disagree?
Every count of "packages in registry X" makes choices. Does it include packages deleted for malware? Packages with no releases? Placeholder names? Yanked versions? Different answers give different totals.
Table 2. Registry's own figure versus ecosyste.ms, read 23 October 2026
| Registry | Registry or registry adjacent figure | ecosyste.ms figure | Gap | Gap as % of the lower figure |
|---|---|---|---|---|
| PyPI | 889,796 (pypistats) | 944,170 | 54,374 | 6.1% |
| RubyGems | 197,876 (rubygems.org/stats) | 213,489 | 15,613 | 7.9% |
| Packagist | 475,338 (packagist.org/statistics) | 524,487 | 49,149 | 10.3% |
| crates.io | 359,402 (crates.io summary API) | 363,572 | 4,170 | 1.2% |
| NuGet | not published on the stats page | 871,218 | n/a | n/a |
| npm | not published (registry root returns an empty object) | 6,001,436 | n/a | n/a |
| Maven Central | no public live total found | 730,975 (coordinates) | n/a | n/a |
| Go | no registry; proxy view only | 2,541,048 (module paths) | n/a | n/a |
ecosyste.ms is higher in every case where we could compare. The likely reasons:
- Removed packages. An index that crawled a package before it was deleted for malware or spam may keep it. Registries typically drop deleted packages from their totals.
- Packages with no visible releases. Packagist, for example, can list a package whose versions were all soft deleted. Since 7 July 2026 Packagist hides deleted versions from Composer but keeps them visible on the package page with a reason, and records the change in a public transparency log.
- Different definitions of "package". pypistats tracks packages it has download statistics for. RubyGems' stats page counts gems currently hosted.
Download totals agree more closely: ecosyste.ms is 0.3% below RubyGems' own total, 0.7% below Packagist's and 4.1% below crates.io's, because an index that refreshes on a schedule lags a live counter.
What to do with this: when you report "our exposure to ecosystem X", use one source consistently and say which one. Mixing pypistats for PyPI with ecosyste.ms for npm creates a 5% to 10% error you cannot see.
How large is download volume per ecosystem?
Download volume is the most quoted and least comparable figure. Each registry counts differently, and much of the traffic is automation: CI runners, container builds and mirrors fetching the same artifact repeatedly.
Table 3. Download figures as each source reports them, read 23 October 2026
| Registry | Figure | What it measures | Source |
|---|---|---|---|
| NuGet | 8,202,433,206 | Package downloads, week of 12 to 19 Oct 2026 | nuget.org/stats |
| NuGet | 1,047,665,362,183 | Downloads as listed by the index | ecosyste.ms |
| crates.io | 506,164,233,979 | All time downloads | crates.io summary API |
| RubyGems | 272,455,719,437 | All time downloads | rubygems.org/stats |
| Packagist | 207,628,936,283 | Installs since 13 April 2012 | packagist.org/statistics |
| npm | 922,023,501,618 | Downloads as listed by the index | ecosyste.ms |
| PyPI | 127,653,386,679 | Downloads as listed by the index | ecosyste.ms |
| Maven Central | not shown | No download figure on the index page | ecosyste.ms |
| Go | not shown | No download figure on the index page | ecosyste.ms |
ecosyste.ms does not state on its summary page which time window each registry's download figure covers, so treat its npm, PyPI and NuGet rows as "the index's figure", not as a like for like comparison with the registry totals.
Concentration shows up even in these totals. NuGet's six weekly figures rose every week from 7 September to 19 October 2026, and newtonsoft.json alone took 415,888,468 downloads in that window. On RubyGems, bundler leads with 3,866,933,449 all time downloads, and four of the top ten gems are AWS SDK related.
Why it matters for security: download counts tell you how widely a compromised version would spread before anyone notices. They do not tell you how many applications actually run a package. For that, you need your own inventory.
How many reviewed advisories per ecosystem, and per 1,000 packages?
The GitHub Advisory Database is the most widely used per ecosystem advisory source. On 23 October 2026 it held 37,118 GitHub reviewed advisories, 357,187 unreviewed advisories and 125,031 malware advisories. Reviewed advisories are the ones GitHub's curation team has mapped to a specific package and version range, which is what makes them useful for dependency scanning.
Table 4. GitHub reviewed advisories by ecosystem and per 1,000 packages, 23 October 2026
| Ecosystem | Reviewed advisories | Share of the eight | Packages (ecosyste.ms) | Reviewed advisories per 1,000 packages |
|---|---|---|---|---|
| npm | 7,939 | 21.3% | 6,001,436 | 1.32 |
| Maven | 7,193 | 19.3% | 730,975 | 9.84 |
| pip (PyPI) | 6,662 | 17.8% | 944,170 | 7.06 |
| Composer (Packagist) | 6,581 | 17.6% | 524,487 | 12.55 |
| Go | 5,017 | 13.4% | 2,541,048 | 1.97 |
| Rust (crates.io) | 1,603 | 4.3% | 363,572 | 4.41 |
| NuGet | 1,186 | 3.2% | 871,218 | 1.36 |
| RubyGems | 1,162 | 3.1% | 213,489 | 5.44 |
Sources: GitHub Advisory Database ecosystem filters and ecosyste.ms. Per 1,000 figures are our calculation. The eight rows sum to 37,343. Shares are of that sum, because one advisory can list more than one ecosystem.
Now the ranked view. The bar column is a visual aid scaled to the highest value.
Ranked table. Reviewed advisories per 1,000 packages, highest first
| Rank | Ecosystem | Per 1,000 packages | Relative scale |
|---|---|---|---|
| 1 | Composer (Packagist) | 12.55 | ████████████████████ |
| 2 | Maven | 9.84 | ████████████████ |
| 3 | pip (PyPI) | 7.06 | ███████████ |
| 4 | RubyGems | 5.44 | █████████ |
| 5 | Rust (crates.io) | 4.41 | ███████ |
| 6 | Go | 1.97 | ███ |
| 7 | NuGet | 1.36 | ██ |
| 8 | npm | 1.32 | ██ |
Reading the ranking correctly
This is a measure of reviewed advisory density, not of how dangerous an ecosystem is. Four things push the number up or down:
- What the code does. PHP and Java libraries often handle HTTP requests, templates, XML, serialisation and authentication directly on servers. Those categories produce injection and deserialisation bugs that researchers hunt and report.
- Package granularity. npm and Go have huge numbers of small packages and module paths. Dividing by a very large denominator lowers the density even if the absolute count of advisories (7,939 for npm) is the highest of the eight.
- Research attention and reporting culture. Ecosystems with active security research communities and bug bounty interest produce more CVEs.
- Curation coverage. GitHub reviews advisories selectively. An ecosystem may have plenty of unreviewed advisories that never get version ranges.
For your own risk, the better question is how many of the packages you actually use have an advisory, and whether the vulnerable functions are reachable.
The OSV view and why it differs
OSV aggregates advisories from many sources into one schema. On 23 October 2026 its list page showed 2,549,214 entries across 43 ecosystems. For the eight in this post:
Table 5. OSV entries versus GitHub advisories, 23 October 2026
| Ecosystem | OSV entries | GitHub reviewed | GitHub malware | Why they differ |
|---|---|---|---|---|
| npm | 229,961 | 7,939 | 109,264 | OSV includes malicious package reports from OpenSSF as well as vulnerabilities |
| PyPI | 25,925 | 6,662 | 11,878 | Same, plus PyPA's own advisory database |
| Go | 9,586 | 5,017 | 20 | OSV includes the Go vulnerability database |
| Packagist | 7,182 | 6,581 | 2 | Close agreement; few malware reports |
| Maven | 7,085 | 7,193 | 2 | Close agreement |
| RubyGems | 5,397 | 1,162 | 3,580 | Malicious gem reports included in OSV |
| crates.io | 2,836 | 1,603 | 21 | OSV includes RustSec, which also records unmaintained and unsound crates |
| NuGet | 1,924 | 1,186 | 264 | Additional sources beyond GitHub |
Sources: OSV list page and GitHub Advisory Database. The OpenSSF malicious packages repository describes itself as reports of malicious packages "consumable via the Open Source Vulnerability (OSV) format", which is why OSV's npm count is so much larger than any vulnerability count.
The disagreement to note: for Maven and Packagist the two sources are within 2% of each other, because both are vulnerability counts. For npm, PyPI and RubyGems, OSV's number is dominated by malicious package reports. Anyone quoting "OSV has 229,961 npm vulnerabilities" is counting malware reports as vulnerabilities. Split them before you compare.
Our related data notes go deeper on these datasets: reviewed advisories by ecosystem, weakness types in OSV advisories, how a few packages carry most advisories, unrated advisories and an advisory data quality scorecard.
Which registries enforce 2FA, trusted publishing or provenance today?
Most registry compromises start with a stolen credential: a maintainer password, a long lived API token in a CI secret, or a phished session. The registries have answered with three controls. Two factor authentication protects the human account. Trusted publishing replaces long lived tokens with short lived credentials that a CI system obtains through OIDC. Provenance and signatures let consumers check where an artifact came from.
Table 6. Publishing controls by registry, as documented on 23 October 2026
| Registry | Account 2FA | Trusted publishing (OIDC) | Provenance or signatures | Version immutability |
|---|---|---|---|---|
| npm | GitHub said on 22 Sep 2025 it would require 2FA for local publishing and move TOTP users to FIDO | Yes: GitHub Actions, GitLab CI/CD, CircleCI on cloud runners; npm CLI 11.5.1 or later | Sigstore based provenance; automatic with trusted publishing on GitHub Actions and GitLab for public repos and packages; check with npm audit signatures | A used package and version can never be reused; unpublish is restricted after 72 hours |
| PyPI | Required for all users since 1 Jan 2024 | Yes: OIDC; minted tokens expire after 15 minutes | Not covered on the page we read | Deleted filenames can never be reused; yanked releases are skipped by installers unless pinned exactly |
| crates.io | Not confirmed in our sources | Yes since the July 2025 update: GitHub Actions only at the time | Not covered in our sources | Yanking hides a version from new resolutions but does not delete it |
| NuGet | Not confirmed in our sources | Yes: GitHub Actions and GitLab; temporary API key valid for 1 hour, one token per key | Not covered on the page we read | Not covered on the page we read |
| RubyGems | Required since 15 Aug 2022 for owners of gems over 180 million downloads | Yes: GitHub Actions through rubygems/release-gem | Not covered on the page we read | Not covered on the page we read |
| Maven Central | Not applicable to artifacts; namespace verification applies | Not covered on the requirements page | Every deployed file must carry a GPG or PGP signature; md5 and sha1 checksums required | Not stated on the requirements page; in practice releases are not deleted |
| Go modules | No publishing account; code is fetched from version control | Not applicable | sum.golang.org checksum database authenticates module contents; used by default since Go 1.13 | Versions are immutable snapshots; the retract directive marks bad versions without removing them |
| Packagist | Not covered in our sources | Not applicable to public packagist.org; versions come from git tags | Public transparency log since November 2025 | Stable versions immutable since 7 July 2026; retags are blocked and maintainers emailed |
Sources: npm docs on trusted publishers, provenance and unpublish policy; GitHub's npm plan; PyPI trusted publishers, PyPI 2FA and PyPI help; the crates.io July 2025 update and cargo yank; NuGet trusted publishing; RubyGems trusted publishing and MFA on popular gems; Maven Central requirements; Go modules reference and sum.golang.org; Packagist immutable versions.
"Not covered" means the official page we read did not state it. It does not mean the control is absent. Check the registry's current docs before you write policy against a cell in this table.
What the table tells you
- Trusted publishing is the common direction. Five of the eight registries offer it. The CI system issues a signed identity token naming the repository and workflow, the registry checks it against the maintainer's policy, and returns a credential that lives for minutes (PyPI: 15 minutes; NuGet: 1 hour).
- It is opt in. No registry in our sources requires trusted publishing for every package. A package published with a long lived token from a laptop looks the same to a consumer as one published from a locked down workflow, unless the consumer checks provenance.
- npm is going furthest on defaults. npm's docs say trusted publisher configurations created after 3 September 2026 default to stage publishing, where each CI publish needs maintainer approval with 2FA. New configurations must complete a first publish within two days or expire.
- Go and Packagist avoid the upload step entirely. Neither has a "push a package" credential to steal. The risk moves to the source repository: anyone with push rights to the tag. Go's checksum database means that once a version is seen, its content cannot silently change. Packagist reached a similar guarantee for stable versions on 7 July 2026, after which a moved tag is blocked rather than silently followed.
- Maven Central relies on signatures and metadata. Every file needs a signature and checksums, and the POM must name licenses, developers and an SCM URL. A PGP signature tells you who signed, not how the artifact was built.
Which ecosystems attract most malicious publishing, and why is npm the outlier?
Table 7. GitHub malware advisories by ecosystem, 23 October 2026
| Rank | Ecosystem | Malware advisories | Share of all malware advisories | Per 1,000 packages |
|---|---|---|---|---|
| 1 | npm | 109,264 | 87.4% | 18.21 |
| 2 | pip (PyPI) | 11,878 | 9.5% | 12.58 |
| 3 | RubyGems | 3,580 | 2.9% | 16.77 |
| 4 | NuGet | 264 | 0.2% | 0.30 |
| 5 | Rust (crates.io) | 21 | under 0.1% | 0.06 |
| 6 | Go | 20 | under 0.1% | 0.01 |
| 7 | Maven | 2 | under 0.1% | under 0.01 |
| 7 | Composer (Packagist) | 2 | under 0.1% | under 0.01 |
Source: GitHub Advisory Database malware filter, 125,031 total. Per 1,000 figures use ecosyste.ms package counts and are our calculation.
A second view comes from one software supply chain vendor's 2026 report, which says it identified more than 454,600 new malicious packages in 2025, brings its cumulative count to over 1.233 million across npm, PyPI, Maven Central, NuGet and Hugging Face, and found that over 99% of 2025's open source malware was on npm.
Where the sources disagree: GitHub puts npm at 87.4% of all malware advisories ever recorded; the vendor puts npm above 99% of malware found in one year. They measure different things (all time advisories versus one year of detections), so both can be true.
Why npm
Several structural features combine:
- Scale and naming. Six million packages in a flat namespace (scopes are optional) leave plenty of room for names that look like popular ones.
- Install time code execution. npm packages can run lifecycle scripts during install. That gives an attacker execution on a developer laptop or CI runner the moment a package is installed, before any application code calls it. GitHub said the September 2025 Shai-Hulud worm spread by injecting malicious post install scripts into popular packages through compromised maintainer accounts. Our Shai-Hulud explainer covers it in depth.
- Deep transitive trees. Many small packages means many maintainers in a typical dependency graph, so one compromised account reaches far.
- Low cost of publishing. Anyone can publish an unscoped name in minutes. Spam campaigns that publish thousands of junk packages inflate the counts too.
PyPI and RubyGems show high per package malware rates as well (12.58 and 16.77 per 1,000) because Python and Ruby packages can also run code at install or build time. Maven, Go and Packagist have very few malware advisories. Their structures help: Maven requires namespace verification and signed artifacts, Go fetches from version control with a checksum database, and Packagist reads from git rather than accepting uploads. Low counts may also reflect less scanning attention, so treat "2 malware advisories" as "few found", not "none exist".
How concentrated is maintenance?
The best public evidence here is thin. The figure most often quoted comes from a developer's self built tracker, described in a DEV Community post dated 24 August (the page footer indicates 2026). The author reports that, across more than 60,000 repositories in npm, Go, PyPI, Packagist, crates.io, RubyGems, Maven and others:
- about 354 billion of 460 billion monthly downloads (77%) come from repositories where one person makes most commits;
- 74% of repositories rely on one maintainer for most commits;
- 3,279 single maintainer repositories have over a million monthly downloads;
- the top 1% of packages account for 71% of download volume;
- only 19% of repositories have a security policy.
Caveats, stated by the author and by us: this is a self reported tracker; the scoring code is not open source; the data collection date is not given; and "most commits by one person" is not the same as "one maintainer". Use it as a directional signal, not a benchmark.
The registry data shows the same shape: on RubyGems, bundler alone has 3.87 billion downloads, and on NuGet the top two packages took 648 million downloads in six weeks.
Why it matters: concentration cuts both ways. A small number of critical packages means a small number of things to watch closely. It also means a single tired maintainer, a single stolen token or a single unpatched bug can reach most of an ecosystem. That is why a credible fallback for "upstream has not fixed it yet" belongs in your plan.
Which ecosystem features make remediation easier or harder?
When an advisory lands, you care about one thing: how fast can every build move to a safe version, including the copies buried three levels deep in transitive dependencies. Five mechanics decide that.
Table 8. Remediation mechanics by ecosystem
| Ecosystem | Lockfile | Force a transitive version | Bad version handling | Integrity check |
|---|---|---|---|---|
| npm | package-lock.json (also pnpm and Yarn lockfiles) | overrides in package.json | Deprecate; unpublish only in narrow cases; version numbers never reused | Lockfile integrity hashes; npm audit signatures |
| PyPI | No single standard lockfile; requirements with hashes, or tool specific lockfiles | Constraints files (-c constraints.txt) or tool specific overrides | Yank; deleted filenames never reused | --require-hashes mode in pip |
| Maven | No lockfile by default | dependencyManagement pins the version used across the tree | Releases stay available | Checksums and PGP signatures on Central |
| Go | go.mod plus go.sum | Raise the require version; minimal version selection picks it | retract warns users without removing | go.sum checked against sum.golang.org |
| crates.io | Cargo.lock | cargo update -p name --precise version; [patch] for forks | Yank; existing lockfiles unaffected | Checksums in Cargo.lock |
| NuGet | packages.lock.json when enabled | Central Package Management with transitive pinning | Unlist | Lockfile content hashes when enabled |
| RubyGems | Gemfile.lock | Pin in Gemfile; bundle update --conservative name | Yank | Lockfile pins exact versions |
| Packagist | composer.lock | Require the fixed version directly, or use conflict to ban ranges | Soft delete with recorded reason; stable versions immutable | Lockfile records the source reference |
These are documented tool behaviours; the details vary by tool version, so confirm against your toolchain.
What makes it harder
Committed, enforced lockfiles, a single place to override transitive versions, and immutable version numbers make remediation easier. Four things make it harder:
- Yanking is not removal. Cargo's documentation is explicit that a yank "does not delete any data", and existing lockfiles keep resolving the yanked version. PyPI installers still install a yanked release if you pin it exactly. A yank protects new projects, not yours.
- No default lockfile. Maven and plain pip installs resolve at build time unless you add pinning yourself. Two builds a day apart can differ.
- Go's minimal version selection is predictable but means a fix only takes effect when some module in the graph requires the fixed version.
- Version ranges in libraries. Wide ranges in npm and Python libraries mean your resolved version depends on when you last ran install.
What does this mean for a multi-ecosystem estate's policy?
Many organisations run several of these ecosystems. A useful policy is short, ecosystem aware, and enforced in CI. Here is a step by step baseline.
Step 1. Inventory what you actually use
Start with lockfiles and manifests, not guesses.
# Find every manifest and lockfile in a monorepo or checkout
find . -type f \( -name package-lock.json -o -name pnpm-lock.yaml -o -name yarn.lock \
-o -name requirements*.txt -o -name poetry.lock -o -name uv.lock \
-o -name pom.xml -o -name build.gradle* -o -name go.sum -o -name Cargo.lock \
-o -name packages.lock.json -o -name '*.csproj' -o -name Gemfile.lock -o -name composer.lock \) \
-not -path '*/node_modules/*'
Then produce an SBOM for each application so the inventory is queryable. With the Safeguard CLI:
safeguard scan --dir . # scan a repository; results as JSON under ./output
safeguard scan --image myapp:1.4 # scan a built container image
safeguard scan --sbom sbom.json # analyse an existing SBOM file
Our post on SBOMs in incident response shows why this inventory is the first thing you will want on a bad day.
Step 2. Enforce lockfiles and integrity in CI
npm ci # fails if package-lock.json and package.json disagree
pip install --require-hashes -r requirements.txt
cargo build --locked
go mod verify # checks module cache against go.sum
dotnet restore --locked-mode # requires packages.lock.json
bundle config set frozen true && bundle install
composer install --no-interaction # installs exactly what composer.lock records
Step 3. Check signatures and provenance where the ecosystem offers them
npm audit signatures # verifies registry signatures and provenance attestations
For Go, keep the default checksum database on. Only list genuinely private module paths in GOPRIVATE, because modules matched there skip both the public proxy and the checksum database.
Step 4. Run the ecosystem's own advisory checks as a floor
npm audit --omit=dev
pip-audit -r requirements.txt
govulncheck ./... # Go's official tool; reports reachable vulnerable symbols
dotnet list package --vulnerable --include-transitive
bundle exec bundler-audit check --update
composer audit
These native tools are a floor, not a programme. Each covers one ecosystem, uses its own advisory source, and reports differently. A platform that matches every SBOM against vulnerability databases in one place, ranks findings with reachability, and evaluates policies and gates centrally saves you from reconciling eight report formats.
Step 5. Know how to force a fixed version in each ecosystem
npm, forcing a transitive dependency:
{
"overrides": {
"lodash": "4.17.21"
}
}
Maven, pinning a transitive version across the tree:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.2</version>
</dependency>
</dependencies>
</dependencyManagement>
Cargo, moving one crate to an exact version:
cargo update -p some-crate --precise 1.2.4
Go, raising a module to the fixed version:
go get example.com/some/module@v1.4.2 && go mod tidy
The versions above are illustrations of syntax. Use the fixed version named in the advisory.
Step 6. Lock down your own publishing
If you publish packages, move every package to trusted publishing where the registry offers it. A minimal GitHub Actions job for npm needs the OIDC permission and a recent npm:
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
registry-url: 'https://registry.npmjs.org'
- run: npm install -g npm@latest
- run: npm ci && npm test
- run: npm publish
Then set the package's publishing access to require 2FA and disallow tokens, as npm's docs recommend. The same pattern applies on PyPI, crates.io, NuGet and RubyGems with their own login actions.
Step 7. Write the policy per ecosystem
A sample policy that reflects the data in this post:
| Rule | npm | PyPI, RubyGems | Maven, NuGet | Go, crates.io, Packagist |
|---|---|---|---|---|
| Committed lockfile required | Yes | Yes (hashes for pip) | Yes (enable NuGet lock mode; pin Maven versions) | Yes |
| Code that runs at install or build | Disable install scripts in CI by default; allowlist exceptions | Prefer wheels over source builds | Review build plugins and MSBuild targets | Review Cargo build scripts and Composer plugins |
| New dependency review | Required; check age, maintainers, provenance | Required | Required | Required |
| Critical or high with fix available | Fix pull request within your SLA | Same | Same | Same |
| No fix available | Mitigate, then fork or request hardened build | Same | Same | Same |
Where Safeguard fits
Counts are not risk. Safeguard scans repositories, container images and SBOM files, shows which projects contain a given component and version, matches components against vulnerability databases, and uses reachability analysis to show whether the vulnerable code is actually called. Policies and gates are evaluated in the platform, which gives you the evidence trail compliance teams ask for. Griffin autonomous remediation opens fix pull requests for the findings that matter, and it ships switched off so you choose what runs on its own. Gold Open Source shows per package CVE history and safe versions, and Gold Registry offers hardened components for regulated environments.
Safeguard does not change how a public registry authenticates its publishers, and it does not replace the registry controls in Table 6. It does not claim to detect malicious or typosquatted packages; use the registry's controls, install script policies and new dependency review for that.
How we measured
- Date. Every figure in this post was read on 23 October 2026, except where a different date is stated next to it (NuGet's page was last updated 22 October 2026).
- Package and version counts. Taken from the ecosyste.ms packages summary for all eight registries so that one method applies to every row. Registry and registry adjacent figures (pypistats, rubygems.org/stats, packagist.org/statistics, the crates.io summary API) are shown alongside for comparison.
- Go. "Packages" means module paths seen through proxy.golang.org, as counted by ecosyste.ms. Go has no registry with accounts or uploads.
- Maven. "Packages" means groupId and artifactId pairs. Version counts are distinct releases, not individual files.
- Advisories. GitHub reviewed and malware advisory counts are from the ecosystem filters on github.com/advisories. Because an advisory can name several ecosystems, ecosystem rows sum to more than the reviewed total; shares in Table 4 use the sum of the eight rows. OSV counts are from the OSV list page's ecosystem filter and include malicious package reports.
- Per 1,000 packages. Advisory count divided by the ecosyste.ms package count, times 1,000. The denominator includes inactive and removed packages, which lowers the ratio for registries with lots of spam (npm in particular).
- Downloads. Reported as each source states them. We did not convert or annualise download figures, because each registry counts differently and CI traffic dominates.
- Publishing controls. Taken only from official registry documentation or announcements. "Not covered" means the page we read did not say, not that the control is missing.
- Vendor figures. Security vendor reports are cited generically. Their figures come from their own telemetry and detection pipelines.
- Maintenance concentration. From a self reported tracker, labelled as such.
To reproduce: open each source in the Sources list on the same day, record the figures, and recompute the ratios. Monthly snapshots will give you a real growth series.
Common mistakes
- Comparing raw advisory counts across ecosystems. npm's 7,939 reviewed advisories look like the worst figure until you divide by six million packages. Always normalise, and say what you normalised by.
- Counting malware reports as vulnerabilities. OSV's npm total is mostly malicious package reports. Mixing the two inflates "vulnerability" counts by an order of magnitude.
- Treating downloads as installs. Much download traffic is CI and mirrors. Use your own inventory to measure your exposure.
- Mixing sources for package counts. pypistats and ecosyste.ms differ by 6.1% for PyPI. Pick one per report.
- Assuming a yank removes a version. Yanked crates and PyPI releases still install for existing lockfiles or exact pins.
- Assuming trusted publishing is universal. It is available on five of the eight registries and mandatory on none of them in our sources. Consumers still need to check provenance.
Fix it without waiting for upstream
Across all eight ecosystems the same gap appears: an advisory is published, and the fixed version, if one exists, still has to reach every build that carries the vulnerable one. In ecosystems where a few single maintainer packages carry most downloads, the gap before any fix exists can be the longer one.
Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.
1. Autonomous remediation: pull requests, not tickets
When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.
2. Gold Open Source: the safe version in seconds, free
gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.
3. Gold Registry: start from components that are already fixed
registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.
When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.
| Your situation | What to use |
|---|---|
| A fixed version exists | Autonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version |
| You need to know today whether you are affected | Gold Open Source lookup and "Am I affected?" checker, free and without login |
| You want vulnerable versions to stop shipping | The Gold scan GitHub Action in CI, plus Safeguard policies and gates |
| Upstream has no fix yet, or will not ship one | A custom Gold request; fork and patch (early access) |
| You want to start clean on new projects | Pull hardened components and images from Gold Registry |
One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.
FAQ
Which package registry is the largest in 2026?
npm, by a wide margin. ecosyste.ms listed 6,001,436 npm packages and 80,541,257 versions on 23 October 2026, about half of all packages across the eight largest language registries. The Go module proxy is second by module paths (2,541,048), but Go has no central registry, so that figure counts every module path seen, not maintained packages.
How many packages are on PyPI?
It depends on the source. ecosyste.ms showed 944,170 PyPI packages on 23 October 2026, while pypistats said it indexes 889,796. The 6.1% gap mostly reflects removed and inactive packages that one index keeps and the other does not.
Which ecosystem has the most vulnerabilities?
In absolute GitHub reviewed advisories, npm leads with 7,939, followed by Maven (7,193), pip (6,662) and Composer (6,581). Per 1,000 packages, Composer leads at 12.55 and Maven follows at 9.84, while npm is lowest at 1.32. Neither number tells you your own risk; your inventory and reachability do.
Why does npm have so many malicious packages?
npm combines a very large flat namespace, cheap publishing, install time lifecycle scripts and deep transitive trees. Together these make it the cheapest place to reach many developers and CI runners. GitHub records 109,264 npm malware advisories, 87.4% of its total.
What is trusted publishing?
Trusted publishing lets a CI workflow publish a package using a short lived credential obtained through OpenID Connect, instead of a long lived API token stored as a secret. npm, PyPI, crates.io, NuGet and RubyGems support it. PyPI's minted tokens expire after 15 minutes and NuGet's after one hour.
Does PyPI require two factor authentication?
Yes. PyPI has required 2FA for all users since 1 January 2024. Users without it cannot perform management actions or upload files.
Can a yanked crate or PyPI release still be installed?
Yes. Cargo's documentation says yanking does not delete data and existing lockfiles keep working. PyPI installers ignore a yanked release unless it is the only match for an exact pin. If your lockfile pins a yanked version, you are still using it.
How do I compare risk across ecosystems fairly?
Normalise advisory counts by package count, split malware reports from vulnerabilities, use one source per metric, and then narrow to the packages you actually use. Reachability analysis tells you whether the vulnerable function is called in your code.
See how Safeguard would help
The Safeguard CLI demo shows a real terminal session: you ask it to fix the lodash CVE in a repository, it reads the finding and advisory, asks before each command, edits package.json to lodash 4.17.21, runs npm install and npm test with your approval, and pushes nothing. That is the npm override and upgrade path from the remediation table above, done with a human approving each step. It finishes with /scan ., the same inventory step this post recommends for every ecosystem.
Click through it at your own pace, or open it full screen.
The local runner demo shows how to set up the runner on your own machine, approve it with its device code, and scan from the terminal. For a multi ecosystem estate, it is the route for running the Step 1 inventory scan on a machine you control and keeping that scan in your normal terminal workflow.
Click through it at your own pace, or open it full screen.
The Claude with Safeguard demo shows how to connect Claude to the Safeguard MCP server with OAuth and choose which tools Claude can use. Once connected, you can ask Claude about your own projects and their vulnerabilities in plain language, limited to the tools you chose to allow.
Click through it at your own pace, or open it full screen.
To try it on your own repositories, download Safeguard and follow the CLI docs. For more on the remediation side, see our benchmark of mean time to remediate and the state of SBOM adoption across industries.
Quick check
Which ecosystem has the most GitHub reviewed advisories per 1,000 packages, and which has the fewest?
Composer (Packagist) has the most at 12.55 per 1,000 packages. npm has the fewest at 1.32, because its six million package denominator is so large, even though its absolute count of 7,939 is the highest.
Does yanking a crate remove it from projects that already depend on it?
No. Cargo's documentation says a yank does not delete data and existing lockfiles are not affected. It only stops new resolutions from choosing that version, so you still need to update your own lockfile.
Sources
- ecosyste.ms packages summary
- pypistats
- NuGet statistics
- RubyGems.org stats
- Packagist statistics
- crates.io summary API
- Go module index
- Go module mirror and checksum database
- Go modules reference
- GitHub Advisory Database
- OSV vulnerability list
- OpenSSF malicious packages repository
- npm docs: trusted publishers
- npm docs: generating provenance statements
- npm docs: unpublish policy
- GitHub blog: our plan for a more secure npm supply chain
- PyPI docs: trusted publishers
- PyPI blog: 2FA required for all users
- PyPI help
- Rust blog: crates.io development update, July 2025
- Cargo docs: cargo yank
- Microsoft Learn: NuGet trusted publishing
- RubyGems guides: trusted publishing
- RubyGems blog: requiring MFA on popular gems
- Maven Central publishing requirements
- Packagist blog
- Packagist blog: immutable versions on Packagist
- DEV Community: single maintainer download concentration (self reported tracker)
Last reviewed: 23 October 2026. Registry counts change daily; check the linked statistics pages for current figures.