serde has 13M weekly downloads and one crate owner. Rust's supply chain risk looks like npm's.

rust dev.to

Rust developers tend to assume their supply chain is safer than npm's. The language is safer. The compiler catches more. The ecosystem feels more considered.

None of that helps when the threat is a compromised crates.io account.

I added Cargo support to Proof of Commitment this week and ran the same analysis I've been doing on npm and Python. The results are structurally identical.


The numbers

I audited the 20 most-downloaded Rust crates. 11 scored CRITICAL — a single crates.io owner with massive download volume. Here are the worst:

Crate Downloads/wk Owners Risk
syn 22.6M 1 🔴 CRITICAL
rand 19.1M 1 🔴 CRITICAL
thiserror 17.1M 1 🔴 CRITICAL
quote 16.1M 1 🔴 CRITICAL
proc-macro2 15.6M 1 🔴 CRITICAL
serde 13.3M 1 🔴 CRITICAL
serde_json 12.8M 1 🔴 CRITICAL
regex 11.8M 1 🔴 CRITICAL
clap 11.8M 1 🔴 CRITICAL
anyhow 10.2M 1 🔴 CRITICAL
hyper 10.1M 1 🔴 CRITICAL

Eleven CRITICAL crates in the top 20. Combined: roughly 160 million downloads per week behind single-owner crates.io accounts.


The dtolnay concentration

Five of those crates — syn, quote, proc-macro2, thiserror, and anyhow — have one crates.io owner: David Tolnay (dtolnay). He also owns serde and serde_json, which have an additional GitHub team owner.

Together, those seven crates account for over 107 million weekly downloads. One crates.io account. One phishing email would be enough.

To be clear: dtolnay is one of the most productive and careful maintainers in any ecosystem. The quality of the code is not the issue. The issue is that the identity layer — the crates.io account that controls the publish key — is a single point of failure. The competence of the person holding the key doesn't reduce the blast radius if the key is stolen.

This is exactly the structural profile that led to the ua-parser-js compromise in 2021 (one npm maintainer, 8M downloads/week, phished credentials, malicious publish). Rust's scale is larger.


What good looks like

Not every top Rust crate has this problem. Some have distributed publish authority:

Crate Downloads/wk Owners Risk
libc 17.1M 5 ✅ HEALTHY
log 12.4M 4 ✅ HEALTHY
tokio 10.7M 2 ✅ HEALTHY
url 9.5M 3 ✅ HEALTHY
futures 8.7M 3 ✅ HEALTHY

Multiple owners means multiple accounts would need to be breached simultaneously for a malicious publish. Not impossible, but the attack cost scales linearly with the number of people holding the key.


This pattern is universal

I've now run this analysis on three ecosystems. The finding is the same every time.

  • npm: chalk (413M/wk, 1 publisher), axios (100M/wk, 1 publisher), minimatch (581M/wk, 1 publisher)
  • Python: certifi (350M/wk, 1 publisher), boto3 (737M/wk, 1 publisher), fastapi (101M/wk, 1 publisher)
  • Rust: syn (22.6M/wk, 1 owner), serde (13.3M/wk, 1 owner), rand (19.1M/wk, 1 owner)

The programming language doesn't matter. The registry identity model does. Every major package registry has the same structural weakness: the publish credential is the final gate, and at the top-downloaded crates, that gate is held by one person.


Check your own Cargo.toml

# Audit any Rust crate
curl -s https://poc-backend.amdal-dev.workers.dev/api/audit \
  -H 'Content-Type: application/json' \
  -d '{"packages": ["serde", "tokio", "rand"], "ecosystem": "cargo"}'
Enter fullscreen mode Exit fullscreen mode

Web view (no install): getcommit.dev/audit

The API returns a score, owner count, download volume, and risk flags for each crate. CRITICAL means single owner plus high download volume — the structural conditions for a credential-compromise attack.


cargo audit checks for known CVEs. It won't flag serde. There's no CVE for "one person holds the publish key to 13 million weekly installs." That's a structural risk, not a vulnerability. Different tools catch different things.


Proof of Commitment is open-source. Web audit at getcommit.dev/audit. Cargo support is new — data accurate as of May 8, 2026.

Also: The same analysis on Python · Why npm audit returns zero for the most dangerous packages

Source: dev.to

arrow_back Back to Tutorials