How Safe is the Rust Ecosystem? A Deep Dive into crates.io


Auditing the Rust Ecosystem: Analyzing crates.io with cargo-deny

The relentless wave of high-impact supply chain attacks throughout 2025—most notably the major incident within npm (in more details you could read at https://www.paloaltonetworks.com/blog/cloud-security/npm-supply-chain-attack/) —suggests this trend is far from peaking. In fact, with the rapid adoption of AI and LLMs in development workflows, we are likely facing an acceleration of these threats rather than a decline, in my opinion.

Thats why it highlighting the urgent need for rigorous security practices across all programming languages. As a Rust dev and part of the Rust community, and (obviously!) active consumer of packages from crates.io, I am more than concerned how Rust ecosystem would stand against such threats.


  • How resilient is the Rust ecosystem against these evolving threats?
  • Can we be certain that the thousands of packages we rely on are free from known vulnerabilities and adhere to strict quality standards?

The goal of this post is to analyze the overall quality of the Rust ecosystem, by leveraging the powerful tool cargo-deny, to conduct a comprehensive audit of crates.io packages.

Tooling

Dataset

The analysis is based on a dataset generated from a snapshot of the crates.io-index at commit e12a7dd, dated December 8, 2025.

It consists of the following columns:

Analysis

During the analysis the focus is narrowed to the most critical metrics: a:vulnerability, a:unsound, a:notice, a:unmaintained and a:yanked.

ℹ️ Click here to view detailed definitions of these Advisory Diagnostics

  • vulnerability
    • Meaning: A specific security vulnerability (CVE) has been identified in the dependency or in the crate itself.
    • Context: This is the most critical check. It indicates the crate version contains code that could be exploited (e.g., via memory corruption or denial of service). Users typically need to upgrade to a fixed version immediately.
  • unsound
    • Meaning: The crate contains “unsound” code.
    • Context: In Rust, “soundness” refers to code that strictly upholds the language’s memory safety guarantees. If a crate is flagged as unsound, it usually means it uses unsafe blocks in a way that could lead to undefined behavior (like segmentation faults) under valid usage, violating Rust’s safety contract.
  • notice
    • Meaning: An informational advisory.
    • Context: These are generally low-severity issues or notifications that do not fit into the other categories, such as warnings about future deprecation or minor issues that do not strictly qualify as vulnerabilities.
  • unmaintained
    • Meaning: The crate (or the specific version/branch in use) is no longer actively developed or supported.
    • Context: While not an immediate security threat, relying on unmaintained code is a significant risk. If a vulnerability is discovered later, it is unlikely to be patched. This serves as a warning to investigate a replacement or a maintained fork.
  • yanked
    • Meaning: The specific version of the crate has been “yanked” (retracted) from the registry by its authors.
    • Context: While yanked versions remain available for existing builds (to avoid breaking Cargo.lock), they cannot be added as new dependencies. Yanks often signal that a version was published by mistake, contains a severe bug, or has been superseded due to a non-critical issue.

Note: The columns a:notice, a:unmaintained, and a:yanked are not treated as critical failures in this context. Instead, they are viewed as additional insights, similar to the Supplemental metric group in CVSS.

General overview

Lets start with the general overview of the crates.io distribution based on their popularity (downloads column). In original download-distribution the distance between min-and max is big, so log-distribution is used instead.

log_downloads

As shown by the log-transformed download distribution, there is a spike at the lower end corresponding to packages with very few downloads. These packages are often experimental, inactive, or used by a negligible number of users. Since the goal of this analysis is to study vulnerabilities in packages that have a meaningful impact on the ecosystem, we restrict our focus to relatively more widely used packages and exclude this extreme low-download tail. So crates with less than 50 downloads have been cut, which corresponds to 7% or 14 019.

log_downloads_cut

Based on the distribution and for the future reference, crates would be splitted into popularity groups based on the number of downloads.

grouped_downloads


Advisory vulnerability and unsound

The next step would be to analyze safeness and secureness of packages, based only on vulnerability and unsound advisories.

CategoryPercentageCount
Total186 287
🟢 Passed70.541%131 409
🔴 Failed29.459%54 878

Note: The Failed count represents crates for which vulnerability or unsound failed at least once.

Failure Rate Distribution by Popularity:

vulnerability_unsound_failure_rate

Note: Failure rate represents the percentage of crates within a specific group that failed the check.

Average Failure Number by Popularity:

vulnerability_unsound_average_failure

Note: Average failure number represents the mean value of vulnerability and unsound failures per a specific group.


Advisory vulnerability

CategoryPercentageCount
Total186 287
🟢 Passed76.271%142 083
🔴 Failed23.729%44 204

Note: The Failed count represents crates for which vulnerability failed at least once.

Failure rate distribution across established popularity groups.

vulnerability_failure_rate

Note: Failure rate represents the percentage of crates within a specific group that failed the check.

Average failure number across established popularity groups.

vulnerability_average_failure

Note: Average failure number represents the mean value of vulnerability failures per a specific group.


Advisory unsound

CategoryPercentageCount
Total186 287
🟢 Passed79.621%148 323
🔴 Failed20.379%37 964

Note: The Failed count represents crates for which unsound failed at least once.

Failure rate distribution across established popularity groups.

unsound_failure_rate

Note: Failure rate represents the percentage of crates within a specific group that failed the check.

Average failure number across established popularity groups.

unsound_average_failure

Note: Average failure number represents the mean value of unsound failures per a specific group.


Advisory notice

CategoryPercentageCount
Total186 287
🟢 Passed99.988%186 264
🔴 Failed0.012%23

Note: The Failed count represents crates for which notice failed at least once.

Given the negligible failure rate for this advisory, further detailed investigation is not required.


Advisory unmaintained and yanked

CategoryPercentageCount
Total186 287
🟢 Passed61.873%115 261
🔴 Failed38.127%71 026

Note: The Failed count represents crates for which unmaintained or yanked failed at least once.

Failure rate distribution across established popularity groups.

unmaintained_yanked_failure_rate

Note: Failure rate represents the percentage of crates within a specific group that failed the check.

Average failure number across established popularity groups.

unmaintained_yanked_average_failure

Note: Average failure number represents the mean value of unmaintained and yanked failures per a specific group.


Does a lack of recent updates increase vulnerability risks?

There is a common belief in software engineering that code “rots”—that if a package hasn’t been touched in years, it eventually becomes a security liability. Is it true based on the collected data?

I wanted to test this hypothesis by looking at Release Recency. Put simply: Is there a correlation between how long a crate has gone without an update and its likelihood of containing vulnerabilities?

Using the date of the latest release (created_at column) as freshness characteristics, let’s see if stale packages really are more dangerous than active ones, based on vulnerability and unsound advisories.


TestCoefficientP-Value
Pearson0.18660.0
Spearman0.18390.0
Kendall0.14230.0

The P-value of 0.0 across all tests confirms that the relationship is statistically significant—the pattern is not due to random chance. However, the correlation coefficients (ranging from 0.14 to 0.19) indicate a weak positive correlation.

Verdict: while older packages are slightly more likely to have issues, inactivity alone is not a strong predictor of insecurity. It is a contributing factor, but certainly not a definitive smoking gun.


Does more dependencies increase vulnerability risks?

There is a common sentiment in software engineering:

“Every line of code is a liability.”

By extension, every additional dependency you add expands your potential attack surface.

I wanted to test this by looking at the Total Dependency Count. Put simply: Does a higher number of dependencies directly increase the probability of a crate having vulnerability flags?

Using the full dependency tree size (all_deps column) as a measure of complexity, let’s see if “heavier” crates are actually more dangerous than lightweight ones, based on vulnerability and unsound advisories.


TestCoefficientP-Value
Pearson0.34720.0
Spearman0.56110.0
Kendall0.44860.0

As in the previous analysis, the P-value of 0.0 across all tests confirms that the relationship is statistically significant—the pattern is not due to random chance.

However, unlike the previous analysis on freshness, the correlation coefficients here (ranging from 0.35 to 0.56) indicate a moderate to strong positive correlation. The Spearman coefficient of 0.56 is particularly notable, suggesting that as dependency ranks increase, risk ranks rise consistently.

Verdict: the data strongly supports the liability hypothesis. Increasing the size of the dependency tree directly and significantly increases the risk of inheriting a vulnerability. Complexity is a much stronger predictor of insecurity than age.


Conclusion & Next Steps

If you scrolled past the data to get to the end, don’t worry—I can’t blame you! 😉

The analysis shows a quite concerning situation in the Rust ecosystem. A significant portion, around 30%, of actively used crates fail to pass a standard configuration of the cargo-deny tool.

Beyond the raw numbers, the data uncovers a clear chain of causality regarding project safety:

More dependenciesHigher complexityHigher vulnerability risk

While the ecosystem is vibrant, this complexity penalty suggests that blindly adding packages is a dangerous strategy.

What to do ?

Security is not a state, it is a process.

Based on these findings, here are three immediate steps you can take to improve your projects:

  1. Integrate Auditing Tooling: Don’t wait for a breach. Add tools like cargo-deny to your CI/CD pipeline today to catch vulnerabilities before they merge.
  2. Prune Your Tree: Be wise and cautious when adding new dependencies. Every crate you add brings its own tree of transitive dependencies—and potential risks.
  3. Be an Active Collaborator: The ecosystem is only as strong as its contributors. If you encounter a vulnerability or an unmaintained crate,consider submitting a PR or offering to help maintain it rather than just waiting for a fix.

The Rust ecosystem is growing up, so our practices for consuming it must mature as well and thats how everyone could step forward and help improving it 🚀.

Acknowledgments

Many thanks for my beloved wife (https://github.com/Marynochka) for helping with the analysis.