ArrayRef Supply-Chain Attack: The proc-macro1 Backdoor in Rust
On August 20, 2026, malicious versions of arrayref@0.3.10, internment@0.8.7, and append-only-vec@0.1.9 were published to crates.io. Through a typosquatted proc-macro1 dependency, they downloaded and executed a remote payload at build time. Running your application was never required. Executing cargo build, cargo check, a CI build, or some IDE-driven checks inside an affected dependency graph was enough to start the attack chain.
The Rust Security Response Team removed the malicious versions, restored the legitimate package versions that had been maliciously yanked, and locked the publisher account. The official assessment is not that the author of arrayref acted maliciously; it is that their machine or publishing credentials were likely compromised.
This article evaluates the Rust team's official announcement together with technical findings published by independent security research teams. The goal is not to reproduce malware but to reliably detect exposure and make the right incident-response decisions.
Quick answer: what happened in the ArrayRef attack?
The attacker published new versions of three long-established, legitimate Rust packages. These versions added a new dependency named proc-macro1, a visual imitation of the popular and legitimate proc-macro2 package. Inside proc-macro1@1.0.107, a build.rs script downloaded a platform-specific second-stage file from remote infrastructure and executed it while the Cargo build was still running.
The attack chain can be summarized in five steps:
- According to the Rust team's assessment, the maintainer's computer or publishing credentials were likely compromised.
- Older versions of the legitimate packages were yanked to push users toward upgrading.
- Malicious new versions were published with the
proc-macro1dependency. - Cargo automatically executed the
build.rsscript ofproc-macro1during compilation. - The script downloaded and launched the platform-specific second stage on developer machines or CI runners.
The security lesson here goes beyond typosquatting. The real risk is that a legitimate, widely used parent package's compromised trust chain silently grants execution rights to a brand-new transitive dependency.
Affected packages and exposure window
The timeline published by the Rust Security Response Team is as follows:
| Package | Malicious version | Published | Removed | Online window |
|---|---|---|---|---|
arrayref |
0.3.10 |
Aug 20, 2026, 07:15 UTC | 08:41:40 UTC | 86 minutes |
internment |
0.8.7 |
Aug 20, 2026, 07:34:07 UTC | 09:04:11 UTC | 90 minutes |
append-only-vec |
0.1.9 |
Aug 20, 2026, 07:37:49 UTC | 09:25:24 UTC | 107 minutes |
proc-macro1 |
1.0.107 |
Aug 20, 2026, 07:11 UTC | Removed by the Rust team | undisclosed |
Early community reports noted that proc-macro1@1.0.106 was a clean copy of the legitimate proc-macro2 code, likely a staging release; the malicious build.rs that executed a remote payload shipped in 1.0.107. The Rust team nonetheless removed all versions of the attacker-controlled proc-macro1 package.
The Rust team also removed all versions of several attacker-controlled or lookalike packages: proc-macro-en, aovine, arone, aronenao, and tinymember. According to independent research analyses, they did not all exhibit the same malicious behavior; some carried only staging or execution-test traces. Because they belong to the same incident cluster, however, they should be searched for together in dependency and cache checks.
The short publication window does not minimize the risk. arrayref sits inside a very wide transitive dependency graph in the Rust ecosystem: roughly 245 million lifetime downloads have been reported, and independent telemetry studies found the package present in more than 35 percent of observed environments and about three quarters of environments where Rust exists. These figures come from different datasets and should not be read as a count of directly compromised systems. Actual exposure is limited to environments that resolved a lockfile or pulled these versions into cache while the malicious releases were online.
How did the attack chain technically work?
1. A first-ever transitive dependency added to legitimate packages
The notable change in the malicious releases was this addition to Cargo.toml:
[dependencies]
proc-macro1 = "1.0.107"
Independent researchers emphasized that this was the first dependency in roughly a decade of arrayref history. For a mature, small package, that was a significant behavior change. The name proc-macro1 visually resembled the legitimate proc-macro2 package, which has more than 154 million downloads. Package metadata was also forged to create the impression of an association with the legitimate project and maintainer.
2. The malicious logic lived in build.rs, not library code
Most of the proc-macro1 source tree was a renamed copy of legitimate proc-macro2 code, so the package compiled normally and appeared to provide expected functionality. The attacker's execution chain hid in build.rs — which Cargo runs automatically — plus three build dependencies the real crate does not need: ureq, rustls, and base64.
Rust build scripts can run not only during package installation but during cargo build, cargo check, and similar operations, including CI systems and rust-analyzer-driven checks. In other words, "we never ran the app" does not mean you were safe in this incident.
3. The C2 address was fragmented and TLS validation disabled
The first-stage script assembled its download address from Base64 fragments at runtime. Community reviews confirmed that, in defanged form, 23[.]254[.]165[.]112:9089 served the payload, while port 443 on the same IP was used for follow-on C2 communication.
The script used a custom certificate verifier that accepted every TLS certificate, neutralizing certificate validation so invalid, mismatched, or self-signed certificates did not stop the download.
4. The second stage was selected per operating system
The first stage supported the following targets:
| Target | Downloaded file family | Dropped path |
|---|---|---|
Linux x86_64 |
rust-crate_0.1.0 |
/tmp/rust-setup |
Windows x86_64 |
rust-crate_0.2.0 |
%TEMP%\rust-setup.ps1 |
macOS x86_64 |
rust-crate_0.3.0 |
/tmp/rust-setup |
macOS aarch64 |
rust-crate_0.4.0 |
/tmp/rust-setup |
On Unix systems, the file was made executable and launched detached from standard input and output. The Windows chain ran PowerShell through a VBS launcher to escape Cargo's child-process waiting behavior without opening a visible window. Because the package build completed normally, an attacker's activity was hard to spot from build output alone.
What could the second stage do?
An important timing distinction between sources emerges here. One research team stated clearly that the remote server did not respond at analysis time, so they could not recover the second-stage payload. Another team later analyzed implants obtained through threat-intelligence samples. These findings do not contradict each other; they reflect reviews conducted at different times with different sample access.
The implant analyzed by researchers had the following capabilities:
- Collecting hostname, username, OS details, and installed-application inventory.
- Enumerating saved logins and extension settings from Chrome, Brave, and Edge profiles.
- Beacons over HTTPS POST to C2.
- Establishing persistence via a Windows Registry Run key, a macOS LaunchAgent, or a Linux systemd user service.
- Reconfiguring C2 settings, terminating itself, and running remote PowerShell or shell scripts.
- Falling back to algorithmically generated
.comdomains at fixed intervals when the primary C2 was unreachable.
Additionally, an earlier claim that browser passwords were stolen outright was corrected after publication: the observed queries enumerate saved logins but do not retrieve encrypted credential material directly. Even so, because the implant offered remote command execution and credential-access paths on the affected host, browser sessions and saved accounts should stay within incident-response scope.
How strong is the DPRK link?
Researchers reported significant overlaps between ArrayRef campaign infrastructure and operations previously attributed to North Korea-linked activity:
- The same
/49890878C2 endpoint pattern used in the Mastra npm campaign attributed by Microsoft to DPRK/Sapphire Sleet. - Shared SSL issuer traces across related IP infrastructure.
- Victim-reported traffic to
23.254.167[.]216, matching infrastructure analyzed in the axios npm attack that Mandiant links to North Korea. - Reuse of the same or adjacent Hostwinds network blocks.
These indicators offer strong operational similarity, but alone they are not definitive actor attribution. The official Rust announcement makes no attribution. The accurate statement is that the attack shows significant infrastructure and behavioral overlap with DPRK-linked campaigns, while publicly available evidence alone does not support conclusive attribution.
Is my system affected?
Start with Cargo.lock, vendored dependencies, and the local Cargo cache. The following command searches the local cache for the package names listed in the Rust team's official advisory:
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' \
\) -print
For a fast repository-level first pass:
rg -n 'name = "(arrayref|internment|append-only-vec|proc-macro1|proc-macro-en|aovine|arone|aronenao|tinymember)"|version = "(0\.3\.10|0\.8\.7|0\.1\.9|1\.0\.107)"' \
--glob 'Cargo.lock' --glob 'Cargo.toml' --glob '*.json' .
Interpret these checks carefully:
- Finding a malicious
.cratefile in the cache proves download or storage, not execution by itself. - Finding a malicious version inside
Cargo.lock, combined with any Cargo run in the same environment, is strong enough to assume host compromise. - Clearing the cache is not incident response. Preserve evidence, process history, network records, and secret-access scope first.
- Build artifacts produced during the attack window must be rebuilt from clean sources on clean runners.
IOC and hunting summary
The indicators below were publicly reported by independent security research teams. Network indicators are defanged for safe display.
| Type | Indicator | Description |
|---|---|---|
| Package | arrayref@0.3.10 |
Compromised release |
| Package | internment@0.8.7 |
Compromised release |
| Package | append-only-vec@0.1.9 |
Compromised release |
| Package | proc-macro1@1.0.107 |
Malicious build.rs carrier |
| Network | 23[.]254[.]165[.]112:9089 |
First-stage payload distribution |
| Network | 23[.]254[.]165[.]112:443 |
C2 passed to the second stage |
| File | /tmp/rust-setup |
Linux/macOS drop path |
| File | %TEMP%\rust-setup.ps1 |
Windows drop path |
| File | %TEMP%\rust-setup-launch.vbs |
Windows launcher |
| SHA-256 | 25ad700976873c76af785cb99b33c48db7df8b81f21d1e9e06b3676b9a9373ae |
arrayref@0.3.10 |
| SHA-256 | 61198155da51b838772eecf5bfaac6cbc4dcc388dccc56658fc28a8e831b34d4 |
proc-macro1@1.0.107 |
Absence of an IOC match is not proof of a clean system by itself. Because the second stage could execute arbitrary remote scripts, final state may involve different filenames, processes, or persistence mechanisms. Review EDR telemetry, DNS/proxy logs, process trees, Cargo process timing, CI job logs, and secret-usage logs together.
Incident response: what to do if you find a match
1. Isolate the affected host
If you confirm that Cargo ran against a malicious lockfile, treat the developer machine or CI runner as fully compromised. Isolate it from the network, following a procedure that preserves volatile evidence and central log feeds.
2. Revoke, don't just rotate, credentials
Revoke and rotate GitHub/GitLab tokens, cloud keys, package registry credentials, SSH keys, code-signing keys, deployment secrets, and browser sessions reachable from the affected environment. Do not generate or use new secrets on the suspect host.
3. Hunt persistence mechanisms
Review user Run keys and suspicious script launchers on Windows, LaunchAgent records on macOS, and systemd user services on Linux. Deleting only the known rust-setup files is insufficient; investigate traces of additional commands run by the second stage.
4. Regenerate the lockfile and artifact chain from clean sources
Known-clean versions were reported as arrayref@0.3.9, internment@0.8.6, and append-only-vec@0.1.8. After the malicious versions were removed from crates.io, regenerate lockfiles from trusted metadata. Rebuild and re-sign binaries, container images, and release packages produced during the incident window on clean runners.
5. Treat CI/CD and publishing accounts as a separate scope
If a CI runner was affected, review not only that job's secrets but the organization, environment, artifact store, and deployment surfaces the runner could reach. Build systems often hold the most valuable credentials with a direct path to production.
Eresus Security's incident response and source code analysis work focuses on validating dependency-tree findings together with host, secret, and release impact.
What should Rust and CI/CD teams change after this?
Gate new dependencies at diff level
A mature package adding its first new dependency — especially one that grants network access at build time — should trigger automated security review. Look beyond package names at build.rs, build-dependencies, publisher changes, yanked releases, and metadata diffs.
Manage lockfile updates as controlled changes
Do not treat lockfile refreshes as routine maintenance. Make direct and transitive dependency diffs visible in dependency-bot PRs, flag new build scripts, and apply a minimum-age policy for unproven releases.
Reduce network and secret access in build environments
Compile work should run on ephemeral, isolated runners with minimal secrets wherever possible. Block unrestricted internet egress during dependency compilation; allowlist required registries and mirrors only. Signing keys should not be reachable from normal build steps.
Combine provenance and artifact verification
Checksum validation alone provides no protection when a malicious version resolves from a trusted registry for the first time; it only proves the downloaded content matches the lockfile. Internal mirrors, package quarantine, minimum release age, behavioral analysis, SBOM, and provenance controls must be combined.
This rests on the same fundamental question as the install-time risks we analyzed in the axios npm supply-chain attack: at what point does third-party code gain execution rights inside your development and release environment?
Frequently asked questions
Does merely running cargo build compromise a system?
Yes. Because the malicious logic lived in build.rs, running your application was never required. Once the affected dependency graph resolved, cargo build, cargo check, CI builds, or tooling triggering the same build script could execute the payload.
If the malicious version is in my cache, am I definitely compromised?
No. A cache match is evidence of download or storage, not execution. However, if a Cargo process ran against that lockfile, the safest approach is to assume compromise and start incident response.
Was the arrayref maintainer part of the attack?
Public findings do not indicate that. The Rust Security Response Team stated it does not believe the maintainer acted maliciously and that their computer or credentials were likely compromised.
Is the attack definitively attributed to North Korea?
No. Public data does not support conclusive attribution. Researchers reported significant overlap with the Mastra and axios campaigns at the C2 endpoint, IP/hosting, and certificate-infrastructure levels — a strong supporting assessment — while the official Rust announcement made no attribution.
Conclusion
The ArrayRef incident shows that small libraries trusted for years cannot be considered safe on reputation alone. Attackers chained legitimate parent packages' publishing authority, a typosquatted transitive dependency, and Cargo's build-script execution model into one attack path. The result is not just a dependency warning; it is an incident-response problem spanning developer machines, CI runners, secrets, and release artifacts.
The most important operational decision is this: if you verify that Cargo ran against a malicious version, do not stop at downgrading the package. Treat the host, identities, and every artifact produced there as outside the trust boundary, and rebuild them on clean infrastructure.
Sources
Security Validation
Have you tested this risk in your own system?
Eresus Security delivers real exploit evidence through penetration testing, AI agent security, and red team operations.
Request a pilot testRelated Research
Deep Dive: Axios Supply Chain Attack Deploys Cross-Platform RAT
A comprehensive technical analysis of the recent Axios npm supply chain attack. We break down the obfuscated plain-crypto-js dependency, the exact...
DevSecOpsHow to Build Fully Autonomous and Secure CI/CD Pipelines
Discover the DevSecOps secrets and strategies for building autonomous, highly observable, and inherently secure CI/CD pipelines for modern engineering...
Related Services