Teardown: the arrayref and proc-macro1 crates.io attack

One lookalike dependency, three popular crates, malware at compile time.

What happened on 20 August 2026, how a copy of proc-macro2 got into arrayref's dependency tree, and where Chainsaw would stop it.

Questions and answers

What happened?

The proc-macro1 attack is a crates.io typosquat from 20 August 2026, in which malicious versions of three Rust crates went up from one maintainer’s account: arrayref 0.3.10 at 07:15 UTC, internment 0.8.7 at 07:34 and append-only-vec 0.1.9 at 07:37. Each version added a dependency on proc-macro1, a lookalike of the widely used proc-macro2, whose build.rs downloaded and started a platform-specific payload while Cargo compiled the project. The Rust Security Response Team, alerted by the Nextron Systems research team, deleted the three versions within 86, 90 and 107 minutes. arrayref 0.3.10 was downloaded 2,285 times before removal, which RustSec puts at under 10% of arrayref download traffic.

How did the malicious crate get in?

Through a dependency line, not a mistyped name. Nobody had to type proc-macro1: the attacker published new versions of crates people already used and listed it as a dependency. The Rust blog says the team does not believe the maintainer acted maliciously and that their computer or credentials were “likely compromised”. It does not say how.

The lookalike was built to pass a quick look. JFrog found that proc-macro1 1.0.107 is a renamed copy of the real proc-macro2 1.0.107, with its author field faked as David Tolnay, who maintains the real crate. The additions are a build script and three build dependencies the real crate does not need: ureq, rustls and base64.

Earlier arrayref releases had been yanked; the Rust team unyanked them. RustSec says the 2,285 downloads were under 10% of arrayref download traffic, because most users had older versions locked.

What ran at build time?

A download-and-execute step in proc-macro1’s build.rs. Cargo runs a build script while compiling, before any of the crate’s code is called, so building a project that resolved the malicious version was enough. According to JFrog’s analysis:

  • It rebuilt its download URL from Base64 fragments at runtime, pointing at 23[.]254[.]165[.]112 on port 9089.
  • It picked a payload by platform: separate binaries for Linux x86_64, Windows x86_64, macOS x86_64 and macOS on Apple silicon.
  • It turned off TLS certificate checks for the download.
  • On Linux and macOS it wrote the payload to /tmp/rust-setup, made it executable and started it detached, with 23[.]254[.]165[.]112:443 as an argument.
  • On Windows it wrote %TEMP%\rust-setup.ps1 and a rust-setup-launch.vbs launcher, and ran the script hidden through wscript and PowerShell with the execution policy bypassed.

JFrog could not retrieve the second stage, because the server had stopped answering, so what it did next is unknown. JFrog’s advice is to treat any machine where the script ran as fully compromised.

Would Chainsaw have stopped it?

Yes, on day 0, for builds that go through the Chainsaw proxy. This comes from our re-run of 58 attacks from 2026 against production Chainsaw (v0.22.118) on 19 October 2026:

  • The typosquat rule refuses proc-macro1 on day 0. proc-macro1 1.0.107 is refused on its first download by the default rule that blocks suspected typosquats.
  • Its build script trips a second rule. The build.rs network connection also trips the default rule on install scripts that fetch remote URLs.
  • Either refusal fails the build of arrayref, internment and append-only-vec. Cargo cannot fetch proc-macro1, so its build script never runs.
  • Builds that bypass the proxy get the malware. A laptop or CI runner pointed straight at crates.io is not protected. That is why Chainsaw also gates CI, pins laptops through MDM and blocks direct registry traffic at the network.

How the typosquat matching works is covered in our guide to typosquatting. Every attack in the re-run is on the 2026 attack coverage page.

What should you do?

The proc-macro1 typosquat reached builds through arrayref 0.3.10, internment 0.8.7 and append-only-vec 0.1.9. The most urgent step, per JFrog, is to treat any machine or runner that built one of the three as compromised. To check with Cargo, run cargo tree -i proc-macro1 in each workspace, and search Cargo.lock and any vendored crates for these versions:

  • arrayref 0.3.10 (last clean release 0.3.9)
  • internment 0.8.7 (last clean release 0.8.6)
  • append-only-vec 0.1.9 as published on 20 August 2026 (crates.io now serves a clean 0.1.9 the maintainer published on 18 October 2026, and 0.1.10)
  • proc-macro1, any version

cargo tree only reads the current lockfile, so also look in the local registry cache, as the Rust blog suggests: search ~/.cargo/registry/cache for .crate files with the names and versions above, on every developer machine and CI runner which built Rust code on 20 August 2026.

If you find one, follow JFrog’s advice:

  1. Treat the machine or runner as compromised. The second stage is unknown, so cleaning up the visible files is not enough.
  2. Rotate every credential the build could reach: registry tokens, cloud keys, SSH keys and CI secrets on the machine.
  3. Look for the dropped files: /tmp/rust-setup, %TEMP%\rust-setup.ps1 and %TEMP%\rust-setup-launch.vbs.
  4. Block 23[.]254[.]165[.]112 on ports 9089 and 443.
  5. Move to a clean release (arrayref 0.3.9, internment 0.8.6, append-only-vec 0.1.8 or the October 0.1.9 and later) and regenerate the lockfile from crates.io.

For the next one, keep Cargo.lock committed and review every change to the lockfile. A new crate appearing in the tree, with a name one character away from a popular one, is the signal the proc-macro1 attack left.

Sources

One teardown per notable attack

Get the next teardown by email

What happened, the install-path angle most coverage misses, and the rule that refuses it.