On August 20, a compromised release of the Rust crate arrayref — version 0.3.10 — appeared on crates.io. The library code was clean. Every line of it. What wasn’t clean was a new dependency: a typosquatted crate called proc-macro1, a one-character misspelling of David Tolnay’s widely used proc-macro2. That crate’s build script downloaded a second-stage binary over TLS with no certificate checks and ran it. Not when the code shipped. Not when a user ran the program. When a developer typed cargo build.

The crates.io team yanked the malicious versions within hours. The Rust Security Response Team locked the maintainer’s account and said the likeliest explanation was compromised credentials. Six attacker-created crates were deleted. By the standards of supply chain incidents, this was a fast, competent response.

And it will happen again, because the response treated the symptom, not the design.

The Blast Radius Is the Developer’s Machine

The mental model most security teams carry around is that a supply chain attack means malicious code that ships to end users. A bad library gets compiled into a product, the product gets distributed, and the bad code runs on customer machines. That’s the SolarWinds model, the xz-utils model.

The arrayref attack doesn’t fit that model. The payload ran at build time, on the machines of developers and CI pipelines. Those machines are not production servers. They are often more valuable than production servers. A CI pipeline that builds and deploys code typically holds signing keys, cloud credentials, access to internal registries, and a direct line to production infrastructure. A developer’s laptop holds SSH keys, password managers, and session tokens for every service the company uses.

The attacker didn’t need to get their code into production. They needed to get it onto the machine that deploys to production. That’s a different target, and it’s a softer one, because the security posture of a build environment is almost always weaker than the posture of the runtime environment it feeds.

Build Scripts Are Arbitrary Code Execution, Normalized

Here’s the part the postmortems keep dancing around. In the Rust ecosystem, a build.rs file is arbitrary Rust code that runs on your machine every time you compile. It can read files, make network requests, spawn processes, write anywhere the user has permissions. There is no sandbox. There is no permission model. There is no prompt. Cargo does not ask, “This crate wants to execute code on your machine at compile time — is that acceptable?” It just runs it.

This is not a vulnerability. It’s a feature. Build scripts exist to do legitimate things: compile C dependencies, generate code, detect platform features. The Rust community has accepted, for years, that cargo build means “run arbitrary code from every crate in my dependency tree, on my machine, with my privileges.”

The arrayref attacker didn’t exploit a zero-day. They used the feature exactly as designed. The typosquatted crate’s build script did what build scripts are allowed to do. The only thing that made it malicious was intent, and intent is the one thing no scanner can reliably detect.

The fixes being proposed — more 2FA, more scanning, more SBOMs — are all aimed at catching bad actors. None of them change the fact that the build step is an unguarded remote code execution vector sitting inside every Rust developer’s workflow. Sandboxing build scripts, requiring explicit opt-in for network access at build time, namespace protection on crates.io — these are design changes, and the Rust community has resisted them for years because they would make cargo build slightly less frictionless.

The Defense Worked Because the Attacker Was Sloppy

It’s worth being precise about why this attack was caught quickly. The typosquat was obvious. proc-macro1 versus proc-macro2 is the kind of one-character difference that a human reviewer spots immediately. The build script’s behavior — downloading a binary over TLS with certificate checks disabled — is the kind of thing that trips every static analyzer in existence.

The next attacker won’t be sloppy. They’ll compromise a maintainer account for a crate with a legitimate build script and make a subtle change. They’ll use a typosquat that’s harder to spot, or no typosquat at all — just a compromised account on a crate people already trust. The payload will be small, quiet, and patient.

When that happens, the question won’t be whether the crates.io team can yank the bad version fast enough. It will be whether the Rust ecosystem is willing to change a design that treats arbitrary code execution at build time as the cost of doing business. So far, the answer has been no.

Sources