Introduction
The Rust Project has pulled malicious versions of three widely used crates — arrayref 0.3.10, internment 0.8.7, and append-only-vec 0.1.9 — after an attacker compromised a maintainer account and published releases carrying a typosquatted dependency. That dependency's build script (build.rs) downloaded and executed remote malicious code during compilation. Together, these crates account for roughly 245 million downloads, meaning a very large number of projects transitively depend on them — arrayref alone is a dependency in serialization, cryptography, and parsing stacks across the ecosystem.
This is the nightmare scenario for software supply chain defense: the malicious code doesn't ship in the published artifact — it executes at build time, on developer workstations and CI/CD runners, with the full privileges of the build user. That means potential access to SSH keys, cloud credentials, signing keys, environment secrets, and any artifact the pipeline produces. If your organization builds Rust code anywhere — laptops, GitHub Actions, GitLab runners, self-hosted build farms — you need to assume exposure until proven otherwise.
No CVE has been assigned to this incident as of publication. This is a compromised-package event, not a vulnerability in the crates' legitimate code. Detection and response must therefore focus on behavioral indicators (build scripts making network egress, cargo spawning unexpected child processes) and dependency forensics (did any build resolve the poisoned versions?).
Technical Analysis
Affected packages and versions
| Crate | Malicious version | Notes |
|---|---|---|
arrayref | 0.3.10 | Extremely common transitive dependency (crypto/serialization stacks) |
internment | 0.8.7 | String/data interning library |
append-only-vec | 0.1.9 | Data structure crate |
All three malicious releases were published from the same compromised owner account, which is the key tell: this was an account takeover, not three independent incidents. The attacker introduced a typosquatted dependency into each release. Cargo's build process then executed that dependency's build.rs build script, which fetched and ran a remote payload during compilation.
Attack chain (defender's view)
- Account compromise: The attacker gains control of a legitimate crates.io maintainer account — a reminder that package registry accounts are high-value identity targets and must be protected with phishing-resistant MFA.
- Malicious publish: New patch/minor versions of legitimate, trusted crates are published. Because version numbers look routine, automated dependency updaters (Dependabot, Renovate,
cargo update) will happily pull them. - Typosquat dependency injection: The poisoned release adds a dependency on an attacker-controlled crate whose name mimics a legitimate one. Typosquats survive casual review in
Cargo.lockdiffs. - Build-time execution: The malicious crate's
build.rsruns automatically duringcargo build/cargo check/cargo test. Rust build scripts are arbitrary native code execution by design — they compile and run on the host before the main crate builds. - Remote payload retrieval: The build script reaches out to an attacker-controlled endpoint, downloads a second-stage payload, and executes it. This happens on developer machines and CI runners, typically with access to source code, credentials in environment variables, and artifact signing infrastructure.
Why build-time execution is uniquely dangerous
Most supply chain incidents (e.g., malicious npm packages) execute at runtime in deployed applications — bad, but at least production EDR sees the payload in a server context. Build-time execution instead lands on:
- Developer workstations — where SSH private keys, browser sessions, cloud CLI credentials, and password managers live.
- CI/CD runners — where artifact signing keys, deployment credentials, registry tokens, and secrets managers' short-lived tokens are exposed.
- Air-gapped-adjacent build environments — that often have weaker EDR coverage than production because "it's just a build server."
Additionally, the payload is fetched from a remote URL at compile time, so static analysis of the published crate source may reveal only a small bootstrap (the download logic), not the actual malware. The second stage can be served conditionally — targeted by IP, user agent, or CI environment fingerprint — and can disappear entirely by the time incident responders look.
Exploitation status
- Confirmed in the wild: Yes — malicious versions were published to crates.io and publicly downloadable before the Rust Project deleted them. Any build that resolved the affected versions during the exposure window executed the malicious build script.
- CVE assigned: None at time of publication. Do not rely on CVE-based scanning alone to find exposure — SBOM and lockfile analysis are the correct instruments here.
- CISA KEV: Not listed (no CVE). Treat the crates.io advisory and Rust Project security announcements as the authoritative references.
Detection & Response
Detection strategy centers on three observable behaviors: (1) presence of the poisoned versions in lockfiles/SBOMs, (2) cargo/rustc spawning network-capable child processes during builds, and (3) network egress from build processes to non-registry destinations (legitimate cargo traffic goes to crates.io / static.crates.io / GitHub — a build script fetching from arbitrary hosts is anomalous).
Sigma Rules
---
title: Cargo Build Process Spawning Network or Scripting Tools
description: Detects cargo, rustc, or cargo build scripts spawning network utilities or script interpreters, consistent with a malicious build.rs downloading a remote payload as seen in the arrayref/internment/append-only-vec crates.io supply chain attack.
id: 4b7e2c91-6a3d-4f58-9c21-8e0b5d3a7f42
status: experimental
references:
- https://thehackernews.com/2026/08/rust-supply-chain-attack-puts-build.html
- https://attack.mitre.org/techniques/T1195/002/
- https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/08/10
tags:
- attack.initial_access
- attack.t1195.002
logsource:
category: process_creation
product: windows
detection:
selection_parent:
ParentImage|endswith:
- '\cargo.exe'
- '\rustc.exe'
- '\rustup.exe'
selection_child:
Image|endswith:
- '\powershell.exe'
- '\pwsh.exe'
- '\cmd.exe'
- '\curl.exe'
- '\wget.exe'
- '\certutil.exe'
- '\bitsadmin.exe'
- '\mshta.exe'
- '\rundll32.exe'
condition: selection_parent and selection_child
falsepositives:
- Legitimate crates with build scripts that shell out to system tools (cc, nasm, protoc wrappers) - baseline your build environment and tune per build host
level: high
---
title: Linux Cargo Build Spawning Download or Execution Chains
description: Detects cargo/rustc build scripts on Linux spawning downloaders or shells, matching the build-time remote payload retrieval technique used in the crates.io supply chain compromise of arrayref 0.3.10, internment 0.8.7, and append-only-vec 0.1.9.
id: 9c1d5e84-2f7a-4b6c-a3d1-5e9f0b8c4d63
status: experimental
references:
- https://thehackernews.com/2026/08/rust-supply-chain-attack-puts-build.html
- https://attack.mitre.org/techniques/T1195/002/
- https://attack.mitre.org/techniques/T1105/
author: Security Arsenal
date: 2026/08/10
tags:
- attack.initial_access
- attack.t1195.002
- attack.t1105
logsource:
category: process_creation
product: linux
detection:
selection_parent:
ParentImage|endswith:
- '/cargo'
- '/rustc'
selection_child:
Image|endswith:
- '/curl'
- '/wget'
- '/bash'
- '/sh'
- '/python'
- '/python3'
- '/perl'
- '/nc'
- '/ncat'
condition: selection_parent and selection_child
falsepositives:
- Build scripts for crates with native dependencies frequently invoke sh/bash via cc-rs and pkg-config - restrict alerting to download/execution combinations on build hosts and tune per CI image
level: high
---
title: Build Process Network Egress to Non-Registry Destinations
description: Detects cargo or rustc processes initiating network connections to destinations outside known Rust package infrastructure (crates.io, static.crates.io, GitHub). Malicious build scripts in this campaign fetched second-stage payloads from attacker-controlled infrastructure during compilation.
id: 2f8a4d67-1c9e-4b35-8f02-7a6c1e5d9b84
status: experimental
references:
- https://thehackernews.com/2026/08/rust-supply-chain-attack-puts-build.html
- https://attack.mitre.org/techniques/T1071/001/
author: Security Arsenal
date: 2026/08/10
tags:
- attack.command_and_control
- attack.t1071.001
logsource:
category: network_connection
product: windows
detection:
selection:
Image|endswith:
- '\cargo.exe'
- '\rustc.exe'
- '\build-script-build.exe'
filter_crates:
DestinationHostname|endswith:
- 'crates.io'
- 'static.crates.io'
- 'github.com'
- 'githubusercontent.com'
- 'githubassets.com'
condition: selection and not filter_crates
falsepositives:
- Builds using private registries, mirrors, or vendored git dependencies - add your internal registry domains to the filter
level: high
A note on the third rule: on Windows, build scripts compile to build-script-build.exe — a predictable, high-fidelity process name. A build-script-build.exe process making any network connection is suspicious almost by definition; legitimate build scripts compile code, they don't phone home. On Linux the equivalent is the build-script-build binary under target/*/build/*/build-script-build. If your EDR telemetry supports it, alerting specifically on network egress from these binaries gives you a durable, low-noise control against this entire class of attack — not just this campaign.
KQL — Microsoft Sentinel / Defender
// Hunt 1: Cargo/rustc spawning downloaders or script interpreters (build-time payload retrieval)
// Covers developer workstations and CI runners onboarded to Defender for Endpoint
let BuildParents = dynamic(["cargo.exe", "rustc.exe", "cargo", "rustc", "build-script-build.exe", "build-script-build"]);
let SuspiciousChildren = dynamic(["powershell.exe", "pwsh.exe", "cmd.exe", "curl.exe", "wget.exe", "certutil.exe",
"mshta.exe", "rundll32.exe", "curl", "wget", "bash", "sh", "python", "python3", "perl", "nc", "ncat"]);
DeviceProcessEvents
| where TimeGenerated > ago(30d)
| where InitiatingProcessFileName in~ (BuildParents)
| where FileName in~ (SuspiciousChildren)
| project TimeGenerated, DeviceName, AccountName, InitiatingProcessFileName, InitiatingProcessCommandLine,
FileName, ProcessCommandLine, SHA256, ReportId
| sort by TimeGenerated desc;
// Hunt 2: Network egress from build binaries to non-registry destinations
let RegistryHosts = dynamic(["crates.io", "static.crates.io", "index.crates.io", "github.com",
"raw.githubusercontent.com", "objects.githubusercontent.com", "codeload.github.com"]);
DeviceNetworkEvents
| where TimeGenerated > ago(30d)
| where InitiatingProcessFileName in~ (dynamic(["cargo.exe", "rustc.exe", "build-script-build.exe", "cargo", "rustc", "build-script-build"]))
| where not(RemoteUrl has_any (RegistryHosts))
| where RemoteIP !startswith "10." and RemoteIP !startswith "192.168." and RemoteIP !startswith "172.16."
| summarize ConnectionCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated)
by DeviceName, InitiatingProcessFileName, RemoteUrl, RemoteIP, RemotePort
| sort by ConnectionCount desc;
// Hunt 3: Linux Syslog/Sysmon-for-Linux ingestion path — cargo spawning shells or downloaders
Syslog
| where TimeGenerated > ago(30d)
| where ProcessName in~ ("curl", "wget", "bash", "sh", "python3", "perl", "nc", "ncat")
| where SyslogMessage has_any ("cargo", "rustc", "build-script-build")
or SyslogMessage has_any ("target/debug/build", "target/release/build")
| project TimeGenerated, Computer, ProcessName, SyslogMessage
| sort by TimeGenerated desc
Add your internal crates mirror or private registry domains to RegistryHosts before deploying Hunt 2, or you will alert on your own infrastructure. Run these hunts across the full exposure window — from when the malicious versions were published until your lockfiles were confirmed clean, not just the last 24 hours.
Velociraptor VQL
-- Artifact: Hunt.CratesIO.BuildTimeExecution
-- Identify cargo/rustc build processes with suspicious child processes or
-- active network connections, plus locate affected crate versions in lockfiles.
-- Deploy as a hunt across developer workstations and CI/build hosts.
-- Section 1: Live processes - cargo/rustc lineage with network or script children
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)(cargo|rustc|build-script-build)'
OR CommandLine =~ '(?i)(curl|wget|powershell|certutil|bash|python).*(http|ftp)'
-- Section 2: Open network connections from build processes
SELECT Pid, Name, Pid as BuildPid, Status,
"Laddr" as LocalAddr, "Lport" as LocalPort,
"Raddr" as RemoteAddr, "Rport" as RemotePort
FROM netstat()
WHERE Name =~ '(?i)(cargo|rustc|build-script-build)'
AND Status =~ 'ESTABLISHED'
-- Section 3: Sweep for poisoned crate versions in Cargo.lock files
-- Adjust roots per platform; container/CI images may need /home and /workspace
SELECT FullPath, Size, Mtime,
read_file(filename=FullPath, length=200000) =~ '(?i)name = "(arrayref|internment|append-only-vec)"[^\[]*version = "(0\.3\.10|0\.8\.7|0\.1\.9)"' AS PoisonedVersionFound
FROM glob(globs=[
'C:/Users/**/Cargo.lock',
'/home/**/Cargo.lock',
'/workspace/**/Cargo.lock',
'/builds/**/Cargo.lock',
'/root/**/Cargo.lock'
])
WHERE PoisonedVersionFound
Section 3 is the highest-value component: it directly answers "did this host ever resolve the malicious versions?" On busy build runners the glob() over Cargo.lock files is cheap; the regex match is applied per-file. If you get hits, preserve the lockfile and the corresponding target/ directory before any cleanup — the build-script-build binary and any dropped second-stage artifacts are forensic evidence.
Remediation Script
Run this Bash script on Linux/macOS build hosts and CI runners to detect exposure to the poisoned crate versions. A PowerShell equivalent for Windows developer workstations follows in the same block as a commented alternative — deploy whichever matches your fleet.
#!/usr/bin/env bash
# cratesio-compromise-audit.sh
# Detects poisoned crate versions from the August 2026 crates.io supply chain attack
# (arrayref 0.3.10, internment 0.8.7, append-only-vec 0.1.9) in lockfiles,
# cargo caches, and build artifacts. Read-only: performs no destructive actions.
set -u
POISONED='^(arrayref|internment|append-only-vec)$'
BADVER_ARRAYREF="0.3.10"
BADVER_INTERNMENT="0.8.7"
BADVER_APPENDONLYVEC="0.1.9"
FOUND=0
REPORT="cratesio-audit-$(hostname)-$(date +%Y%m%d%H%M%S).txt"
echo "=== crates.io supply chain exposure audit - $(date) ===" | tee "$REPORT"
# --- 1. Scan Cargo.lock files for the malicious versions ---
echo "[1] Scanning Cargo.lock files..." | tee -a "$REPORT"
while IFS= read -r lock; do
hits=$(awk '/^name = /{n=$3} /^version = /{print n, $3}' "$lock" 2>/dev/null \
| tr -d '"' \
| awk -v a="$BADVER_ARRAYREF" -v i="$BADVER_INTERNMENT" -v v="$BADVER_APPENDONLYVEC" '
($1=="arrayref" && $2==a) || ($1=="internment" && $2==i) || ($1=="append-only-vec" && $2==v)')
if [ -n "$hits" ]; then
echo " [EXPOSED] $lock contains: $hits" | tee -a "$REPORT"
FOUND=1
fi
done < <(find /home /root /workspace /builds /srv /opt -name Cargo.lock -type f 2>/dev/null)
# --- 2. Check cargo registry cache for downloaded poisoned crates ---
echo "[2] Checking local cargo registry caches..." | tee -a "$REPORT"
for d in "$HOME/.cargo/registry/cache" "$HOME/.cargo/registry/src" /usr/local/cargo/registry; do
if [ -d "$d" ]; then
hits=$(find "$d" \( -name 'arrayref-0.3.10*' -o -name 'internment-0.8.7*' -o -name 'append-only-vec-0.1.9*' \) 2>/dev/null)
if [ -n "$hits" ]; then
echo " [EXPOSED] Cached malicious crate artifacts:" | tee -a "$REPORT"
echo "$hits" | sed 's/^/ /' | tee -a "$REPORT"
FOUND=1
fi
fi
done
# --- 3. Search build trees for build-script-build binaries from the exposure window ---
echo "[3] Checking target/ directories for recently compiled build scripts..." | tee -a "$REPORT"
find /home /root /workspace /builds -type f -name 'build-script-build' -mtime -45 2>/dev/null \
| while read -r b; do echo " [REVIEW] $b (verify crate provenance)" | tee -a "$REPORT"; done
# --- 4. Verdict ---
if [ "$FOUND" -eq 1 ]; then
echo "" | tee -a "$REPORT"
echo "RESULT: EXPOSURE FOUND. Treat host as potentially compromised:" | tee -a "$REPORT"
echo " 1. Isolate the host from the network (do NOT power off)." | tee -a "$REPORT"
echo " 2. Rotate all credentials reachable from this host: SSH keys, cloud tokens," | tee -a "$REPORT"
echo " registry tokens (crates.io, Docker, npm), CI secrets, signing keys." | tee -a "$REPORT"
echo " 3. Preserve Cargo.lock, ~/.cargo, and target/ for forensics." | tee -a "$REPORT"
echo " 4. Rebuild any artifacts produced during the exposure window from a clean host." | tee -a "$REPORT"
else
echo "RESULT: No poisoned versions detected on this host." | tee -a "$REPORT"
fi
echo "Report saved to $REPORT"
# Windows equivalent - run elevated on developer workstations and self-hosted runners
$poisoned = @('arrayref-0.3.10', 'internment-0.8.7', 'append-only-vec-0.1.9')
$exposed = $false
# 1. Scan Cargo.lock files under user profiles and common build roots
$roots = @("$env:USERPROFILE", 'C:uilds', 'C:
unner', 'D:uilds') | Where-Object { Test-Path $_ }
Get-ChildItem -Path $roots -Filter Cargo.lock -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$content = Get-Content $_.FullName -Raw -ErrorAction SilentlyContinue
if ($content -match 'name = "(arrayref)"
?
version = "0.3.10"' -or
$content -match 'name = "(internment)"
?
version = "0.8.7"' -or
$content -match 'name = "(append-only-vec)"
?
version = "0.1.9"') {
Write-Warning "[EXPOSED] $($_.FullName)"
$exposed = $true
}
}
# 2. Check cargo caches for poisoned crate artifacts
$cargoCache = "$env:USERPROFILE\.cargo\registry"
if (Test-Path $cargoCache) {
Get-ChildItem -Path $cargoCache -Recurse -ErrorAction SilentlyContinue |
Where-Object { $n = $_.Name; $poisoned | Where-Object { $n -like "$_*" } } |
ForEach-Object { Write-Warning "[EXPOSED] Cached artifact: $($_.FullName)"; $exposed = $true }
}
# 3. Recent build-script-build.exe binaries (exposure window)
Get-ChildItem -Path $roots -Filter 'build-script-build.exe' -Recurse -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-45) } |
ForEach-Object { Write-Output "[REVIEW] $($_.FullName) - verify provenance" }
if ($exposed) {
Write-Warning 'EXPOSURE CONFIRMED: isolate host, rotate all reachable credentials, preserve .cargo and target/ for forensics.'
} else {
Write-Output 'No poisoned crate versions detected on this host.'
}
Remediation
1. Determine exposure — everywhere Rust builds happen. Search every Cargo.lock in your repos, artifact registries, containers, and developer machines for arrayref 0.3.10, internment 0.8.7, or append-only-vec 0.1.9. Remember these are typically transitive dependencies — your developers may never have typed these crate names. Use cargo tree -i arrayref per project to identify which parent dependency pulled them in. Generate SBOMs (cargo cyclonedx, cargo sbom) for all release artifacts and scan them.
2. Purge the poisoned versions from resolution paths. The malicious releases have been deleted from crates.io, which causes builds resolving those exact versions to fail — that failure is your friend; do not work around it. Run cargo update to re-resolve to clean versions, commit the updated Cargo.lock, and verify with cargo tree that none of the three bad versions remain. If you run a private registry mirror or caching proxy (e.g., Artifactory, Nexus, a vendored mirror), evict the malicious versions from the cache — mirrors are a common way poisoned artifacts outlive registry takedowns.
3. Treat exposed build hosts as compromised. Any machine that compiled one of these versions executed attacker-controlled code. Standard IR applies: isolate, preserve forensics (lockfiles, ~/.cargo, target/, build logs, CI job logs), and rotate every credential the build user could reach — SSH private keys, cloud provider tokens, crates.io/PAT tokens, container registry credentials, code-signing keys, and any CI/CD secrets present in the runner environment. Do not limit rotation to the affected repo; build agents often carry credentials for many pipelines.
4. Audit artifacts built during the exposure window. Anything compiled while the malicious versions resolved may be backdoored. Identify the window from crates.io publish timestamps to your lockfile fix, list every artifact produced (binaries, container images, release tarballs), and rebuild them from a clean host with clean dependencies. Check CI logs for outbound connections from build steps to unfamiliar hosts.
5. Harden your dependency pipeline going forward:
- Pin and review: Require lockfile diffs in code review, and alert on new dependencies appearing in transitive trees — the typosquatted dependency in this attack would have shown up as a new name in the
Cargo.lockdiff. - Gate builds on
cargo auditandcargo deny: Fail CI on advisories and on crates not matching an allowlist policy. - Sandbox builds: Build in ephemeral, network-egress-restricted runners. Allow outbound traffic only to crates.io, your internal mirror, and approved hosts. A build script that cannot reach the internet cannot fetch a second stage. This single control would have blunted this exact attack.
- Monitor
build-script-buildexecution: Network egress or shell spawning from build scripts is anomalous — deploy the detections above as standing controls, not one-time hunts. - Protect maintainer identities internally: If your org publishes crates, enforce phishing-resistant MFA (hardware keys) on all registry accounts and audit publish tokens.
6. Stay current with official sources. Follow the Rust Project security announcements and the crates.io security policy page for incident postmortems and IOC publication; check whether your security vendors have added package-level indicators to their supply chain feeds.
Related Resources
Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.