San Francisco — May 2023 marked a quiet inflection point for the Rust ecosystem. Researchers at Kaspersky disclosed a malicious crate named rustdecimal, deliberately typosquatting the widely used rust_decimal library, sitting live on crates.io and reeling in downloads from developers who mistyped a single word in their Cargo.toml. The package's build.rs script — a file Cargo happily executes at compile time, no confirmation dialog required — fetched a second-stage payload designed to harvest credentials from Windows machines. Crates.io pulled the package once it was reported, but the incident was not an isolated glitch. It was a preview.
In the years since, security teams tracking crates.io have watched a steady, low-grade drumbeat of similar campaigns: typosquatted crate names, backdoored "helper" utilities, and dependency confusion attempts riding on Rust's growing footprint in systems programming, WebAssembly, blockchain tooling, and increasingly, AI infrastructure. Rust's reputation for memory safety has made it the language of choice for security-conscious teams rewriting critical components — which is exactly what makes its supply chain an increasingly attractive target for attackers who know that a single compromised crate can ride into production behind a language people trust by default.
This report walks through how these attacks work, why crates.io's publishing model makes them possible, how the pattern compares to what's already well-documented on npm and PyPI, and what security and platform teams should be watching for.
The rustdecimal Case: A Blueprint for the Attack Pattern
The rustdecimal incident is worth dissecting because it is a near-perfect template for how malicious Rust crates get distributed, and most subsequent copycat attempts have followed the same three-step structure:
- Typosquat a popular name.
rustdecimalvs.rust_decimal— a difference easy to miss when a developer is scanning search results or pasting a dependency line from a Stack Overflow answer or an AI code suggestion. - Weaponize
build.rs. Unlike npm'spostinstallhooks or PyPI'ssetup.py, which at least require some familiarity to notice, Cargo's build script convention is nearly invisible to the average consumer of a crate. It runs automatically, with full OS-level privileges, before a single line of the crate's actual Rust code is ever compiled or reviewed. - Stage the payload externally. The malicious logic wasn't fully embedded in the crate itself — it reached out to an external server to pull a second-stage binary, a pattern that lets the crate itself look mostly innocuous to a cursory read and evades simple static string-matching detection.
That combination — social-engineering the name, abusing a build-time execution primitive, and staging payloads off-registry — has reappeared in multiple unrelated campaigns since, targeting both individual developer credentials (crypto wallets, browser session tokens, SSH keys) and CI/CD environment variables where the real prize — cloud credentials and signing keys — usually lives.
Why Crates.io Is a Growing Target
Three structural factors make crates.io a rising rather than static risk:
Rust's adoption curve is accelerating in exactly the places attackers want to be. Rust has moved from niche systems language to a default choice for security teams rewriting components historically written in C/C++, for blockchain and wallet infrastructure, and increasingly for performance-critical layers of AI and data pipelines. That means crates.io dependencies now regularly sit in trust boundaries — key management, transaction signing, model-serving infra — that are far more consequential than a typical web app dependency.
Build-time execution is a first-class, largely un-gated feature. build.rs exists for legitimate reasons: generating bindings, compiling C dependencies, detecting platform features. But the same mechanism that lets a crate compile a vendored C library also lets it execute arbitrary shell commands, read environment variables, and phone home — all before a security scanner that only inspects the crate's public API surface would notice anything.
Publisher trust has historically been thin. For a long stretch of crates.io's history, publishing a new crate name or pushing a new version required only an authenticated GitHub account and an API token — no mandatory two-factor enforcement, no waiting period for first-time publishers, and no automated malware scanning gate comparable to what mature registries have since bolted on. The crates.io team and the Rust Foundation have made real, ongoing improvements here (trusted publishing via OIDC, tightened namespace-squatting policies, closer collaboration with RustSec), but the attack surface built up over years of a fully open, low-friction publishing model doesn't disappear overnight, and older crates published under the looser regime remain in dependency trees today.
The Broader Pattern: Crates.io Is Not Alone, But It Is Catching Up
Malicious package uploads have been a chronic problem across every major open-source registry — npm and PyPI in particular have logged malicious or typosquatted packages by the thousands over the past several years, and that volume has trained both attackers and defenders. What makes crates.io notable is not raw volume, since Rust's registry is smaller and its curation historically tighter, but rate of change: as Rust adoption climbs, the economics of typosquatting a Rust crate — low effort, plausible deniability via "test package" excuses if caught, high potential yield given where Rust code tends to run — start to resemble the economics that made npm and PyPI perennial targets.
Security researchers have also observed cross-registry campaign reuse: the same threat actors and infrastructure (C2 domains, payload-hosting services, obfuscation toolkits) showing up behind malicious packages on multiple registries in the same campaign window, suggesting crates.io is increasingly treated as just another lane on a highway attackers already know how to drive, rather than a separate, unfamiliar ecosystem requiring bespoke tooling.
What Security Teams Should Watch For
For teams consuming Rust dependencies at any scale, a few concrete practices meaningfully reduce exposure:
- Audit
build.rsand.cargo/config.tomlchanges on every dependency bump, not just diffs tosrc/. A version bump that adds or modifies a build script is a meaningfully higher-risk event than one that only touches library code, and most CI pipelines don't flag this distinction today. - Pin exact versions and verify checksums via
Cargo.lock, and treat any unexpected lockfile churn — especially transitive dependency additions — as worth a second look before merging. - Watch for name-proximity risk on new dependencies: does the crate name closely resemble a well-known package (
rustdecimalvs.rust_decimal, an underscore swapped for nothing, a letter transposed)? This is cheap to check and catches a meaningful share of real-world attempts. - Monitor outbound network activity during build, not just at runtime. A crate that reaches out to the network during
cargo build— outside of legitimate registry or vendored-source fetches — is a strong signal worth alerting on. - Track maintainer account changes. Sudden ownership transfers, new co-maintainers added shortly before a suspicious release, or a long-dormant crate suddenly shipping an update are all patterns that have preceded real incidents on other registries and are increasingly relevant to crates.io as well.
How Safeguard Helps
Safeguard is built for exactly this class of problem. Our reachability analysis determines whether a flagged or suspicious crate — including transitive dependencies pulled in via build.rs or vendored C bindings — is actually exercised by your application's code paths, so teams can triage real exposure instead of chasing every entry in a dependency tree. Griffin, Safeguard's AI-powered detection engine, is trained to recognize the behavioral fingerprints of supply chain attacks like typosquatting, build-script payload staging, and anomalous outbound network calls introduced in a new package version, surfacing them before they land in a production build. Safeguard generates and ingests SBOMs across your Rust, JavaScript, and Python estates so you have a continuously updated, queryable record of exactly which crates and versions are in use the moment a new advisory or malicious-package report drops. And when a fix is available, Safeguard opens an auto-fix pull request that updates the affected dependency to a safe version, letting engineering teams close the exposure window in minutes rather than the days or weeks manual triage typically takes.