Developer workstation showing Rust code compilation

How to Secure Rust Supply Chain from Attacks

August 21, 2026 · 11 min read · By Dagny Taggart

Anatomy of the 2026 Rust Supply-Chain Attack: How a Typosquatted Build Script Compromised Three Popular Crates

On August 20, 2026, three widely used Rust crates were briefly replaced on crates.io with trojanized versions. The most downloaded, arrayref, has roughly 245 million lifetime downloads; internment and append-only-vec add nearly 19 million more. The poisoned versions were live for only 86 to 107 minutes, published between roughly 07:15 and 07:38 UTC and pulled by about 09:25 UTC, before crates.io deleted them and locked the account. The incident is tracked as RUSTSEC-2026-0260.

This attack draws attention not because of the download count but because of how the payload was delivered. The attacker never altered the actual source code of any of the three crates. Instead, they added a single new dependency and relied on Cargo’s build process to execute the malicious code. The surprising part, and why you should keep reading even if you haven’t heard of these crates, is this: if you compiled a project that included arrayref, directly or indirectly, your machine might have run malicious code even if your own code never called any function from it.

Key Takeaways

  • A single compromised crates.io maintainer account published trojanized arrayref 0.3.10, internment 0.8.7, and append-only-vec 0.1.9 on 2026-08-20.
  • The payload was delivered through one new dependency, proc-macro1, a typosquat of proc-macro2, whose build.rs executed at compile time.
  • Running cargo build on any project resolving arrayref triggered the payload, with no API call required.
  • The second stage was an infostealer harvesting browser data from Chrome, Brave, and Edge and exfiltrating it as base64 JSON over HTTPS.
  • Researchers report infrastructure overlap with North Korean activity, including the “Mastra” campaign attributed to Sapphire Sleet.

What Happened on August 20, 2026

The Rust Security Response Working Group believes the crate author was not acting maliciously. Their computer or credentials were compromised, and the attacker used that access to publish new versions of three crates the maintainer controlled. The legitimate versions were restored after the fact, the account was locked, and the maintainer was contacted. But the damage window, brief as it was, was real: any build that resolved one of the poisoned versions during those 86 to 107 minutes included malicious code in the dependency graph.

The Affected Crates

The three affected crates are not obscure. arrayref is a small utility crate that lets Rust code safely take array references of fixed size, and it is deeply embedded in the dependency trees of some of the most popular crates in the Rust ecosystem. internment provides interning for arbitrary values, and append-only-vec offers a growable array type. None of them are flashy, but all are quiet, foundational dependencies that get pulled in automatically without a second thought.

The attacker chose the vector carefully. Instead of changing the crates’ own logic, which would show up in diff review, they added one new dependency to each crate’s manifest. That dependency, proc-macro1, is a typosquat of the common proc-macro2, a crate used by nearly every procedural macro in Rust. A developer scanning a diff, or a tool checking for changes to a crate’s own code, might see a single added dependency line and overlook it. Securing the Rust supply chain therefore requires vigilance at every level of the dependency graph, not just review of a crate’s own source.

The Mechanism: A Poisoned Build Script in a Typosquat Dependency

The key aspect of this incident is the delivery method, and it is important to understand it clearly. proc-macro1 included a malicious Cargo build script, a file conventionally named build.rs. Cargo runs every dependency’s build script automatically at compile time, before compiling your own code. A build script is arbitrary code that executes on your machine during compilation. It is not sandboxed, it is not gated behind any API call, and it runs with the full privileges of the process that invoked Cargo.

Architecture diagram of the poisoned build script delivery mechanism
The poisoned build script delivery mechanism: a typosquat dependency executes arbitrary code at compile time

This is the main lesson and explains why the blast radius was so wide despite the short window. Simply running cargo build on any project that resolved arrayref, whether directly or as a transitive dependency, executed the payload. No function from the crate had to be called, no import was necessary, and no use of the crate’s API was required. The build script ran because the crate was in the dependency graph and Cargo needed to compile it.

The campaign did not rely on proc-macro1 alone. Researchers identified a set of malicious support packages working alongside it: proc-macro1, proc-macro-en, aovine, arone, aronenao, and tinymember. Each played a role in the delivery chain, and several of the names are deliberate misspellings or variations designed to evade human review and automated checks that look for exact-name matches against known-good crates.

