
The Finding That Died at the Last `if`**Card description:** A practical lesson in adversarial self-validation: why disproving your own exploit before submission protects your reputation and improves your research. **Cover image:** `01-last-if-statement.jpg` --- Finding a vulnerability creates a dangerous kind of excitement. The exploit path looks clean, the impact sounds severe, and for a few minutes the report almost writes itself. Then one condition kills everything. During a recent protocol review, I found a transfer path that appeared to bypass an important restriction. The state changes lined up, the attacker-controlled input reached the sensitive operation, and the first trace looked convincing. It felt like the finding was ready. But a candidate is not a finding. I followed the complete call path again, this time trying to disprove my own idea. A guard in the final execution path checked exactly the state I believed could be bypassed. The transaction reverted before any harmful state change could survive. The vulnerability was dead. That result was not wasted work. It was the review working correctly. ## The researcher must be the first triager Before submitting, I try to answer four questions: 1. Can an ordinary attacker reach the vulnerable path? 2. Does the transaction succeed without privileged assumptions? 3. Does the harmful state change survive the full execution? 4. Can the impact be demonstrated using the project's real tests and configuration? If any answer is no, the report is not ready. A scary code fragment, a failing transaction, or an incomplete call graph is not enough. The most important validation step is not proving that the exploit works. It is actively searching for the condition that makes it fail: a modifier, a state check, a downstream revert, an earlier accounting update, or a permission enforced in another module. ## Killing your own finding is a win A false positive costs more than one rejected report. It consumes triage time and weakens trust in future submissions. Rejecting my own hypothesis early protects that trust. The guard that killed this finding also became useful audit evidence. I recorded the protected invariant and added a regression test so a future change cannot silently remove it. That is the difference between collecting suspicious lines and reviewing a system: sometimes the best result is proving that the code is already safe. The report never left my desk. The lesson stayed in my methodology.

Duplicate: The Most Expensive Word in Bug Bounty**Card description:** What a duplicate really means, why it hurts, and how to turn unpaid research into faster testing and stronger methodology. **Cover image:** `02-triager-duplicate.jpg` --- There is a special moment every bug bounty hunter knows. You map the target, reproduce the issue, record the evidence, write a clear report, and wait. Then the response arrives: **Duplicate.** One word can erase the expected reward, but it does not erase the research. A duplicate usually means another researcher reported the same root cause first. It does not automatically mean the analysis was weak or the impact was wrong. In many cases, reaching the same issue independently confirms that the technical reasoning was solid. The difference was timing. That still hurts. Bug bounty rewards results, not hours. Three weeks of work can have zero direct payout. ## What I keep after a duplicate I do not throw the research away. I extract everything reusable: - The exact signal that led to the vulnerability. - The source-to-sink path and the missed trust boundary. - The test harness, scripts, payloads, and environment setup. - The reason existing protections failed. - Nearby variants that may have a different root cause. - A shorter validation path for the next target. This turns one unpaid report into a reusable detection pattern. The important distinction is between searching for the same endpoint again and learning the vulnerability class. Copying the original payload across targets creates noise. Understanding why the system failed creates transferable skill. ## Reducing duplicate risk Duplicates cannot be eliminated, but their probability can be reduced: 1. Validate the core impact quickly before polishing the report. 2. Submit once the PoC is reliable and the evidence is sufficient. 3. Look beyond the obvious endpoint for deeper variants and shared root causes. 4. Prefer less-crowded attack surfaces where manual reasoning has an advantage. 5. Keep notes structured so setup time decreases with every target. Speed matters, but careless speed creates invalid reports. The goal is not to submit first at any cost. The goal is to reach a defensible conclusion efficiently. A duplicate is expensive when the only thing produced was a report. It becomes valuable when the work also produced a new technique, a better test, or a stronger mental model. The bounty may belong to the first reporter. The knowledge still belongs to the researcher who earned it.

I Read Every File. Did I Audit the System?**Card description:** Why 100% file coverage can still miss a critical state transition, and how call graphs and invariants expose system-level vulnerabilities. **Cover image:** `03-file-coverage-state-transition.jpg` --- “Did you read every file?” is one of the most common questions in a security review. It is an important question, but it is not the final one. A reviewer can read every source file and still miss the vulnerability connecting them. Critical bugs often do not live inside one obviously dangerous function. They appear when several individually reasonable operations create an unsafe system-level state. File coverage measures what was opened. It does not prove that every attack path was understood. ## Functions are not isolated islands Imagine three modules: - One updates ownership. - One calculates withdrawable value. - One transfers the asset. Each function may look correct in isolation. The vulnerability may exist only when ownership changes after the value is calculated but before the transfer is finalized. No single file contains the whole bug. The state transition does. This is why my review process tracks more than files and functions. I map: - Assets and where they can enter or leave. - Roles and the operations each role can reach. - State machines and every transition between states. - External calls and callback opportunities. - Accounting values that must move together. - Guards that enforce important invariants. - Cross-module paths that tests do not exercise. ## From coverage to attack paths My workflow starts with a complete inventory. Every auditable file and externally reachable function is recorded. Then the real review begins. For each sensitive operation, I trace backwards to its entry points and forwards to its final state changes. I ask what an unprivileged user can control, which assumptions are made by downstream functions, and whether a different sequence of valid calls can break those assumptions. I use existing project tests as the baseline, then add adversarial sequences. Execution traces help confirm the actual call graph instead of the call graph I imagined while reading. The target is not simply line coverage. The target is invariant coverage. Examples include: - Total user claims must never exceed available assets. - A transfer must not bypass vesting or lock conditions. - A failed external call must not leave partial accounting changes. - A state transition must not be replayed or reordered for profit. - Authorization must remain valid from entry point to final effect. ## The better completion question At the end of an audit, I still want every relevant file reviewed. Unread code can hide an entire attack surface. But the stronger question is: **Have I connected every asset, role, state transition, and trust boundary into testable attack paths?** Read every file. Then connect the system. That is where the serious bugs usually live.