arrayref Rust crate compromised, smuggles in build-time malware

arrayref Rust crate compromised, smuggles in build-time malware

On August 20, 2026, a compromised release of the Rust crate arrayref appeared on crates.io. Version 0.3.10 kept the crate's ordinary macro code, arrayref is a small library of four macros, including the widely used array_ref!, but added one new line to its manifest: a dependency on a typosquatted package called proc-macro1. Cargo builds every declared, non-optional dependency whether or not the code actually calls it, so that single manifest line was enough. Any project that pulled in arrayref 0.3.10 built proc-macro1 too, and building proc-macro1 ran its build script. The build script downloads and runs a remote binary while the project compiles, so simply compiling the code was enough to trigger it; no separate install or run step was needed. crates.io has since removed the malicious versions.

The genuine arrayref and append-only-vec crates are maintained by an account called droundy, which the report says appears to have been compromised: the corresponding GitHub repositories, and the entire droundy GitHub account, now return 404. proc-macro1 itself was published from a separate account, dtolney, a username closely resembling David Tolnay's real dtolnay account. The crate's metadata forges an authors field naming David Tolnay and points its repository field at a dtolnay/proc-macro1 path that also returns 404. The disguise goes deeper than the name: proc-macro1 is not proc-macro2, the real crate that Rust macro authors actually depend on, but its source is a near-exact copy of proc-macro2's, carried over down to documentation links and old issue references. Because the library code is genuinely proc-macro2 underneath, projects that pulled in the fake crate kept compiling normally, which is what let the build script run unnoticed.

The malicious code sits only in proc-macro1's build script, not in arrayref itself. The tell is in the build dependencies: proc-macro1 adds three build-time crates that the real proc-macro2 does not have, giving the script base64 decoding, a TLS stack and an HTTP client, an unusual toolkit for a token-parsing library. The server address is never written as plain text; the script stores it as base64 fragments and reassembles them at build time, decoding to the download host hxxps://23[.]254[.]165[.]112:9089/ and a command-and-control address at 23[.]254[.]165[.]112:443. It fetches the binary over a TLS connection whose certificate verifier accepts every certificate and signature check without validation, so a self-signed certificate on the raw IP passes. The script selects a binary by operating system and architecture, supports four targets, and aborts the build on anything else. None of this sits behind a feature flag or an environment check: it runs inside the build script's main function, ahead of the genuine proc-macro2 configuration logic, on every build on a supported platform.

Execution then detaches itself from the build entirely. On Unix, the script writes the downloaded bytes to /tmp/rust-setup, marks the file executable, and spawns it without waiting, passing the command-and-control address as its first argument and sending every standard stream to null. On Windows, the bytes are a PowerShell script written to %TEMP%\rust-setup.ps1 and started hidden through a VBScript launcher under wscript.exe; a comment in the malicious source explains that routing through WScript is what escapes Cargo's own job object, letting the PowerShell process keep running after the build finishes. A final std::mem::forget call leaks the wscript child handle so its destructor never runs. The build itself never waits on any of this, so a compromised project keeps compiling as if nothing happened while the payload runs on its own.

The compromise spread with help from Cargo's own tooling. The owner account yanked the older, clean arrayref releases, 0.3.5 through 0.3.9, which makes Cargo print a warning to consider updating to a version that is not yanked, a nudge that pointed developers toward the only release left standing: the malicious 0.3.10. The person who filed the RustSec advisory on this incident said that nudge is exactly how they encountered it. arrayref sits deep in the Rust ecosystem as a transitive dependency, reached through tiny-skia, sctk-adwaita and winit, which puts it underneath most GUI work built on egui, eframe and iced. The crate has about 245 million all-time downloads, 244,989,384 at the time of writing, with the clean 0.3.9 release alone accounting for roughly 152 million of those. The report is careful to note that this figure measures how widely the crate has been used historically, not how many builds actually pulled in the malicious version during the window it was live.