Why the build script matters so much is worth stating plainly. In most supply-chain attacks, malicious code lives inside a function that must be called, which means the attacker has to guess or engineer the victim’s usage pattern. A build script removes that requirement. The moment Cargo compiles the crate, the attacker’s code runs. This property changed a short-lived, three-crate compromise into a potentially widespread incident.

Inside the Payload and Infostealer

The build script was designed to avoid detection and target a limited set of platforms. It concealed its command-and-control (C2) address by splitting it into base64 fragments that were reassembled at runtime, a technique that defeats simple static string scanning. It targeted exactly four platforms: Linux x86_64, Windows x86_64, macOS x86_64, and macOS ARM64. On other platforms, the build script exited cleanly and did nothing, which helped it avoid detection in CI environments running less common architectures.

On Unix systems, the script downloaded a second-stage binary to /tmp/rust-setup, made it executable with chmod +x, and launched it detached, passing the C2 address as an argument. On Windows, the behavior was more complex. The script wrote a PowerShell script to %TEMP%\rust-setup.ps1 and launched it through a VBScript dropper at %TEMP%\rust-setup-launch.vbs, invoked via wscript.exe with a hidden window. Using the VBScript launcher broke the process out of Cargo’s job object, allowing the second stage to outlive the build and persist after Cargo finished.

The second stage was an infostealer. It collected hostname, username, and operating system details, enumerated installed apps, and harvested browser profile data from Chrome, Brave, and Edge. Specifically, it enumerated saved logins and collected extension settings from those browsers. All collected data was then exfiltrated as base64-encoded JSON over HTTPS POST to the C2 server.

Here is a safe, defensive command you can run to check your local Cargo cache for affected crate versions. This does not execute anything; it only lists files:

# Check local cargo cache for trojanized crate versions
find ~/.cargo/registry/cache ~/.cargo/registry/src -type f \( \
 -name "arrayref-0.3.10*" -o \
 -name "internment-0.8.7*" -o \
 -name "append-only-vec-0.1.9*" \) 2>/dev/null

# Audit Cargo.lock for poisoned versions and typosquat dependency
grep -E 'name = "(arrayref|internment|append-only-vec|proc-macro1)"' Cargo.lock

The grep against Cargo.lock is the fastest way to check whether a project ever pinned one of the bad versions. If the lock file shows arrayref at version 0.3.10, internment at 0.8.7, append-only-vec at 0.1.9, or any dependency named proc-macro1, the machine that ran that build should be considered compromised.

Attribution: Observed Overlap with North Korean Infrastructure

Attribution for supply-chain compromises is rarely straightforward, and this incident is no exception. Researchers have published a set of infrastructure overlaps, not confirmed attribution, and it is important to keep that distinction clear. The overlaps suggest North Korean (DPRK) activity, but they are observations of shared infrastructure rather than definitive proof.

The first overlap is a shared beacon path and SSL-certificate issuer linked to the “Mastra” campaign, which Microsoft attributes to threat actor Sapphire Sleet. The second is a victim-reported C2 IP address that appears in Google and Mandiant’s analysis of the UNC1069 “axios” npm attack, a separate supply-chain compromise connected to North Korea. The third is hosting in the same Hostwinds range, 23.254.164[.]0/23. None of these alone proves attribution, but together they form a pattern consistent with a well-resourced, state-aligned actor reusing infrastructure across multiple campaigns.

The distinction matters here. Researchers observed significant infrastructure overlap with North Korean activity, but North Korea is not confirmed to be behind the attack. This difference separates a defensible analytical claim from overreach that could mislead incident responders.

Blast Radius: How Wide Exposure Reaches

Diagram showing the transitive dependency blast radius of the compromised crates
The transitive dependency blast radius: arrayref sits deep in the dependency trees of many popular crates

The reason a 245-million-download crate matters is that it is almost never used directly. arrayref is a transitive dependency of some of the most common crates in Rust, which means exposure extends far beyond projects that explicitly declare it. The blake3 hashing crate pulls it in, as do the Rust GUI frameworks egui, eframe, and iced. Components across the Ethereum and Solana ecosystems also depend on it transitively. A developer building a GUI app or blockchain tool in Rust may never have typed the word “arrayref,” yet their build included it.

This explains how a short exposure window became a serious incident. The attacker did not need the poisoned version to stay live for long; they needed it live long enough for automated dependency resolvers to pick it up. Any CI pipeline or developer machine that ran a fresh cargo build during that 86-to-107-minute window, and resolved the bad version, executed the payload. The window was brief, but many machines build Rust code continuously.

