Safeguard
Supply Chain Security•

arrayref and two crates republished with a build script backdoor

On 20 August 2026 three Rust crates were republished to depend on proc-macro1, whose build.rs downloaded a payload. They were live for 86 to 107 minutes. How to check your builds.

Safeguard Security Team
6 min read

On 20 August 2026 the Rust Security Response Team removed malicious releases of three crates: arrayref 0.3.10, internment 0.8.7 and append-only-vec 0.1.9. Each had been republished to depend on a lookalike crate, proc-macro1, which imitates proc-macro2. Its build script downloaded and ran a payload on the machine that compiled the code.

The Rust team says it does not believe the maintainer acted maliciously, and suspects the author's computer or credentials were compromised. The account was locked. Socket's scanner flagged proc-macro1 at 07:29 UTC, minutes after the first republish.

What's affected

CrateMalicious versionPublished (UTC)DeletedOnline
arrayref0.3.1007:1508:41about 86 minutes
internment0.8.707:3409:04about 90 minutes
append-only-vec0.1.907:3709:25about 107 minutes

Safe versions per Socket: arrayref 0.3.9 or earlier, internment 0.8.6 or earlier, append-only-vec 0.1.8 or earlier. The attacker also yanked several older legitimate arrayref releases, which may have steered resolution toward 0.3.10. Rust later restored them.

Deleted attacker crates: proc-macro1, proc-macro-en, aovine, arone, aronenao and tinymember (the last impersonated tiny-skia). Socket reports arrayref had roughly 152 million downloads of the prior clean release, according to RustSec.

How the build script works

Cargo runs build.rs automatically when it compiles a crate or any of its dependencies. Your application never calls the code, and tests and cargo check run it too. Socket describes the loader as reconstructing Base64-obfuscated server addresses, detecting the OS, disabling TLS verification and downloading a platform-specific binary.

On Linux and macOS it wrote /tmp/rust-setup. On Windows it dropped rust-setup.ps1 and rust-setup-launch.vbs in %TEMP% and launched hidden PowerShell. The backdoor profiled the host, inventoried Chromium browser login origins, set up persistence (a Run key, a systemd user service or a LaunchAgent), and accepted commands. It can run scripts and shell commands. Socket says the analyzed stage did not decrypt stored passwords.

What the second stage does

Socket's analysis of the second stage adds a few details useful for hunting:

  • The beacon goes to a path ending in /49890878 on the command server, and the payload accepts commands to change its configuration, persist, download and run scripts, and exit.
  • When the main server is unreachable it falls back to ten date-based .com domains. Socket published the list for 20 to 24 August 2026 as hunting indicators, noting they are not necessarily registered.
  • The payloads do not embed the server address. proc-macro1 hands it over at runtime, so a payload found on disk will not name its server.
  • Socket reports the same RSA private key is embedded across payloads, and command authentication does not guarantee a single operator.
  • Linux x86-64, Windows x86-64, macOS x86-64 and macOS ARM64 builds were served, each from its own download path.

The incident was reported to the Rust Security Response Team by Nextron Systems, and the advisory went to RustSec as well.

How to check if you're exposed

grep -nE 'name = "(arrayref|internment|append-only-vec|proc-macro1|proc-macro-en|aovine|arone|aronenao|tinymember)"' -A1 Cargo.lock
find ~/.cargo/registry/cache -name 'arrayref-0.3.10.crate' -o -name 'internment-0.8.7.crate' -o -name 'append-only-vec-0.1.9.crate'
ls /tmp/rust-setup 2>/dev/null

Search CI build logs, caches and internal mirrors for the same names. Hunt in network logs for connections to 23.254.165.112 on ports 9089 and 443. On Windows hosts look for rust-setup.ps1, rust-setup-launch.vbs, unexpected Run keys and wscript.exe activity tied to Cargo builds.

What to do now

  1. Pin Cargo.lock to safe versions and run cargo update -p arrayref --precise 0.3.9 (and the same for the other two).
  2. If a host built a malicious version, treat it as compromised. Check for persistence created after the build, then rebuild from a clean system.
  3. Rotate credentials and secrets that were available to that build environment.
  4. Purge the malicious .crate files from caches and internal mirrors.
  5. Rebuild any release artifacts that were compiled during the window.

How this could have been avoided

  • Treat a new dependency in a patch release as a review event. arrayref 0.3.10 added proc-macro1. A lockfile diff bot or a CI check that fails on new dependencies in patch updates would have flagged it.
  • Commit Cargo.lock and build with --locked for applications, so a fresh republish cannot enter without a commit.
  • Run builds in a sandbox without network access after fetching sources with cargo fetch or cargo vendor. The loader needed to download its payload at build time.
  • Restrict egress from build hosts to your registry mirror and known domains. A raw IP on port 9089 stands out.
  • Cooldowns. All three releases were removed within two hours, and a delay of even a day would have missed them.

Why it matters

Cargo has no npm-style install scripts, and people sometimes assume that removes this class of attack. Build scripts run arbitrary code on the build machine. Here the malicious code sat one dependency away from a crate with roughly 152 million downloads.

The window was short, but the damage depends on who built in it. Teams that update on a schedule, pull fresh dependencies in CI, or build containers on a laptop between 07:15 and 09:25 UTC on that day are the ones to check.

There is also a lesson in how fast the response went. The first report reached the Rust team at 07:15 UTC, the same minute arrayref@0.3.10 went live, and the last deletion came at 09:25. Registries cannot prevent every compromised laptop, but quick reporting limited the exposure to under two hours.

Try it yourself

Pick one Rust service and diff Cargo.lock between your last two release commits. List every crate that gained a dependency. For each, check that its upstream repository has a matching commit and tag. A release with no matching source change is the pattern here.

Click through it at your own pace, or open it full screen.

Quick check

Why did the application never need to call the malicious code?

Cargo runs build.rs automatically when compiling a dependency, so the loader runs at build time.

Which signal would have caught the arrayref release earliest?

A new dependency, proc-macro1, appearing in a patch release with no matching source change upstream.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.