A Rust Crate With 245 Million Downloads Ran Malware During cargo build for 86 Minutes. cargo audit Still Says You Are Fine.
On August 20, 2026, at 07:15:00 UTC, a malicious version of arrayref went up on crates.io from the account of its longtime owner. arrayref is a tiny array-conversion utility with roughly 245 million all-time downloads that sits underneath blake3, winit, and large parts of the Solana and Ethereum tooling stacks. It was live for 86 minutes. If your machine resolved it in that window, the compromise already happened, and cargo audit will not tell you.
The source code was never touched
This is the part that should bother you. There is not a single line of malicious code in arrayref 0.3.10. The library source is identical to the clean release. The entire change to the manifest is one added dependency: proc-macro1 ^1.0.107, the first real dependency in the crate's decade-long history.
proc-macro1 is a typosquat of proc-macro2, published four minutes earlier by a crates.io account named dtolney, one transposed letter from dtolnay, the author of the real thing. That crate also ships genuine proc-macro2 source. Every build succeeds. Every test passes. The weapon is a build.rs script.
Rust build scripts compile and run on the machine doing the build, with that user's full privileges. SSH keys, cloud credentials, CI secrets, signing keys, all in reach. The dropper base64-decodes a payload URL split across five string fragments, fetches a platform-specific binary over TLS with certificate validation explicitly disabled (necessary, since the endpoint is a bare IP at 23.254.165.112:9089), writes it to /tmp/rust-setup on Unix or a .ps1 plus .vbs wrapper on Windows, spawns it detached, and abandons the child handle with std::mem::forget(child) so Cargo does not wait on it. The build script exits 0. The compiler output looks normal.
Your code never has to call arrayref for any of this to happen. Compiling is enough.
The yank was the delivery mechanism
Here is the move I had not seen before, and the reason I think this post is worth your time even if you do not write Rust.
Twenty-four seconds after publishing the poisoned 0.3.10, the same account started yanking every other modern release. The crates.io audit log reads like a script, because it was one:
07:15:00.82Z publish arrayref 0.3.10 07:15:24.21Z yank arrayref 0.3.9 (+24s) 07:15:26.80Z yank arrayref 0.3.8 (+2.6s) 07:15:30.70Z yank arrayref 0.3.7 (+3.9s) 07:15:36.29Z yank arrayref 0.3.6 (+5.6s) 07:15:40.18Z yank arrayref 0.3.5 (+3.9s)
Yanking does not break existing builds. Cargo happily keeps using a yanked version already sitting in your lockfile. What it does is print a warning on every subsequent build telling you the version in your Cargo.lock is yanked and you should consider updating.
So you see a scary new warning on a dependency with a nine-figure download count, you do the responsible thing, you run cargo update -p arrayref, and Cargo resolves to the only non-yanked modern release: the poisoned one. The original reporter says this is exactly how they got hit. The attacker turned the registry's own safety feature into the distribution channel, and it cost them six API calls.
I keep coming back to that. Every instinct that makes you a careful maintainer, noticing warnings, keeping dependencies current, not letting yanked versions rot in the lockfile, is the instinct being exploited.
It was three crates, not one
The compromise was of the maintainer's account, not of any single crate. The same account published internment 0.8.7 at 07:34:07 UTC and append-only-vec 0.1.9 at 07:37:49, each with the identical one-line dropper injection. Three poisoned releases in 23 minutes. Combined all-time downloads across the three: about 264 million.
There is a nasty second-order effect there. internment depends on append-only-vec, so one internment resolve during the window pulls two poisoned crates into a single tree.
The Rust Security Response Team removed a second dropper crate (proc-macro-en, carrying the same build script, a spare in case the first typosquat got burned) plus four more attacker-owned crates. Two of those, arone and aronenao, had their final publishes on August 18, two days before the arrayref push. The infrastructure was staged in advance. The team's assessment is that the owner's machine or credentials were compromised, not that the owner did anything wrong, and they locked the account as a precaution.
Overall exposure window across all three: 07:11 to 09:25 UTC.
Why your tooling reports clean
The malicious versions were deleted from crates.io, not yanked. That distinction is doing a lot of damage.
cargo audit reads RustSec advisories against your lockfile. The advisories for this incident were still pending at the time of writing, and the versions themselves no longer exist in the registry to be matched. A project that pinned arrayref 0.3.10 gets a clean audit report.
Worse, deletion from crates.io does nothing to your local cache. A machine with a warm ~/.cargo/registry or a committed vendor/ directory keeps building the payload offline, from a version that officially does not exist anymore.
And Cargo.toml pins do not help. arrayref = "0.3" matches 0.3.10 perfectly well. Only the resolved lockfile version tells you anything.
What I'd actually do
Three commands, in this order, and then a policy change.
First, grep your checkouts. This is a lockfile question, not a registry question:
grep -rEn 'proc-macro1|proc-macro-en|aovine|arone|aronenao|tinymember|arrayref.*0\.3\.10|internment.*0\.8\.7|append-only-vec.*0\.1\.9' \ --include=Cargo.lock --include=Cargo.toml .
Second, check the cargo cache directly, since deletion upstream did not clean it:
find ~/.cargo/registry/cache -type f \ \( -name 'arrayref-0.3.10.crate' -o -name 'internment-0.8.7.crate' \ -o -name 'append-only-vec-0.1.9.crate' -o -name 'proc-macro1-*.crate' \) -print
Third, on Unix check whether /tmp/rust-setup exists. On Linux also look for $HOME/.config/AzureKits, $HOME/.config/ServiceKit, and binaries named MonoService or MonoXpc, which an infected developer reported in the public advisory thread as stage-2 persistence. Those are third-party reports, not independently reproduced, but they cost nothing to check.
If any of that hits, the payload ran as you. Rotate everything the machine could reach and rebuild anything you shipped from it.
The policy change matters more than the greps, though. The three malicious releases were live for 86, 90, and 107 minutes. A rule that only admits crate versions older than some number of days would have made this a complete non-event. I have been lukewarm on dependency cooldown policies for years because they feel like bureaucracy for a one-person shop. This incident changed my mind. A 72-hour cooldown costs you almost nothing (you are not shipping against a two-day-old point release anyway) and it defeats the entire class of attack that depends on a short exposure window.
Also: commit Cargo.lock, build with --locked in CI, and actually read lockfile diffs in review. A new entry named proc-macro1 in a diff is impossible to miss. That is a five-second review habit that beats every scanner in this story.
Where I could be wrong
The cooldown recommendation has a real cost I am glossing over: it delays security patches too. If a genuine CVE fix lands in a crate you depend on, a 72-hour hold is 72 hours of exposure you chose. The honest version is that you want a cooldown with a manual override, and most solo operators will never build the second half of that. If you are going to implement half of it, implement the cooldown and accept you will occasionally override manually.
The other thing I am not certain about is blast radius. 406 dependent crate versions is the direct count, but the number of builds that actually resolved a poisoned version during a 90-minute window on a Thursday morning UTC is unknown and probably small. This may end up being a near-miss with an excellent writeup rather than a widespread compromise. The technique is what I would file away, not the incident.
Author
Lukas
@lukcombinatorSources
- Rust Supply-Chain Attack: arrayref, internment, and append-only-vec Poisoned by the proc-macro1 Build-Time Dropper
- Hackers poison arrayref Rust crate to push infostealer malware
- Hackers poison popular Rust crates to steal developers' credentials
- rustsec/advisory-db#3161: original disclosure
- Rust Supply Chain Attack Puts Build-Time Malware in Crates with 245 Million Downloads