There is a gap in security research that almost nobody writes about, because admitting it feels like admitting you were wrong. You weren't. The code really does what you said it does. The invariant really is broken. Your proof-of-concept really does compile, run, and print the number you promised. And the report is still worth nothing.
Between "the code is wrong" and "you get paid" sits a second body of work that has nothing to do with finding the bug. I have come to call it the realism gate: a set of deterministic checks a finding has to survive before I will let it near a submission. Not "is the mechanism real" — I already know it is. The question is narrower and harsher: is the attack real, under the conditions this protocol actually runs in, performed by someone who could actually perform it?
I built the gate because I kept failing it. This is the story of three findings that each died at a different checkpoint. All three are mechanically correct. All three are held in my log, not submitted — a held finding can be re-proved later; a submitted-and-rejected one cannot be retracted. In the public rejection taxonomy they are three rows, classified under a single earliest gate named REALISM.
The gate is six questions, run in order. A finding has to answer all of them from evidence, not from intent:
Attacker model. Who performs each step, with what capability, in which transaction or call boundary, on which chain execution model? "An attacker can…" is not an attacker model. Name the account, the capital, the trigger, and the boundary.
PoC-privilege audit. Does any attacker step in the proof run inside a privileged context? Separate senders, separate transaction boundaries. If the exploit only works because the harness quietly put the attacker's call inside the victim's own transaction, the harness is lying to me.
Impact-reachability. A corrupted-state claim needs a permissionless, value-moving consumer of that state. Corrupting a number nobody spends is not a loss.
Realistic-configuration. The headline number has to be measured at the configuration the protocol documents and real deployments use, not the one parameter setting that makes the margin look good.
Concession and contradiction scanner. If the report itself concedes no fund loss, no attacker, or a fee-drag that eats the profit, that concession caps the severity. The report cannot argue against itself and win.
Program-rules gate. Scope entry, the quoted severity bullet, the known-issue and prior-audit search — recorded, not assumed.
Here is each one firing.
On a Sui vault I found a genuinely missing authorization. A public function, update_curator_position_value, took an asset key from the caller and wrote a previously-approved valuation into it — no capability required, the key chosen by the caller from another key's value. That is a real missing-authorization primitive. I proved it in a harness.
Then the attacker model killed it. To turn the write into a corrupted vault total, the injected write has to land inside the operator's own programmable-transaction block. Sui PTBs are single-sender: an outsider cannot splice a command into the operator's transaction. And in the reading transaction, the operator's own re-valuation overwrites my injected value before anything consumes it. I went looking for a permissionless consumer of the corrupted total — a path where someone other than the operator moves value based on the poisoned number. There isn't one (checkpoint three, impact-reachability, also fails here). The only PoC that "worked" worked because my harness composed the injection into the operator's boundary. That is checkpoint two failing out loud: the proof was running the attacker's step inside a privileged context.
Mechanism: real. Attacker: nonexistent on this chain's execution model. Held.
On a swap VM I found a two-swap round trip that extracts maker inventory by composing two instructions — a concentrate step before a pegged swap. The round trip runs: 20 of 20 cycles at one particular parameter. It is a real invariant drift on the production opcode table.
Three checkpoints fired at once. The round trip only exists if the maker authors that specific composition — a strategy no deployed or documented maker ships, so the realistic-configuration gate fails: I was measuring at a configuration that does not exist in the wild. At the documented stablecoin configuration the margin is about 0.009% of the pool per cycle at half-fills or better — and the report's own fee differential shows the taker does not profit once fee drag is applied, likely going negative after gas. That is the concession scanner firing: the report conceded the thing that would have made it payable. And the severity was self-derived "High" with no quoted program severity bullet — the program-rules gate had nothing recorded to stand on.
Mechanism: real, 20 of 20. Profit at the real configuration: negative. Held.
On a Bitcoin-bridge CLI I found a verification-integrity defect worth describing: the recovery flow re-derives its taproot parameters from the same live config it is supposed to verify against, and the verify step then certifies a consensus-invalid recovery transaction. The control validates its input against a mutable copy of that input. That is a real defect in a tool whose entire job is to be trustworthy.
The concession gate ended it. The trigger is the user re-running their own init, upgrade or config step — there is no attacker; the victim is the actor. The report concedes no funds are lost. And the consensus-invalid transaction is rejected at broadcast, so the worst realistic case is a failed retry the user performs against themselves. A finding with no attacker and no fund loss, on a program whose client-tooling scope and lowest paid tier I had not even established, is not a submission.
Mechanism: real. Attacker: the victim. Fund loss: conceded as none. Held.
On a Bitcoin-bridge CLI I found a verification-integrity defect worth describing: the recovery flow re-derives its taproot parameters from the same live config it is supposed to verify against, and the verify step then certifies a consensus-invalid recovery transaction. The control validates its input against a mutable copy of that input. That is a real defect in a tool whose entire job is to be trustworthy.
The concession gate ended it. The trigger is the user re-running their own init, upgrade or config step — there is no attacker; the victim is the actor. The report concedes no funds are lost. And the consensus-invalid transaction is rejected at broadcast, so the worst realistic case is a failed retry the user performs against themselves. A finding with no attacker and no fund loss, on a program whose client-tooling scope and lowest paid tier I had not even established, is not a submission.
Mechanism: real. Attacker: the victim. Fund loss: conceded as none. Held.
Three findings, three different checkpoints, one outcome. In the rejection taxonomy they are the three rows whose earliest catchable gate is REALISM — out of 111 logged rejections. That is a small count, and I keep it visible on purpose: these are not the demoralising rejections. The duplicates and the out-of-scope clauses are things I could have caught before reading a line of code. The realism rows are different. Each one cost a full proof to produce, and each one taught me a check I now run before the proof, not after.
The uncomfortable truth the gate encodes is this: finding the bug is the part I am good at, and it is not the part that pays. A correct mechanism with no real attacker, measured at a configuration nobody runs, conceding its own lack of impact, is not a near-miss. It is a complete piece of analysis that answers the wrong question. The realism gate is me, in advance, asking the right one.
The finding was real. I had to learn to ask whether the attack was.

