The first I knew of it was my own pipeline going red. Not my code, not my tests… trivy, the security scanner, waving three HIGH advisories at me from deep in the dependency tree under rust-tool-base. And because a blocking gate that fails on every run is a gate nobody can merge through, the first thing I had to do was the thing I hate most: set my own security scanner to allow_failure. Tape over the smoke alarm, promise myself it’s temporary.
I’ve written before about the day the right answer to a scanner was “no, and here’s why”. This was the other day. The one where the scanner is simply right, and the only question is how much of your afternoon it’s about to eat.
The one-line fix that did nothing
Two of the three pointed at the gix family, the pure-Rust git implementation my rtb-vcs crate sits on: a submodule-name validation bypass with a path-traversal flavour in gix-validate 0.10, and another in gix-fs. The third was over in rmcp, my MCP layer. The gix-validate one was the proper eyebrow-raiser, and the theoretical fix is the boring kind you knock out before your coffee cools: bump the lockfile, rebuild, done.
Except cargo update -p gix-validate did precisely nothing. A Rust crate declares, in its own manifest, which versions of its dependencies it will accept, and gix 0.72 pins gix-validate to ^0.10… the patched version lives outside that range, so the lockfile physically cannot get there. The pin isn’t mine to change, it belongs to gix… and if you can’t move the child, you move the parent, whether you fancied that job or not.
So the quick bump became two real ones: rmcp from 0.16 to 1.7 to clear the rmcp CVE, and gix from 0.72 to 0.83 to drag the gix family off the vulnerable versions. Eleven minor versions of a library that touches everything my VCS layer does.
Eleven minors of churn, none of it mine
The rmcp jump crossed a major, and majors in Rust tend to arrive with opinions. This one had marked its public structs #[non_exhaustive], which is upstream’s way of saying “I may add fields later, so you don’t get to build this struct by hand any more”. Every struct literal in my MCP server became a constructor call. Net effect on my code: minus twenty-odd lines, and honestly the constructors read better.
The gix jump had a sharper edge. Somewhere in those eleven minors, gix moved its hash backends behind feature flags (compile-time switches for optional functionality), and gix-hash now refuses to compile at all unless you pick one:
# gix 0.83 moved hash backends behind feature flags — gix-hash
# emits `compile_error!` if neither sha1 nor sha256 is enabled.
"sha1",
I’ll give gix credit here: a build that fails loudly with an explanation beats a build that silently picks a default you didn’t know you’d chosen.
Then the pipeline fell over anyway, for the most mundane reason available: the bumped dependency tree fattened the build until the CI runner ran out of disk. The proper fix, a bigger runner, burns quota at a 3x multiplier, and I’m not paying real money to store debug symbols. So DWARF got stripped back to line-tables-only and the build fit again. Doing this on my own is mostly a long argument with myself about which problems are worth actual pounds. This one wasn’t.
Still red
Here’s the bit that made the whole afternoon worth writing up. After both bumps, with everything compiling and 88 test results green… trivy was still flagging the two gix advisories.
The trail led to gix-testtools, sitting in rtb-vcs’s dev-dependencies. I went looking for what would break without it, and the answer was nothing, because nothing had ever imported it. It had been declared speculatively, back when the crate was young (“we’ll want the test fixtures eventually”), and eventually never came. It contributed no code, no tests, no value. What it did contribute was a stale copy of the gix sub-crates: a fixture helper that lags the main release, dragging the old vulnerable gix-validate and gix-fs back into the tree, the very versions my bump was supposed to have retired, welded to every build for months.
Deleting one line from Cargo.toml took 411 lines out of the lockfile and both gix advisories with them. One line. Trivy went back to blocking the same day, and I peeled the tape back off the smoke alarm.
The dependency that did nothing
For the record: the scanner flagged it, I made the call, and the agent did the legwork, the survey, both merge requests, the CI triage. The bump-or-waive decision is the only part of this that needed me, and it needed about ten seconds: “best practice says handle the bumps, so let’s get them out of the way.”
But the thing I actually took from the afternoon isn’t about scanners or agents. Of everything in that dependency tree, the crate that hurt me was the one doing literally nothing. The imports I use earn their risk. The one I declared “for later” sat there for months as pure attack surface, no upside attached, waiting for a CVE to make it visible. It found one.
I’ve started reading my dev-dependencies the way I read my bank statements… looking for the subscription nobody remembers signing up for.





