A package on Rust’s crates.io registry has been flagged as carrying code that runs during compilation rather than at run time. The mechanism is ordinary Cargo behaviour, which is precisely what makes it useful to attackers.
Key takeaways
- Rust packages can execute arbitrary code on a developer’s machine during compilation, before the resulting program is ever run.
- The reported incident involves a crate published to crates.io whose contents allegedly differed from what its name and reputation would suggest.
- Build-time execution means that running cargo build in a continuous integration job can be enough to trigger a payload.
- Similar execution hooks exist in other ecosystems, including npm install scripts and Python packaging scripts, so this is not unique to Rust.
- Independent verification of any specific package’s contents matters more than early summaries, because details in fast-moving reports are frequently revised.
What is actually happening
The report circulating among developers concerns a package on crates.io, the default registry for the Rust programming language, said to contain code that executes while the package is being compiled rather than when the finished binary is run. The package name involved is associated with a small, widely depended-upon utility, which is part of why the report attracted attention.
The essential claim is about placement, not sophistication. Rust packages, called crates, may include a build script — conventionally a file named build.rs — that Cargo, the Rust build tool, compiles and executes as a normal step of building the crate. That script runs with the same permissions as the user invoking the build. Anything that user can do, the script can do: read files, open network connections, write to disk, modify the environment.
What is not publicly settled, at least not in a form that can be confirmed here, is the precise scope: which version or versions were affected, how long they were available for download, how many downloads occurred, what the payload attempted to do, and whether the crate was a newly published impersonation, a compromised account, or something else. Those distinctions matter a great deal and should be taken from registry and maintainer statements rather than from summaries.
Why it is in the news now
Attention gathered on developer forums because the story combines two things that individually generate discussion and together generate a lot of it. The first is Rust’s reputation. The language is usually discussed in terms of memory safety, and a supply-chain incident is a reminder that memory safety and distribution safety are unrelated problems. A crate can be entirely free of undefined behaviour and still hand an attacker a shell.
The second is the target. Registry attacks against npm and PyPI have become routine enough that individual cases rarely spread beyond security newsletters. Cases involving crates.io are less common, so each one is treated as a data point about whether the Rust ecosystem is genuinely different or merely smaller and therefore less attractive.
There is also a recurring structural argument that resurfaces with every such report: that build scripts and procedural macros give package authors arbitrary code execution by design, that this has been known and documented for years, and that the ecosystem has not agreed on what, if anything, to do about it.
The background a newcomer needs
Rust projects declare dependencies in a manifest file, Cargo.toml. Cargo resolves those dependencies, downloads them from crates.io, and compiles them alongside the project’s own code. A lock file, Cargo.lock, records exactly which versions were selected so that later builds reproduce the same set.
Two features allow dependency code to run at compile time. Build scripts exist so that crates can detect system libraries, generate source code, or configure conditional compilation. Procedural macros exist so that crates can transform code during compilation, which is how much of Rust’s ergonomic serialisation and error-handling tooling works. Both are legitimate and both are common; a large project will typically compile and execute dozens of them without anyone noticing.
Crates.io has properties that shape incident response. Published versions are immutable, so a bad release cannot be silently edited; instead it can be yanked, which prevents new dependency resolution from selecting it while leaving existing lock files functional. Names are claimed on a first-come basis, which enables typosquatting, and account compromise remains a possibility independent of any registry policy.
Who is affected and how
The exposed party in a build-time attack is the developer and their build infrastructure, not the end user of the finished software — at least not initially. If a payload runs during compilation, the machines at risk are laptops, build servers and continuous integration runners. Those environments are frequently the most credential-rich systems an organisation operates, holding registry tokens, cloud keys, signing material and source code for unreleased work.
Continuous integration deserves particular emphasis because it removes the human. A pipeline that fetches dependencies and builds on every commit will execute whatever build scripts the dependency graph contains, on a schedule, without anyone reading the code. Ephemeral runners limit persistence but do not prevent exfiltration during the run.
Projects that pin dependencies through a committed lock file and build with --locked are insulated from newly published malicious versions, though not from a compromise of a version they already depend on. Projects that vendor dependencies or build offline have a further layer. Projects that regularly widen version ranges, or that run cargo update unattended, have the least.
Where informed people disagree
The sharpest disagreement is over whether build scripts are a design mistake. One position holds that compile-time code execution is a legitimate requirement — cross-compilation, native library discovery and code generation all need it — and that removing it would break a substantial share of the ecosystem for a security benefit attackers could route around via procedural macros anyway.
The opposing position is that compilation ought to be a pure transformation of inputs to outputs, and that any language whose build step can open a socket has conceded too much. Proponents point to sandboxing as the answer: run build scripts under restricted capabilities by default, with an explicit opt-in for network or filesystem access.
A third argument concerns responsibility. Some hold that registries should perform automated inspection of published packages and flag build scripts that behave suspiciously. Others counter that scanning is an arms race with an obvious asymmetry, that false positives would erode trust in the warnings, and that the durable fix is reproducible, isolated builds rather than detection.
There is also disagreement over disclosure practice. Rapid public posting warns people quickly but propagates unverified detail; coordinated handling is more accurate but slower, and slower can mean more downloads.
What this means in practice
None of the available mitigations are novel, which is part of the frustration around these incidents. Committing a lock file and building with --locked in continuous integration removes the class of attack that depends on a fresh malicious publication being resolved automatically. Auditing tools that check a dependency tree against the RustSec advisory database catch known-bad versions once they are catalogued.
Treating build machines as privileged is the broader lesson. Restricting what credentials are available inside a build, scoping registry tokens narrowly, and separating the environment that compiles code from the environment that signs or deploys it all reduce the value of a successful build-time payload.
Reviewing what a project actually depends on remains the least automatable and most effective step. Transitive dependency counts grow quietly, and a dependency added for one function may pull in a build script that nobody has ever read.
What to watch next
The most informative signals will be procedural. Whether the affected package is yanked, whether an advisory is published in the RustSec database, and what any registry post-mortem says about the publication path will indicate whether this was typosquatting, account compromise or something else — and that determines which defences would have helped.
Beyond the individual case, watch for movement on sandboxed build execution, on tooling that surfaces which dependencies contain build scripts at all, and on whether registries adopt stronger publisher authentication. Incidents like this tend to accelerate proposals that were already under discussion rather than generate new ones.
Frequently asked questions
What is a Rust build script?
A build script is a file, conventionally build.rs, that ships inside a Rust package. Cargo compiles and runs it as part of building that package, before the package’s own code is compiled. It exists so crates can locate system libraries, generate source code or set compilation flags. Because it is an ordinary program, it can do anything the user running the build can do.
Does compiling code from crates.io run that code?
Yes, in part. Compiling a dependency that includes a build script or a procedural macro executes code from that dependency on your machine at compile time. This is documented, intended behaviour rather than a vulnerability. It does mean that downloading and building a project is not a passive operation, and that inspecting a dependency only after running it is too late.
How is this different from an npm or PyPI attack?
Structurally it is very similar. The npm ecosystem has install scripts and PyPI packages have historically executed setup.py during installation, both giving package authors code execution on developer machines. The differences are in scale and tooling maturity rather than in the underlying mechanism. Rust’s lock file and immutable published versions help with reproducibility but do not remove compile-time execution.
Am I affected if I use Rust?
That depends on whether the specific package and version were in your dependency tree, which cannot be determined generally. Checking your Cargo.lock for the package name and version is the direct test. If a build ran with an affected version present, the prudent assumption is that any credential reachable from that build environment should be rotated.
Can crates.io delete a malicious package?
Crates.io published versions are immutable, so the normal remedy is yanking: the version stays downloadable for builds that already reference it in a lock file, but new dependency resolution will not select it. This preserves reproducibility for existing projects while stopping further spread. Registry operators may take additional action in severe cases, though the specifics of any individual case are theirs to state.
How can I reduce build-time supply-chain risk?
Commit a lock file and build with –locked so versions do not change silently. Run advisory-database checks against your dependency tree. Limit the credentials available inside build environments and keep build machines separate from deployment ones. Audit dependencies periodically, paying attention to transitive additions. Consider vendoring or offline builds where the operational cost is acceptable.
Sources and further reading
- The official Cargo documentation, which describes build scripts, the lock file and dependency resolution behaviour in detail.
- The crates.io registry policy pages, which set out publication rules, name allocation and the yanking process.
- The RustSec advisory database, a community-maintained catalogue of security advisories for Rust crates, and the auditing tools built on it.
- Developer discussion forums including Hacker News, where the report circulated and where practitioners debated build-script sandboxing.
Surfaced from the hackernews signal “malicious package registry report”. AI-assisted draft, editorially reviewed.

