A compromised crates.io publishing account pushed three poisoned Rust packages on August 20. A developer or CI runner that resolved one of those releases during a normal Cargo build automatically ran a malicious build script. The application did not have to import or call the injected dependency.
The RustSec advisory for arrayref says version 0.3.10 was downloaded 2,285 times before removal 86 minutes after publication. That is the confirmed scale, not a victim count. Rust has not quantified how many downloads reached a real build, how many build hosts fetched the second stage, or whether any organization lost secrets.
Three poisoned releases landed
The Rust Security Response Team received a report at 07:15 UTC that proc-macro1 carried a build script which downloaded a malicious payload. Four minutes earlier, according to StepSecurity’s vendor registry analysis, [email protected] had replaced a clean decoy release with the downloader.
At 07:15, the legitimate arrayref maintainer account published [email protected]. Its manifest added proc-macro1 as the small library’s first normal dependency. Within the same minute, the account yanked clean versions 0.3.5 through 0.3.9. A developer following Cargo’s warning about a yanked release could therefore be steered toward the poisoned version that remained available.
The same account published [email protected] at 07:34 and [email protected] at 07:37. Rust removed the three releases after 86, 90, and 107 minutes, restored the clean arrayref releases, and locked the account. The distribution window was brief. The response team wrote: “We do not believe the author of arrayref to be acting maliciously, but their computer or credentials are likely compromised.” Available evidence does not establish which credential layer was taken.
Rust also deleted proc-macro1, proc-macro-en, aovine, arone, aronenao, and tinymember. Socket’s vendor research found the same malicious build.rs in [email protected] and [email protected]. Its samples of aovine, arone, and aronenao looked like staging packages, while tinymember did not contain the active downloader. Rust’s guidance is to search for every version of all six names.
Cargo ran the hidden downloader
Cargo executes a dependency’s build.rs while compiling it. The poisoned legitimate crates contained little suspicious application code; each pulled in the typosquatted proc-macro1, which copied the real proc-macro2 source and hid the downloader in its build script. A project could compile and test successfully while that script selected a payload for the host operating system.
Socket reports that the loader reconstructed its infrastructure from Base64 fragments, disabled TLS certificate verification, and fetched from 23[.]254[.]165[.]112:9089. It passed 23[.]254[.]165[.]112:443 to the fetched process as command-and-control infrastructure. StepSecurity reproduced the build and observed the outbound connection during a successful CI job.
On Linux and macOS, the loader wrote /tmp/rust-setup and launched it detached. On Windows, it created %TEMP%\rust-setup.ps1 and %TEMP%\rust-setup-launch.vbs, then used wscript.exe to start hidden PowerShell with ExecutionPolicy Bypass. Socket also lists %TEMP%\rust-setup.ps1.cfg, %TEMP%\ps-<GUID>.ps1, %APPDATA%\<operator-controlled folder>\<name>.ps1, and the matching .ps1.cfg file.
Socket analyzed recovered Linux x86-64, Windows x86-64, and macOS ARM64 second stages. They profiled the host, inventoried Chromium login origins and usernames, established user-level persistence, and accepted commands to run scripts or shell commands. The analyzed stage did not decrypt stored browser passwords. Windows persistence used an HKCU Run entry; Linux used a systemd user service; macOS used a LaunchAgent.
StepSecurity later added third-party reports from the RustSec thread. One infected Linux host created $HOME/.config/AzureKits and $HOME/.config/ServiceKit, ran executables named MonoService and MonoXpc, registered systemd persistence, and connected to 23[.]254[.]167[.]216. Another researcher reported 23[.]254[.]167[.]107 as command-and-control infrastructure. StepSecurity has not independently reproduced either observation.

