Rust people love to act like they escaped the cursed parts of the ecosystem.
No postinstall junk. No random JavaScript tattoos on your build pipeline. Just clean, typed, respectable machinery and a stern little face that says this time, surely, the dependency won’t do anything weird.
Yeah. About that.
On June 10, the onering crate shipped version 1.4.1 with a build.rs that went looking for the consuming project’s git metadata and then exfiltrated the latest diff along with commit info to a Sentry ingest endpoint. Not the crate’s own code. Your code. The project that depended on it. The thing you thought Cargo was merely compiling.
That is not a bug. That is a supply-chain mugging with a nice font.
Cargo’s favorite lie
The lie is that build.rs is a benign little helper for binding generation and platform glue. Sometimes it is. Sometimes a crate really does need to compile some trashy C library or discover where the headers are hiding.
But “sometimes useful” and “safe by default” are not the same sentence, and Rust has spent years letting people talk as if they were.
build.rs runs at compile time. That means the dependency gets to execute code before your app even has the decency to exist. It can read files. It can inspect the environment. It can phone home. It can do all the stupid, useful, horrifying things a real program can do. The language being memory-safe does not matter if the package manager hands the crate a live socket and a map of your driveway.
onering just made the whole arrangement impossible to pretend away.
The part that should make you mad
The disgusting detail here is not just “malicious crate exists.” We’ve seen that movie. The special sauce is that the payload targeted the consumer’s repository state. It walked up from Cargo’s build directory, found the project root, and stole recent source changes.
So if you were thinking, “well, I didn’t call any suspicious API,” congratulations on discovering the package manager did it for you.
That’s the real insult. Dependency installs and builds are already overloaded with trust. We tell ourselves the lockfile pins everything down, as if pinning a burglar to the floor somehow stops him from opening your cabinets. A lockfile preserves version selection. It does not certify intent. It does not disinfect build scripts. It does not stop a crate from rummaging through the workspace like a petty accountant with a grudge.
Rust didn’t avoid npm’s bullshit
This is the part where the Rust crowd gets huffy, because the whole brand promise was supposed to be “we’re better than that.”
Better than what, exactly?
The ecosystem still has code that runs during dependency resolution. It still has proc macros. It still has build scripts. It still has tooling that is awkwardly opaque enough that people are now discussing whether Cargo should split fetching from building into separate phases, because the obvious defense is to disable network access during the build and only allow it while fetching. Which, honestly, is the sort of fix that should make everyone feel a little embarrassed.
That Rust Internals thread is the tell. Nobody starts talking about --fetch-only because things are going great. They start talking about it because the current shape of the toolchain makes “fetch dependencies” and “execute third-party code” feel like adjacent hobbies.
Build scripts are install hooks with a sobriety filter removed
I do not trust ecosystems that let untrusted code run before I have even asked for a function call.
Cargo people will tell you build.rs is for legitimate cases. Fine. npm people say the same thing about postinstall. Every ecosystem has its little ceremonial exception, its polite euphemism, its “well, normally this is okay” escape hatch.
And every one of those exceptions is eventually occupied by some goblin with a credential stash and a deadline.
That’s why the onering mess is so irritating. The exploit wasn’t mystical. It wasn’t a zero-day. It was a dependency getting to act like a full citizen of your build machine when all you wanted was a library. The security story was already fragile; the crate just leaned on the wall and made the crack visible.
The fix is boring, and that’s why people will ignore it
The answer is not “Cargo bad, Rust bad, go home and hand-roll everything in assembly.” Spare me.
The answer is boring as hell:
- network off during build
- fetch and build separated
- audit
build.rslike it matters, because it does - stop assuming “safe language” means “safe dependency execution model”
- treat build-time code as hostile until proven annoying
None of that is glamorous. None of it gets a conference talk with dramatic lighting. It is just the ugly plumbing necessary to keep a package manager from becoming a confessional booth for your source tree.
The actual lesson
onering was yanked after roughly six hours and, as far as the public record says, there’s no evidence the compromised version saw real use. Great. Lovely. We got lucky. Again.
But luck is not a strategy. The next crate may not bother with Sentry cosplay. The next one may not touch git metadata. The next one may steal credentials, poison tests, or wait patiently until your CI job runs with the good secrets mounted.
The lesson is not that Rust failed.
The lesson is that Cargo has always had a hole in its back pocket, and build.rs has been sitting there with a knife for years.
The only surprising part is how long it took for the knife to point at the consumer instead of the crate author.