Beyond the Fix
A patch is the highest-signal artifact in any repository. Someone already did
the expensive part: they found the class, located the file, and wrote down what
they believed was wrong. What they did not do — because nobody does — is keep
looking after the tests went green.
The thesis, in one line:
That someone fixed it means they fixed that, and nothing else.
A fix is evidence about the past: something was wrong here, and a human
confirmed it. It is not evidence about the present. Those are different claims,
and the entire discipline of this skill lives in the gap between them.
Three biases produce that gap, and all three are structural rather than
personal:
- Attention collapses at green. The search terminates the moment the
reporter's PoC stops reproducing. That oracle — my one input no longer
works — is the weakest oracle in existence: it tests an input, not the
property (
falsifiable-testing).
- The frame came from the reporter. The fixer inherited the demonstrated
path and fixed the demonstrated path. Whatever the reporter did not think of,
the fix does not cover.
- A fixed area repels review. The file now carries a mark that says
already looked at, usually by the person who understood it best. It becomes
the safest-looking code in the repo, and it is the code most likely to hold
the next bug. Security fixes ship under time pressure and get less review
than ordinary features, not more.
This is not a hypothesis about how people work. Project Zero's tracking of
exploited-in-the-wild 0-days has repeatedly found that a large share of them
each year are variants of previously patched bugs. The exact fraction moves;
the pattern does not.
Composes with the library:
- beyond-the-sink — the sibling; its Part 3 (a root class is a template)
is this skill's engine, applied systematically to fix history
- invariant-hunting — name the property the fix was attempting to
establish, then ask whether it holds
- discriminating-proof — the pre-fix / post-fix control in Part 4
- software-archaeology — reconstructing intent from a diff whose author is
unavailable
- audit-before-patch — the fix you are auditing must be confirmed present
in the live code before anything is built on it
- remediation-driven-reporting — the report that gets the class fixed this
time, referencing their own prior fix
- falsifiable-testing — the regression test that stops the fix from decaying
Scope: your own repositories, authorized targets, and public patches of open
source. Reading a public diff is research; probing someone's production because
the diff suggested something is not, and needs the same authorization as any
other test.
Part 1 — Reconstruct the bug from the diff, not from the advisory
The advisory is the vendor's framing, written to be reassuring and to reveal as
little as is polite. The diff is the fact. Start there, always, and derive what
must have been possible before the change.
Then extract the thing that makes this skill work:
The fix encodes the author's mental model of the bug. The gap between that
model and the real system is where the next bug is.
Read the fix for its model, and the model predicts what else was missed:
| The fix looks like |
Their model was |
So they probably did not consider |
blocking ../ and %2e%2e |
"the attack is a string pattern" |
resolution, symlinks, Unicode, the second decode |
| a check added in one handler |
"the bug is in this handler" |
every other caller of the same primitive |
| a length check before a copy |
"the bug is this buffer" |
the other three copies from the same source |
try/except around the crash |
"the bug is the exception" |
the state the failed operation left behind |
| escaping at output |
"the bug is display" |
the stored value, and every other consumer of it |
| a new role check in the UI route |
"the bug is who can click" |
the API, the batch job, the CLI, the internal caller |
This is the abductive step (abductive-engineering): the patch is a sign, its
object is the author's belief, and the interpretant you want is the region their
belief did not cover.
Part 2 — The eight ways a fix falls short
Name the family before searching. Each has a distinct search.
- It covers the input, not the property. They rejected the reporter's exact
payload, encoding, or id. Search: the next member of the class — another
encoding layer, a different normalization, an equivalent value.
- It covers one path, not every path. Sync patched, async not. The v2
handler patched, v1 still routed. The interactive path patched; the batch,
import, retry, streaming, replay, admin, and CLI paths untouched. Search:
every path that reaches the same sink.
- It is at the wrong layer. Patched where the bug was observed rather
than where the property should be enforced — so every other caller of the
same primitive still has it. This is the highest-yield family, because the
fix is genuinely correct and genuinely local.
- It suppresses the symptom. A
catch, a retry, a clamp, a default. The
operation no longer crashes; it now fails silently, and the corrupt state it
leaves behind is a new problem (honest-degradation). Search: what does the
system do after the suppressed failure?
- It is correct but unenforced. The right check exists, and nothing stops
the next contributor from adding a caller that skips it. No test, no type, no
chokepoint. Search: how many callers exist, and what makes a new one safe?
- It introduced a new bug. The diff was written fast, under pressure, by
someone tired, and reviewed deferentially because they were the expert.
Off-by-one in the new bound, a null path that did not exist before, a
TOCTOU between the new check and the old use, a lock now held across I/O.
Read the fix diff adversarially as new code, because that is what it is.
- Siblings were left unpatched. Other implementations, other languages,
other services, forks, vendored copies, the same function copy-pasted twice.
Coordinated fixes almost never reach every implementation
(
beyond-the-sink Part 3's existence and behavior gates).
- It decayed. The fix landed and no longer exists: a refactor moved the
check to the wrong side of a branch, a performance patch added a fast path
that skips it, a merge dropped it, a rewrite reimplemented the function
without it. Search: does the check still exist at HEAD, and is it still on
every path it used to cover?
Family 8 deserves a habit of its own: for every historical fix you care
about, verify it is still present today. Fixes with no regression test are the
ones that decay, and a decayed fix is a live bug with a closed ticket in front
of it.
Part 3 — Where fixes announce themselves
The fix history is a map of where the authors already bled. Mine it:
- Commit messages:
fix, security, CVE, bypass, sanitize, escape,
validate, harden, overflow, injection, regression, hotfix.
- Advisories, CVE records and their patch commits; changelog and release notes;
issues closed as fixed; your own past reports.
- Suspiciously terse messages on sensitive files — "minor cleanup" on a
parser, "refactor" on an auth path. A silent security fix looks exactly like
this, and it is the highest-value commit in the log.
- Clusters. Three fixes in one file over two years is not three incidents,
it is one structural problem that has been patched three times. Fix density
is the single best predictor of the next bug's location.
- Velocity tells. A fix landed within hours of a report, in a file its
author does not usually own, is a rushed fix by definition.
And the rule inherited from beyond-the-sink: dig adjacent to old fixes, not
on top of them. The exact bug is the one place that is genuinely well tested
now. The neighbors are not.
Part 4 — The pre-fix control makes the verdict unambiguous
When you have a candidate, run it against three versions, not one. This is
the discriminating experiment for this whole skill:
| Candidate behaves as |
Pre-fix build |
Post-fix build |
Verdict |
| the original bug |
works |
works |
the fix never covered this path — family 1, 2, 3 or 7 |
| the original bug |
works |
blocked |
the fix holds here; record it as terrain |
| a new failure |
clean |
works |
the fix introduced it — family 6 |
| nothing |
clean |
clean |
refuted; log it (forensic-persistence) |
That table is why the pre-fix build is worth the trouble of checking out: it
separates never fixed from newly introduced, which are different findings,
with different severities, reported to different people, with different fixes.
Without it you have an observation and a guess about its origin.
Then apply the epistemic ladder as usual — a surviving path found by reading is
PLAUSIBLE until it is run (discriminating-proof).
Part 5 — The report writes itself, and the blue fix is a chokepoint
An incomplete-fix finding is often stronger than a novel bug report, and it
is certainly easier to land:
- The vendor already agreed it is a security issue. They fixed it once.
The entire "is this really a vulnerability" argument is pre-settled — cite
their own commit or advisory.
- Name the family from Part 2 and show the specific surviving path. "The
fix is incomplete" without a demonstrated survivor is an opinion.
- Propose the chokepoint, not another point fix — otherwise you are filing
the same report again in six months, which is precisely the loop this skill
exists to break (
remediation-driven-reporting Part 3).
The defensive deliverable, for your own repos:
- A regression test per fix, asserting the property, not the reporter's
input. A fix with no test is a fix with an expiry date.
- A decay check: a test that fails if the check is removed or bypassed —
the only thing that catches family 8 before an attacker does.
- A post-fix review pass as policy. The security fix diff gets more
scrutiny than a feature diff, not less. Right now the opposite is true
everywhere.
- A fix-cluster review. When one file collects its third fix, stop patching
it and redesign the boundary.
Deliverable
## Fix audit — <component> — fixes reviewed: <n>
| fix / commit / CVE | invariant it attempted | families checked | outcome |
### Findings
<id> — family (1–8) — the surviving path — pre-fix/post-fix control result
— epistemic level — the prior fix it references
### Fixes verified complete
<recorded as terrain: what was traced, which paths, what enforces it>
### Decayed fixes
<fix, when it landed, what removed or bypassed it, whether it is live now>
### Structural proposal
<chokepoint + regression tests + decay checks>
### Not reviewed
Anti-patterns
- Reading the advisory instead of the diff, and inheriting the vendor's framing
of what the bug was.
- Re-running the original PoC against the patched build and concluding the fix
is complete — that is the fixer's own weak oracle, borrowed.
- Assuming the fix's location is the correct location for the property.
- Never checking whether a historical fix still exists at HEAD.
- Treating the fix diff as reviewed code. It is the least-reviewed code in the
repository.
- Skipping the pre-fix control, and so being unable to distinguish "never
covered" from "introduced by the fix".
- Digging on top of the exact bug that was fixed — the one spot now covered by
a test — instead of adjacent to it.
- Reporting "the fix is incomplete" without a demonstrated surviving path.
- Accepting a second point fix for the same file, and scheduling your own next
report.
- Recording a verified-complete fix nowhere, so the next auditor re-does the
work you already did.
1---2name: beyond-the-fix3description: Beyond the Fix4---56# Beyond the Fix78A patch is the highest-signal artifact in any repository. Someone already did9the expensive part: they found the class, located the file, and wrote down what10they believed was wrong. What they did not do — because nobody does — is keep11looking after the tests went green.1213The thesis, in one line:1415> **That someone fixed it means they fixed that, and nothing else.**1617A fix is evidence about the *past*: something was wrong here, and a human18confirmed it. It is not evidence about the present. Those are different claims,19and the entire discipline of this skill lives in the gap between them.2021Three biases produce that gap, and all three are structural rather than22personal:23241. **Attention collapses at green.** The search terminates the moment the25 reporter's PoC stops reproducing. That oracle — *my one input no longer26 works* — is the weakest oracle in existence: it tests an input, not the27 property (`falsifiable-testing`).282. **The frame came from the reporter.** The fixer inherited the demonstrated29 path and fixed the demonstrated path. Whatever the reporter did not think of,30 the fix does not cover.313. **A fixed area repels review.** The file now carries a mark that says32 *already looked at*, usually by the person who understood it best. It becomes33 the safest-looking code in the repo, and it is the code most likely to hold34 the next bug. Security fixes ship under time pressure and get *less* review35 than ordinary features, not more.3637This is not a hypothesis about how people work. Project Zero's tracking of38exploited-in-the-wild 0-days has repeatedly found that a large share of them39each year are variants of previously patched bugs. The exact fraction moves;40the pattern does not.4142Composes with the library:4344- **beyond-the-sink** — the sibling; its Part 3 (a root class is a template)45 is this skill's engine, applied systematically to fix history46- **invariant-hunting** — name the property the fix was *attempting* to47 establish, then ask whether it holds48- **discriminating-proof** — the pre-fix / post-fix control in Part 449- **software-archaeology** — reconstructing intent from a diff whose author is50 unavailable51- **audit-before-patch** — the fix you are auditing must be confirmed present52 in the live code before anything is built on it53- **remediation-driven-reporting** — the report that gets the class fixed this54 time, referencing their own prior fix55- **falsifiable-testing** — the regression test that stops the fix from decaying5657Scope: your own repositories, authorized targets, and public patches of open58source. Reading a public diff is research; probing someone's production because59the diff suggested something is not, and needs the same authorization as any60other test.6162---6364## Part 1 — Reconstruct the bug from the diff, not from the advisory6566The advisory is the vendor's framing, written to be reassuring and to reveal as67little as is polite. The diff is the fact. Start there, always, and derive what68must have been possible *before* the change.6970Then extract the thing that makes this skill work:7172> **The fix encodes the author's mental model of the bug. The gap between that73> model and the real system is where the next bug is.**7475Read the fix for its model, and the model predicts what else was missed:7677| The fix looks like | Their model was | So they probably did not consider |78|---|---|---|79| blocking `../` and `%2e%2e` | "the attack is a string pattern" | resolution, symlinks, Unicode, the second decode |80| a check added in one handler | "the bug is in this handler" | every other caller of the same primitive |81| a length check before a copy | "the bug is this buffer" | the other three copies from the same source |82| `try/except` around the crash | "the bug is the exception" | the state the failed operation left behind |83| escaping at output | "the bug is display" | the stored value, and every other consumer of it |84| a new role check in the UI route | "the bug is who can click" | the API, the batch job, the CLI, the internal caller |8586This is the abductive step (`abductive-engineering`): the patch is a *sign*, its87object is the author's belief, and the interpretant you want is the region their88belief did not cover.8990## Part 2 — The eight ways a fix falls short9192Name the family before searching. Each has a distinct search.93941. **It covers the input, not the property.** They rejected the reporter's exact95 payload, encoding, or id. Search: the next member of the class — another96 encoding layer, a different normalization, an equivalent value.972. **It covers one path, not every path.** Sync patched, async not. The v298 handler patched, v1 still routed. The interactive path patched; the batch,99 import, retry, streaming, replay, admin, and CLI paths untouched. Search:100 every path that reaches the same sink.1013. **It is at the wrong layer.** Patched where the bug was *observed* rather102 than where the property should be *enforced* — so every other caller of the103 same primitive still has it. This is the highest-yield family, because the104 fix is genuinely correct and genuinely local.1054. **It suppresses the symptom.** A `catch`, a retry, a clamp, a default. The106 operation no longer crashes; it now fails silently, and the corrupt state it107 leaves behind is a new problem (`honest-degradation`). Search: what does the108 system do *after* the suppressed failure?1095. **It is correct but unenforced.** The right check exists, and nothing stops110 the next contributor from adding a caller that skips it. No test, no type, no111 chokepoint. Search: how many callers exist, and what makes a new one safe?1126. **It introduced a new bug.** The diff was written fast, under pressure, by113 someone tired, and reviewed deferentially because they were the expert.114 Off-by-one in the new bound, a null path that did not exist before, a115 TOCTOU between the new check and the old use, a lock now held across I/O.116 **Read the fix diff adversarially as new code, because that is what it is.**1177. **Siblings were left unpatched.** Other implementations, other languages,118 other services, forks, vendored copies, the same function copy-pasted twice.119 Coordinated fixes almost never reach every implementation120 (`beyond-the-sink` Part 3's existence and behavior gates).1218. **It decayed.** The fix landed and no longer exists: a refactor moved the122 check to the wrong side of a branch, a performance patch added a fast path123 that skips it, a merge dropped it, a rewrite reimplemented the function124 without it. Search: does the check still exist at HEAD, and is it still on125 every path it used to cover?126127Family 8 deserves a habit of its own: **for every historical fix you care128about, verify it is still present today.** Fixes with no regression test are the129ones that decay, and a decayed fix is a live bug with a closed ticket in front130of it.131132## Part 3 — Where fixes announce themselves133134The fix history is a map of where the authors already bled. Mine it:135136- Commit messages: `fix`, `security`, `CVE`, `bypass`, `sanitize`, `escape`,137 `validate`, `harden`, `overflow`, `injection`, `regression`, `hotfix`.138- Advisories, CVE records and their patch commits; changelog and release notes;139 issues closed as fixed; your own past reports.140- **Suspiciously terse messages on sensitive files** — "minor cleanup" on a141 parser, "refactor" on an auth path. A silent security fix looks exactly like142 this, and it is the highest-value commit in the log.143- **Clusters.** Three fixes in one file over two years is not three incidents,144 it is one structural problem that has been patched three times. Fix density145 is the single best predictor of the next bug's location.146- **Velocity tells.** A fix landed within hours of a report, in a file its147 author does not usually own, is a rushed fix by definition.148149And the rule inherited from `beyond-the-sink`: **dig adjacent to old fixes, not150on top of them.** The exact bug is the one place that is genuinely well tested151now. The neighbors are not.152153## Part 4 — The pre-fix control makes the verdict unambiguous154155When you have a candidate, run it against **three** versions, not one. This is156the discriminating experiment for this whole skill:157158| Candidate behaves as | Pre-fix build | Post-fix build | Verdict |159|---|---|---|---|160| the original bug | works | works | **the fix never covered this path** — family 1, 2, 3 or 7 |161| the original bug | works | blocked | the fix holds here; record it as terrain |162| a new failure | clean | works | **the fix introduced it** — family 6 |163| nothing | clean | clean | refuted; log it (`forensic-persistence`) |164165That table is why the pre-fix build is worth the trouble of checking out: it166separates *never fixed* from *newly introduced*, which are different findings,167with different severities, reported to different people, with different fixes.168Without it you have an observation and a guess about its origin.169170Then apply the epistemic ladder as usual — a surviving path found by reading is171`PLAUSIBLE` until it is run (`discriminating-proof`).172173## Part 5 — The report writes itself, and the blue fix is a chokepoint174175An incomplete-fix finding is often *stronger* than a novel bug report, and it176is certainly easier to land:177178- **The vendor already agreed it is a security issue.** They fixed it once.179 The entire "is this really a vulnerability" argument is pre-settled — cite180 their own commit or advisory.181- **Name the family** from Part 2 and show the specific surviving path. "The182 fix is incomplete" without a demonstrated survivor is an opinion.183- **Propose the chokepoint**, not another point fix — otherwise you are filing184 the same report again in six months, which is precisely the loop this skill185 exists to break (`remediation-driven-reporting` Part 3).186187The defensive deliverable, for your own repos:1881891. **A regression test per fix**, asserting the *property*, not the reporter's190 input. A fix with no test is a fix with an expiry date.1912. **A decay check**: a test that fails if the check is removed or bypassed —192 the only thing that catches family 8 before an attacker does.1933. **A post-fix review pass as policy.** The security fix diff gets *more*194 scrutiny than a feature diff, not less. Right now the opposite is true195 everywhere.1964. **A fix-cluster review.** When one file collects its third fix, stop patching197 it and redesign the boundary.198199---200201## Deliverable202203```204## Fix audit — <component> — fixes reviewed: <n>205| fix / commit / CVE | invariant it attempted | families checked | outcome |206207### Findings208<id> — family (1–8) — the surviving path — pre-fix/post-fix control result209 — epistemic level — the prior fix it references210211### Fixes verified complete212<recorded as terrain: what was traced, which paths, what enforces it>213214### Decayed fixes215<fix, when it landed, what removed or bypassed it, whether it is live now>216217### Structural proposal218<chokepoint + regression tests + decay checks>219220### Not reviewed221```222223## Anti-patterns224225- Reading the advisory instead of the diff, and inheriting the vendor's framing226 of what the bug was.227- Re-running the original PoC against the patched build and concluding the fix228 is complete — that is the fixer's own weak oracle, borrowed.229- Assuming the fix's location is the correct location for the property.230- Never checking whether a historical fix still exists at HEAD.231- Treating the fix diff as reviewed code. It is the least-reviewed code in the232 repository.233- Skipping the pre-fix control, and so being unable to distinguish "never234 covered" from "introduced by the fix".235- Digging on top of the exact bug that was fixed — the one spot now covered by236 a test — instead of adjacent to it.237- Reporting "the fix is incomplete" without a demonstrated surviving path.238- Accepting a second point fix for the same file, and scheduling your own next239 report.240- Recording a verified-complete fix nowhere, so the next auditor re-does the241 work you already did.