Nobody reads the diff: the arrayref supply chain compromise
Supply chain attacks on package registries are not new, and this one will not be the last. The arrayref compromise caught our attention because the Rust ecosystem has a tool that would have flagged this malicious update. But flagging is not the same as catching. Here is what happened, and what we took away from it.
What happened?
On 20 August 2026, the Rust Security Response Team received a report that a crate called proc-macro1 downloaded and executed a backdoor at compile time. It turned out three legitimate Rust crates had been updated to depend on the malicious crate:
- arrayref 0.3.10
- append-only-vec 0.1.9
- internment 0.8.7
The attacker published new versions of these crates where one single line was relevant for the attack:
[dependencies]
proc-macro1 = "1.0.107"The problem is that proc-macro1 is a typosquat of proc-macro2, a legitimate crate. The attackers copied the real crate’s source code and documentation, and added the malicious part to its build.rs. The build.rs is a special file that Cargo executes at compile time to, e.g., generate code or call external tools.
The malicious crates were deleted from crates.io within hours, so we relied on the analysis published by researchers at Wiz. According to them the malicious script reconstructs an attacker-controlled address, disables TLS certificate validation, and then downloads and executes a backdoor on the victim’s system. This could be a developer’s computer where the attacker can collect e.g. SSH keys or a CI build server that might expose production secrets. The backdoor collects host information, installs persistence, and lets the attacker run commands afterwards.
These were new patch versions so a cargo update or a fresh transitive pull would have resolved the highest compatible version, so 0.3.10 arrives without requiring a conscious version bump.
Are you affected?
The Rust blog published a command to check whether the affected crates have been pulled locally:
find ~/.cargo/registry/cache -type f \( \
-name 'append-only-vec-0.1.9.crate' -o \
-name 'arrayref-0.3.10.crate' -o \
-name 'internment-0.8.7.crate' -o \
-name 'proc-macro1-*.crate' -o \
-name 'proc-macro-en-*.crate' -o \
-name 'aovine-*.crate' -o \
-name 'arone-*.crate' -o \
-name 'aronenao-*.crate' -o \
-name 'tinymember-*.crate' \
\) -printIf your machine built one of these versions, treat it as compromised.
We already know how to mitigate such attacks
In the Rust ecosystem the answer to insecure code in third-party dependencies is called cargo-vet. It enforces that every active dependency has been audited or explicitly exempted, and for the audit part it supports delta audits, so you only review changes between two versions instead of the whole crate.
In this case cargo-vet would have flagged the update in two places. First, updating to arrayref 0.3.10 would have required a delta audit from 0.3.9. Second, proc-macro1 would have entered the graph as a completely new crate with no audit history at all.
It is worth mentioning that the second flag is the one that is harder to miss. Cargo-vet lets you mark a publisher as trusted, so anyone who had trusted the maintainer of arrayref would have accepted the malicious version 0.3.10 without complaint, as the attacker published it from the legitimate account. However, proc-macro1 would still have been flagged, because a new crate from an unknown publisher has no trust to inherit. We noticed that the legitimate proc-macro2 is published by dtolnay, and according to the Wiz research team proc-macro1 came from dtolney. That is one letter off, the same typosquatting approach as the crate name.
However, cargo vet is not perfect when it comes to build-time dependencies. While cargo vet does not execute build scripts, cargo build does. So the order has to be cargo update, then cargo vet, then cargo build. If the last two steps are reversed the backdoor runs before the check ever happens. This is what cargo-vet’s safe-to-run criterion exists for, and CI should enforce it before any build step touches production tokens. However, currently there is no easy way to enforce this for local developer machines.
So the control is there. The question is what happens after it flags a dependency.
Nobody reads the diff
cargo-vet flags these dependencies and stops the CI, it is then up to the developer to move past this disruption. This necessarily involves that someone reads and audits the new code that changed between releases. Or in the case of proc-macro1 to perform a full audit of a new crate.
In our analysis of 408 cargo-vet projects, a project that wants to stay fully vetted faces a median of 8,700 changed lines of dependency code per week. That is why the average project carries around 206 exemptions.
The realistic response to a failing CI build at 09:00 on a Thursday is
cargo vet regenerate exemptions, not reading all the code in there.
Where Light Squares comes into play
The malicious change was tiny and actually not that hard to detect for anyone who looked at the code. A long-stable utility crate suddenly adds a new dependency in a patch release, pointing at a typosquatted version of one of the most-used crates in the ecosystem. This needs no CVE and no knowledge of the attack itself. It only requires someone reading the diff in time.
That is the gap that we are building Dependency Canary for. It provides AI-supported audits of dependency updates, published within 24 hours of a release and in cargo-vet compatible format, so a verdict already exists when the CI pipeline starts to fail.
It is fair to ask why only 408 projects across GitHub adopted cargo-vet. We think the answer is 8,700 changed lines of code per week. Cargo-vet asks for work the ecosystem cannot currently supply, and cutting that burden is the point of Dependency Canary.
Light Squares builds security products that replace trust with proof. Dependency Canary is in development and we are looking for design partners. Get in touch.

