Status: DRAFT for review. Built only from already-public CVEs/GHSAs and my
complete-fix analysis of them — no embargoed or in-flight finding details.
Companion to The Fix Is the Map .
A prediction you can make before reading the code
In the first post I argued the patch is a treasure map: the diff tells you where, the advisory tells you why, the test tells you what input. This post is the empirical follow-up. After auditing a long run of recent fixes back to back, one thing got hard to ignore:
You can predict whether a fix is incomplete from its shape, before you read the surrounding code.
Not from the CVE severity. Not from the project's size. From whether the patch band-aids the symptom or removes the capability.
The two shapes
Band-aid — the patch treats the specific report and stops:
- widens a regex / boundary by a character
- adds one entry to a denylist
- flips a default flag without restructuring
- drops a guard at the one call site named in the report
Re-architecture — the patch makes the bug class unrepresentable:
- deletes the vulnerable code path entirely
- routes every caller through one central validator
- moves the check to a single chokepoint
- adds a regression/enforcement test that pins the invariant repo-wide
- replaces string/regex logic with structural logic (a real parser, a nonce
delimiter, the actual connected IP instead of the URL string)
Band-aids leave residuals. Re-architectures hold. The sample below is lopsided on purpose — every one of these recent fixes turned out complete, and each is complete for the same reason: it changed shape.
The data (all public, all complete — and why)
- authlib (CVE-2026-27962, JWS
jwk-header signature bypass): the fix
deleted the key is None → header['jwk'] fallback outright. You can't bypass a code path that no longer exists. Deletion forces completeness.
- pyLoad (CVE-2026-35459, SSRF via redirect): replaced a string-URL check
with a libcurl PREREQFUNCTION that validates the connected IP on the initial request and every redirect hop. That kills redirect-bypass and DNS rebinding in one move — structural, not stringly.
- AzuraCast (Liquidsoap injection): deleted the leaky
cleanUpString
sanitizer and moved every user value to nonce-delimited raw strings. The attacker can't pre-craft a terminator they can't predict.
- changedetection.io (CVE-2026-35490, auth bypass via decorator ordering):
reordered the routes and shipped a repo-wide test that fails if any future route stacks the decorators wrong. The invariant is now enforced, not hoped.
- mantisbt (CVE-2026-30849, SOAP auth bypass via MySQL type juggling): a
one-line ?string coercion — but at mci_check_login, the shared chokepoint both the SOAP and REST APIs funnel through. One line, total coverage.
- MCP-Server-Kubernetes (CVE-2026-46519): tool restrictions were enforced at
discovery but not execution; the fix moved enforcement to the single tools/call chokepoint every invocation passes.
- Pimcore (CVE-2026-45704, report share-bypass): added the access check to
every data-returning endpoint a restricted user can reach — not just the one in the report.
Seven fixes, seven different products, one pattern: they all changed the shape so the class can't recur. When I went hunting for the residual, there wasn't one. That's not a failure — a disciplined "this fix is complete, stop" is an output. It keeps you (and the next person) from re-walking a closed door.
When the shape doesn't change — that's the opening
The flip side is where the bounties are. The fixes that yielded findings this cycle were all band-aid-shaped: one flipped a default flag and reopened a path-validation regression; one closed one case of an auth bypass and left a sibling; one widened a regex boundary by a single character and still missed an adjacent one. (Those are in coordinated disclosure, so no specifics here — but the shape is the whole story.)
The tell is almost always: the patch references the exact thing in the report, and nothing structural moved.
Scoring it
Because the signal lives in the diff shape, you can rank it. I built a small ranker (lead_puller) that pulls the advisory firehose, fetches each fix's diff, and scores band-aid-ness:
- + regex/boundary tweak, denylist add, default-flag flip, tiny single-file
diff, "incomplete fix of CVE-X" in the text
- − deletes a function, adds a central validator, adds a regression test,
large/multi-file refactor, pure-deletion diff
High score floats to the top; that's where you dive first. Two honest calibration notes from running it:
- It scored a proper recursive sanitizer as a band-aid because the diff
touched regex-looking code. It reads shape, not soundness.
- A "boundary-only" type fix looked incomplete but was actually a **shared
chokepoint** covering every entry point.
So the ranker triages; it doesn't conclude. The manual diff-read is still the job. It's a metal detector, not a shovel.
The uncomfortable base rate
Most recent fixes from well-maintained projects are complete. The wins cluster on structurally weak patches — regressions, reactive whack-a-mole, leaky regex — not on teams with mature AppSec. The hardest targets give up the least: a project with a dedicated security team and confidential fix MRs hides the map itself, so you're reverse-engineering the patch from a release diff before you can even start. Plan for a low hit-rate, and bank the kills.
Steal this
- Pull the fix. Before reading the code, classify the shape: band-aid or
re-architecture?
- Re-architecture (deletion / chokepoint / central validator / enforcement
test / structural replacement) → likely complete. Spend five minutes confirming, then move on.
- Band-aid (regex tweak / denylist / flag flip / one-site guard) → this is your
dive. Ask: what sibling, what adjacency, what other entry point did the symptom-patch miss?
- Rank a batch by shape so you dive the band-aids first.
- Write down the complete ones too. A kill you don't record is a door you'll
open again.
The newest CVE is crowded. The shape of last month's fix is not.
No comments yet