Anatomy of a Finding: How We Write Security Reports
A vulnerability that cannot be understood, reproduced, and fixed by the people who own the code has not really been reported. Our structure, and the reasoning behind it.
Published by Xevry
The report is the product of a security assessment. Testing produces knowledge; the report is how that knowledge reaches the people who can act on it. A brilliant finding written badly gets misprioritized, misunderstood, or fixed incorrectly.
Every finding we write follows the same structure, so engineers know exactly where to look for what they need.
The structure
| Section | Purpose |
|---|---|
| Title | States the flaw and its consequence in one line. "Missing ownership check allows reading other tenants' invoices", not "IDOR in API". |
| Severity | A rating derived from impact and likelihood, with the reasoning shown. |
| Location | Exact files, functions, line ranges or endpoints, pinned to the commit we reviewed. |
| Description | What the code does, what it should do, and why the difference matters. |
| Impact | What an attacker can concretely achieve, and under which preconditions. |
| Proof of concept | A runnable test or exact request sequence that demonstrates the issue. |
| Recommendation | A specific fix, plus any broader pattern the team should address. |
| Status | Updated after fix review: resolved, partially resolved, or acknowledged. |
Severity is reasoning, not a label
We rate severity from two independent questions: how bad is the outcome if exploited, and how likely is exploitation given the real preconditions. Direct loss of user funds that any account can trigger is critical. The same loss requiring a compromised admin key is not.
We show that reasoning in the report, because teams prioritize better when they can see — and challenge — why something was rated the way it was. We also resist severity inflation. A report padded with overstated issues trains readers to discount everything in it.
Evidence over assertion
Every material finding should include a proof of concept. For smart contracts, that is typically a test — in Foundry, Hardhat or similar — that demonstrates the issue before the fix and fails to reproduce it after:
function test_victimDepositMintsZeroShares() public { vm.prank(attacker); vault.deposit(1); // 1 share vm.prank(attacker); token.transfer(address(vault), 10_000e18); // donation vm.prank(victim); uint256 minted = vault.deposit(10_000e18); assertEq(minted, 0, "victim received zero shares");}A PoC removes ambiguity about whether an issue is real, gives the developer a regression test for free, and lets us verify the fix mechanically during the retest.
Recommendations that can be implemented
"Add proper validation" is not a recommendation. We describe the specific change, explain trade-offs between alternative fixes, and flag when a finding is one instance of a pattern that likely exists elsewhere in the codebase.
What we leave out
- Scanner output pasted without manual validation.
- Findings we could not reproduce, presented as confirmed.
- Generic best-practice padding unrelated to the code in scope.
Informational notes about code quality or gas usage are welcome when they are useful, but they are clearly separated from security findings so they never dilute the issues that matter.