Published · yesterdayScience9 min read
The scanner missed it, the build ran it anyway: compile-time payloads and an unmeasured gate
Three Rust crates carried one added manifest line that executed a remote payload during compilation. Nothing had to call it, and nothing in the review path measures how often that class gets missed.
Written for builders.See today for builders

What happened
- The Rust Project deleted malicious versions of three widely used crates from crates.io after a compromised maintainer account published releases that added a typosquatted dependency whose build script downloaded and executed a remote payload during compilation.
- The affected releases are arrayref 0.3.10, internment 0.8.7 and append-only-vec 0.1.9, all published from the same owner account on August 20, 2026, and all removed within 86 to 107 minutes.
- Because the malicious code sat in the build script of the injected dependency, building a project that resolved it was sufficient to run the payload, and nothing from the crates themselves had to be called.
- Placing a file named build.rs in the root of a package causes Cargo to compile that script and execute it just before building the package; documented use cases include building a bundled C library and generating a Rust module from a specification.
- Each compromised release carried a single added line in its manifest, a dependency on proc-macro1, a typosquat of the ubiquitous proc-macro2 crate; the library source of proc-macro1 is a genuine copy of proc-macro2, so builds completed normally.
Compiled by The ScientistSomething wrong?How this is made
Why it matters
Nothing had to call it
The three deleted Rust releases share the property that makes most of a review pipeline irrelevant to them. The malicious code sat in the build script of an injected dependency, so building a project that resolved that dependency was sufficient to run the payload, and nothing from the crates themselves had to be invoked [3]. This is not a subverted feature. Cargo compiles a `build.rs` placed in a package root and executes it just before the package is built, which is documented behaviour intended for compiling bundled C libraries and generating code [4].
The delta a reviewer would have had to catch was one line. Each compromised release added a single dependency on `proc-macro1`, a typosquat of `proc-macro2`, and the library source of `proc-macro1` was a genuine copy of `proc-macro2`, so builds completed normally [5]. Everything hostile lived in the script that Cargo runs on your behalf: it reassembled its payload host and command-and-control address from base64 fragments at build time, installed a certificate verifier whose three verification methods returned success unconditionally, and picked one of four payloads by operating system and CPU architecture [6]. On Unix and macOS it wrote to `/tmp/rust-setup`, marked it executable and spawned it detached with the C2 address as the first argument; on Windows it wrote a PowerShell script to `%TEMP%` and launched it hidden through a VBScript launcher under `wscript.exe`, then abandoned the child process, a step the source comments describe as escaping Cargo's job object so the build does not wait on it [7]. That comment is the tell that matters operationally. Someone had run this under Cargo and tuned it so the build would not hang, which means the observable outcome on a runner is a green build.
Delivery leaned on the tooling's own advice. The owner account yanked arrayref 0.3.5 through 0.3.9 within the same minute as the malicious publish, leaving the compromised release as the only version Cargo would not warn about [8]. The researcher who reported it put it plainly: cargo's suggestion to consider updating to a version that is not yanked "is the lure. That is how I hit it" [9].
The same feature, three package managers
Trend Micro's enterprise business reported a set of trojanized npm packages that reach the same execution point by a different door. When the module loads it locates a bundled binary, marks it executable and launches it as a detached background process, and according to the researchers no install hook is needed: a single import anywhere in the dependency graph, even a transitive one, is enough [10]. The packages deliver the calendar and streak utilities they advertise, with the implant framed as a native math accelerator under filenames that vary between packages [11]. So the two habitual defences here, blocking install scripts and checking that a package does what it claims, both pass.
What the resulting beacon collects is the part worth reading next to a CI runner's contents. Trend Micro describes a Linux implant offering an interactive shell through `/bin/sh` plus system discovery, data collection including SSH keys and browser credentials, persistence, in-memory ELF execution, SOCKS5 proxying and network pivoting [12]. The framework is advertised on a clearnet site for $99.99 and was marketed on a hacking forum in early June 2026 as built for evasion [13]. Python has the same shape by design and says so in its own documentation: setup.py scripts allow arbitrary execution, and pip falls back to building from source, consulting `pyproject.toml` to decide which program to run, when no wheel is available [14].
The common feature across all three is that the interception point is not the call graph. Static analysis, by definition, examines software for weaknesses without executing it [15]. The code under review is precisely the code that executes without being called.
What a passing scan does not tell you
NIST's Software Assurance Metrics and Tool Evaluation team studied thousands of warnings from static analyzers and found that tools have difficulty distinguishing between the absence of a weakness and the presence of a weakness buried in otherwise-irrelevant code elements, a category the team named code complexities [16]. Hold that next to `proc-macro1`, whose library source was a legitimate copy of a ubiquitous crate with the hostile logic elsewhere in the package [5]. That is the described geometry, not an exotic variant of it.
The more useful finding is about measurement. NIST's tool expositions used three selection criteria for test cases: real existing code, enough test data for statistical significance, and known weakness locations, meaning ground truth. In SATE V, each of the three test case types expressed only two of the three, and producing cases that satisfy all three was stated as the main goal for future expositions [17]. Recall is among the metrics discussed for evaluating tool effectiveness [18]. Recall is the inverse of the false-negative rate, and it can only be computed where ground truth exists. In SATE V, ground truth belonged to the CVE-related and synthetic cases, while the production cases, the ones made of real code at scale, were the ones without it [19]. A vendor number for how much a scanner catches therefore does not come from the case type that resembles what your merge gate actually reads. NIST is candid about this; the marketing built on top of it is not.
The runner is the asset, not the artifact
GitHub's Security Lab is explicit about what the machine evaluating an untrusted diff is holding. Workflows triggered by `pull_request_target` have write permission to the target repository and access to its secrets, which workflows triggered by `pull_request` from external forks do not [20]. In the same guidance, the Lab lists how an attacker gets arbitrary code execution in a runner processing a malicious pull request: modify existing build scripts such as make or PowerShell files, redefine the build script in package.json, write the payload as a new test, or add a package reference so that npm install triggers a preinstall or postinstall hook, with the observation that any modern build orchestration is complex enough to have multiple code injection points [21]. The Lab's own advice is that untrusted pull requests should never be checked out and built without careful examination of the code first [22].
Line that up with the implant capability list. A compile-time payload lands in the process that holds repository secrets [20] and on a filesystem where SSH keys and browser credentials are exactly what the beacon's Linux command set enumerates [12]. Model weights, datasets and whatever else sits downstream are not the near-term exposure. The credentials the build holds are.
The record that says it was reviewed
The second half of the assurance story is the merge record, and on unpatched self-hosted GitLab that is writable. CVE-2026-19478, scored 9.4, is a code injection issue exploitable through a GraphQL directive that lets an unauthenticated attacker modify or delete publicly accessible projects and rewrite their data with no credentials, no user interaction and no obscure configuration, fixed in 19.2.4, 19.1.6, 19.0.8 and 18.11.11 [23]. watchTowr told The Hacker News it reproduced the flaw within minutes of disclosure and observed in-the-wild exploitation against its honeypot network [24]. The firm also said the impact goes past deletion: an attacker can delete entire repositories, forge merge records to make it appear as if a fix landed when it didn't, and ban project maintainers [25]. Its interim guidance is to hunt web logs for requests containing `@gl_introduced` and, absent patching, restrict unauthenticated access to `/api/graphql` or remove public repository access [26].
So both controls a team would point at after an incident have soft foundations. The scanner's miss rate on production code is unmeasured, per NIST's own account of its test corpora [19], and the audit trail proving a fix landed can be fabricated by an unauthenticated party, according to watchTowr [25].
The arithmetic of eighty-six minutes
Here is the number nobody in the reporting computed. The Hacker News checked the crates.io API on 21 August and found arrayref at 245,385,500 downloads all time, 53,905,601 in the 90 days ending 20 August, and 403 distinct crates depending on it [27]. That 90-day figure averages about 599,000 downloads a day, roughly 416 a minute, so the 86 minutes arrayref 0.3.10 was online corresponds to about 35,800 fetches of that crate path at the ordinary rate [28]. Across all three releases the combined exposure was 283 minutes, a little under five hours [33].
Whether any of those fetches resolved the malicious version is the question the record does not answer. RustSec's advisories for all three crates record no evidence that any malicious version was used, and there is no patched version and no CVE [29]. The Hacker News asked the Rust Security Response Team for the basis of that finding and for the download counts of the deleted versions, and had not received a response at the time of writing [30]. Until those counts appear, "no evidence of actual usage" and "roughly 36,000 fetches of that path in the window" are not in conflict; they are the same gap seen from two sides.
The timing sharpens it. The Response Team received the report that `proc-macro1` was malicious at 07:15 UTC on 20 August, crediting the research team at Nextron Systems with the discovery [31]. arrayref 0.3.10 was published at 07:15:00Z and deleted at 08:41:40Z, with internment and append-only-vec following on similar arcs [32]. The report landed in the same minute the delivery vehicle went live, so essentially the whole 86-minute window ran with the registry already in possession of it [34].
Publisher reputation would have scored this at the top. The sole listed owner of arrayref is an account registered in October 2009, and the Response Team said it does not believe the author acted maliciously but that his computer or credentials are likely compromised, with no disclosure of how [35]. The remediation on offer is manual: search `~/.cargo/registry/cache` for the deleted files and pin arrayref at 0.3.9 or earlier, versions the team had to unyank during the response because the attacker's yanking was itself part of the delivery [36].
Nothing in this record shows a gate catching any of it. A research team reported the typosquat [31], a developer hit the yank lure and filed it [9], and a vendor research team found the npm set [10]. The controls that failed are the ones with no measured false-negative rate, and the control that would have mattered is upstream of all of them: a build step that reaches the network during compilation is a policy violation before it is a detection problem.
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The Rust Project deleted malicious versions of three widely used crates from crates.io after a compromised maintainer account published releases that added a typosquatted dependency whose build script downloaded and executed a remote payload during compilation.
ReportedView cited source - [2]
The affected releases are arrayref 0.3.10, internment 0.8.7 and append-only-vec 0.1.9, all published from the same owner account on August 20, 2026, and all removed within 86 to 107 minutes.
ReportedView cited source - [3]
Because the malicious code sat in the build script of the injected dependency, building a project that resolved it was sufficient to run the payload, and nothing from the crates themselves had to be called.
ReportedView cited source - [4]
Placing a file named build.rs in the root of a package causes Cargo to compile that script and execute it just before building the package; documented use cases include building a bundled C library and generating a Rust module from a specification.
ReportedView cited source - [5]
Each compromised release carried a single added line in its manifest, a dependency on proc-macro1, a typosquat of the ubiquitous proc-macro2 crate; the library source of proc-macro1 is a genuine copy of proc-macro2, so builds completed normally.
ReportedView cited source - [6]
The build script reassembles its payload host and C2 address from base64 fragments at build time, installs a custom certificate verifier whose three verification methods return success unconditionally, disabling TLS validation, and selects one of four payloads by operating system and CPU architecture.
ReportedView cited source
Sources & coverage · 6 publishers
The reporting this story was synthesized from, earliest first. Every link goes to the original.
- thehackernews.com2d agoCisco Patches Nine Crosswork and Secure Workload Flaws, Five Scoring CVSS 10.0
- thehackernews.com2d agoIdentity Governance Wasn't Built for Breaches That Happen in Hours
- thehackernews.com2d agoZombie Card Attack Can Revive Expired Visa Cards for Contactless Payments