Figure details
A developer workstation or CI runner resolves arrayref 0.3.10, internment 0.8.7, or append-only-vec 0.1.9. Cargo pulls proc-macro1 1.0.107 and automatically runs its build.rs. The loader chooses a platform payload, reaches 23[.]254[.]165[.]112 on port 9089, writes an operating-system-specific file, and launches it detached. The second stage then beacons on port 443 and may establish user-level persistence. A cached crate proves retrieval; process, file, and network evidence is needed to establish execution.
The second stage used the path /49890878 and could fall back to ten date-derived hunting domains for August 20–24: rasGThauFD[.]com, feVVKIiEiU[.]com, phrpjTNckF[.]com, PrOkXLgfjW[.]com, ackeoTaWtl[.]com, GAFWVCMAja[.]com, RNSsddnEgK[.]com, pfHlVOqEeg[.]com, aBEcOrkups[.]com, and epOdIaTMaM[.]com. Socket says these deterministic names are hunting values; a name need not have been registered or active.
Removal leaves a host question
Removal prevents a fresh crates.io resolution from retrieving these deleted releases. Existing hosts still need evidence. It cannot answer what happened on a system that built during the window. RustSec reports that most arrayref users retained older versions in their lockfiles and that 0.3.10 represented less than 10 percent of package download traffic. The package’s roughly 245 million lifetime downloads describe ecosystem reach, while 2,285 is the specific count for the poisoned arrayref release.
A matching .crate file in ~/.cargo/registry/cache proves that Cargo retrieved the artifact. A Cargo.lock entry proves dependency resolution. Neither artifact by itself establishes that the build script ran or that the second-stage fetch succeeded. Process ancestry from Cargo or rustc, the created files, a connection to the defanged IP address, and persistence created after the build supply the execution evidence.
The four second-stage SHA-256 values published by Socket are:
- Linux x86-64
rust-crate_0.1.0:408ef22050ffc5a67e005802809026b29f297a8019f8fda91a2afa8e877ba434 - Windows x86-64
rust-crate_0.2.0:492f2ab86f8d8911adc79c10ec1541704f5311d207d9d799b0d2a57fcc6a4391 - macOS x86-64
rust-crate_0.3.0:c9561a3b00a0fa38b7772675d987f84bd429c55cd024fc08a98245c2d1632848 - macOS ARM64
rust-crate_0.4.0:74d3447e7cf99c99ea01a16332ec27432dfb0f491e10e67cd118065a60483306
The shared malicious build.rs has SHA-256 cb7778eb6dda91028abf087eb7c3553f981a67e756769507d348e8c201805568. The defanged payload locations used host 23[.]254[.]165[.]112, port 9089, and paths /rust-crate_0.1.0, /rust-crate_0.2.0, /rust-crate_0.3.0, and /rust-crate_0.4.0. The beacon used that host on port 443 with path /49890878. StepSecurity also lists the Hostwinds VPS name hwsrv-798836[.]hostwindsdns[.]com and the provider ranges 23[.]254[.]165[.]0/24 and 23[.]254[.]167[.]0/24; use those broader shared-service values as correlation pivots rather than standalone blocking verdicts.
Verify builds and affected hosts
Run Rust’s cache check on developer workstations, CI runners, self-hosted build agents, and release systems:
find ~/.cargo/registry/cache -type f \(
-name 'append-only-vec-0.1.9.crate' -o
-name 'arrayref-0.3.10.crate' -o
-name 'internment-0.8.7.crate' -o
-name 'proc-macro1-*.crate' -o
-name 'proc-macro-en-*.crate' -o
-name 'aovine-*.crate' -o
-name 'arone-*.crate' -o
-name 'aronenao-*.crate' -o
-name 'tinymember-*.crate'
\) -print
The expected clean result is no output and no matching names or poisoned versions in Cargo.lock, dependency inventories, build logs, artifact caches, or software bills of materials. There are no patched successor releases in the RustSec advisories. The last unaffected versions are arrayref<=0.3.9, internment<=0.8.6, and append-only-vec<=0.1.8; those clean versions supersede the deleted poisoned releases for remediation unless Rust publishes a newer advisory-backed release.
When a poisoned crate is present, align its retrieval time with cargo build, cargo update, CI job, and compiler process records. Hunt for 23[.]254[.]165[.]112 on ports 9089 and 443, 23[.]254[.]167[.]107, 23[.]254[.]167[.]216, hwsrv-798836[.]hostwindsdns[.]com, /49890878, $HOME/.config/AzureKits, $HOME/.config/ServiceKit, MonoService, MonoXpc, every listed file and hash, suspicious wscript.exe or PowerShell spawned from the build chain, HKCU Run changes, new systemd user services, and new LaunchAgents. Treat the crate as downloaded until runtime evidence establishes execution. Treat the host as potentially compromised when the build ran, even if the job finished green.
For an executed build, isolate the host, rotate source-control, package-publishing, cloud, signing, and deployment credentials available to it, and rebuild affected software on a known-clean system. The verification result is a clean cache and dependency graph across the fleet, every positive mapped to a build event, no unexplained loader or second-stage telemetry, and rebuilt artifacts plus rotated secrets for each executed case.
The registry response closed the distribution window quickly. The remaining security boundary sits inside build history: teams that can account for the dependency, the process tree, and the egress event can separate a cached package from a compromised build host.