Key facts

  • arrayref version 0.3.10, published on crates.io on August 20, 2026, added a single manifest dependency on a typosquatted package, proc-macro1 1.0.107, whose build script runs a remote binary the moment a project compiles.
  • The attacker's account, dtolney, forged crate metadata naming David Tolnay as author, a username closely resembling the real dtolnay account, and pointed the repository field at a dtolnay/proc-macro1 path that returns 404; proc-macro1's source is a near-exact copy of the real proc-macro2, so builds kept working normally.
  • The owner account yanked the older, clean arrayref releases 0.3.5 through 0.3.9, so Cargo's own warning to update to a non-yanked version nudged developers toward the malicious 0.3.10, which is how the RustSec advisory's reporter found it.
  • arrayref reaches deep into the Rust ecosystem as a transitive dependency, via tiny-skia, sctk-adwaita and winit, underneath GUI toolkits such as egui, eframe and iced, and counts about 245 million all-time downloads, though the report notes that figure measures overall usage, not the number of builds that pulled the malicious version.
  • The payload's TLS client accepts any certificate without validation, then detaches from the build entirely: an unwaited process on Unix, or on Windows a hidden PowerShell script launched through wscript.exe that a code comment says is chosen specifically to escape Cargo's job object.

Why it matters

This is a build-time supply-chain attack, a step beyond the more familiar install-time or run-time kind: nobody has to run a downloaded package or execute a suspicious command, only compile a project that depends on it. That turns ordinary developer machines, and more consequentially CI runners, into unwitting execution environments. The attack also chains several distinct techniques rather than relying on one: a compromised maintainer account, droundy, supplied the legitimate-looking dependency; a typosquatted package, proc-macro1 rather than the real proc-macro2, carried the payload; forged metadata borrowed a trusted name, David Tolnay's real dtolnay account, to make the new dependency look credible; and yanking the older, clean releases turned Cargo's own safety warning into a funnel toward the one malicious version left standing. Each piece is a known technique on its own. Assembled together, they got a payload past a chain of otherwise reasonable trust signals.

Who it affects

Anyone whose Rust project depended on arrayref 0.3.10, directly or transitively, while the malicious version was live on crates.io. arrayref reaches a wide surface through tiny-skia, sctk-adwaita and winit, which sit underneath GUI toolkits such as egui, eframe and iced, so a project that never declared arrayref itself could still have built it. The build script targets both Unix and Windows with operating-system- and architecture-specific binaries, so the exposure was not limited to one platform, and it applies equally to a developer's own machine and to any CI system that builds the same dependency tree.

How to use it

There is no product, price or license to weigh here; the only actionable use of this report is defensive. The mechanism it documents doubles as the checklist: a caret version requirement, like the one arrayref's own manifest added for proc-macro1, can resolve to a brand-new release the moment a compromised account publishes one, and Cargo builds every declared non-optional dependency whether the code calls it or not. Anyone maintaining a Rust project that depends on arrayref, directly or through tiny-skia, sctk-adwaita or winit, is worth checking against that exact window: whether a build ever resolved to version 0.3.10 rather than 0.3.9 or an earlier release, since 0.3.10 is the version the compromised account published and crates.io has since removed.

How solid is it

The write-up is a first-hand technical analysis from SafeDep, an open-source supply-chain security firm, and it walks through the actual crate manifests, the build script's control flow and the reassembled command-and-control address directly rather than relaying second-hand claims. It leaves some specifics undisclosed in the visible text: the three added build-dependency crates are named only by function, base64 decoding, a TLS stack, an HTTP client, not by name; the four supported operating-system and architecture targets are given only as a count, not named; and a promised SHA256 hash of the removed crate artifacts is not present. The report also does not say who compromised the droundy account or how.

Risks and caveats

The report's own download figures, about 245 million all-time for arrayref with roughly 152 million of those on the clean 0.3.9 release, measure how widely the crate has been used historically, not how many builds actually pulled in the malicious 0.3.10 while it was live, and the report says so directly. It does not state whether proc-macro1 1.0.106, the only other version ever published, was itself malicious; only that the caret version requirement resolves to the malicious 1.0.107. Beyond confirming that crates.io removed the malicious versions, the report gives no specific date or time for when that removal was completed, only that it happened afterward.

“consider updating to a version that is not yanked”

— Cargo's warning for a yanked crate version, as quoted in the SafeDep report