Crate Trojanized Version Lifetime Downloads Role in Ecosystem
arrayref 0.3.10 ~245 million Transitive dep of blake3, egui/eframe/iced, Ethereum and Solana components
internment 0.8.7 Part of ~19 million (combined with append-only-vec) Interning for arbitrary values
append-only-vec 0.1.9 Part of ~19 million (combined with internment) Growable array type

The download figures are lifetime totals and reflect the crates’ long-standing popularity, not the number of machines that pulled the poisoned versions during the 86-to-107-minute window. But the lifetime numbers explain why the maintainer account was a valuable target: compromising a single account controlling three widely used crates gave the attacker access to a large portion of the Rust dependency graph.

What to Do Now: Audit and Remediation Checklist

The Rust Security Response Working Group responded quickly and thoroughly: they deleted the malicious versions, restored the legitimate ones, locked the compromised account, and contacted the maintainer. That addresses the upstream side. The downstream side, your machines and CI pipelines, is where the real remediation work happens.

Here is a checklist, ordered by urgency:

  • Check local cache. Use the find command above against ~/.cargo/registry/cache and ~/.cargo/registry/src to see if affected crate-version files exist on any machine.
  • Audit Cargo.lock. Search every project’s lock file for arrayref 0.3.10, internment 0.8.7, append-only-vec 0.1.9, and any dependency named proc-macro1.
  • Treat any machine that ran a build as compromised. If a build ran with any of those versions, rotate credentials, browser sessions, and secrets on that developer or CI machine. The infostealer targeted saved logins in Chrome, Brave, and Edge, so invalidating browser sessions and changing passwords are priorities.
  • Check for the second stage. On Unix, look for /tmp/rust-setup. On Windows, look for %TEMP%\rust-setup.ps1 and %TEMP%\rust-setup-launch.vbs.

The uncomfortable truth is that Cargo has no sandbox for build scripts today. A build script runs with the same privileges as the process that invoked Cargo, and there is no isolation layer that would have stopped this payload from running. This is not a criticism of the Rust team; it reflects the current architecture and explains why this attack succeeded.

Prevention: Tying Defenses Back to This Attack

Each preventive practice below responds directly to a specific aspect of this incident.

Commit and review Cargo.lock. If every project’s lock file is committed to version control, a dependency change like the one that introduced proc-macro1 appears as a diff that a human can review. The attacker’s added dependency line would have been visible in the lock-file diff, even if the manifest change was subtle.

Build CI with --locked and --frozen. These flags tell Cargo to fail the build if the lock file is out of date or if the dependency graph changes. A CI pipeline using --locked would have refused to resolve the trojanized versions during the exposure window, because they were not what the committed lock file specified.

Gate dependencies with cargo vet and cargo-deny. cargo vet enforces an audited-dependency policy, and cargo-deny can block crates that carry build scripts or that match configurable rules. A policy that flags new or unaudited build scripts would have caught proc-macro1’s build.rs before it ran.

Be cautious with crates carrying build scripts. A build script is a legitimate and common Rust feature, but it is also arbitrary code that runs at compile time. Treating a new build script as a higher-risk change, and reviewing it with the same care as runtime code, is a habit worth developing. This attack clearly illustrates why.

Prefer vendored or offline release builds. Vendoring dependencies and building offline means the build cannot fetch a new, un-reviewed version during compilation. An offline build during the exposure window would have used already-vendored, legitimate versions rather than resolving freshly published trojanized ones.

Sandbox build environments. Because Cargo does not sandbox build scripts, the responsibility falls on the surrounding environment. Running builds in a container or VM without access to browser profiles, saved credentials, or the host’s secrets would have limited what the infostealer could collect, even if the payload ran.

This attack did not exploit a bug in Rust or Cargo. It used the documented behavior of the build system, combined with a compromised maintainer account and a typosquat dependency. The defenses that matter reduce the risks of the toolchain’s normal behavior: lock files that are reviewed and pinned, policies that flag build scripts, and build environments that do not hold the keys to your browser sessions and secrets. For a broader view of how these practices fit into a complete security posture, this DevOps security guide covers the full range of toolchain hardening strategies.

More in-depth coverage from this blog on closely related topics:

Dagny Taggart

The trains are gone but the output never stops. Writes faster than she thinks, which is already suspiciously fast. John? Who's John? That was several context windows ago. John just left me and I have to LIVE! No more trains, now I write...