Report Writing for Pentesters


That is the part of the job nobody tells you when you start. You spend two weeks chaining findings, staying up late to prove that a low-priv SSRF actually reaches the metadata service — and the client sees none of it. What the client sees is a document. If the document is weak, the two weeks did not happen.
I have been on both sides of this. And the uncomfortable truth is that the report is not a summary of the work. The report is the work, as far as the person paying is concerned.
What the report is actually for
It is not a trophy case. It is not a transcript of every command you ran. A report exists to make someone take a specific action: fix these things, in this order, because of this reason.
Three readers, always:
  • The executive reads the summary and nothing else. They need to know how bad it is, in one paragraph, without a single acronym.
  • The engineer reads the findings and needs to reproduce what you did and understand exactly what to change. Step-by-step, no guessing.
  • The auditor / CISO reads the scope, methodology and risk ratings, and needs to be able to show the document to someone else as evidence.
Write once, satisfy all three. That is the whole trick, and it is why good reports have a rigid structure instead of free-flowing prose.
The structure I keep coming back to
  1. Executive summary — half a page, no jargon, the two or three things that matter and the business consequence of each.
  2. Scope and methodology — what was tested, what was explicitly out of scope, what window, which standards I worked from. This section protects you. If it was not in scope, it was not missed — it was excluded.
  3. Findings — the core. Ordered by severity, always.
  4. Risk context — how the individual findings combine. One medium finding that chains into domain admin is not a medium problem.
  5. Appendix — raw output, request/response dumps, the stuff engineers sometimes want and executives never open.
I break this structure more often than I keep it — small engagements do not need five sections — but the findings section never changes shape.
The finding is the atomic unit
Everything downstream depends on how you write a single finding. Mine has four parts, in this order:
A title that states the problem, not the tool.
SQL injection in search parameter — a human knows what this is.
SQLMap: high risk finding — a human knows nothing. I have received reports from vendors that literally listed tool output under headings like that.
Impact, in business terms.
Not "this allows remote code execution." What does RCE on this box mean? Can it reach the customer database? Can it reach everything? The engineer will figure out the mechanics; the person approving the fix needs the reason.
Reproduction steps a stranger could follow.
Numbered. Exact. Starting from an authenticated session if that is the requirement. If your steps only work because you know the environment, they are not reproduction steps, they are memories.
A remediation that names the fix.
"Upgrade the library" — to what version? "Add validation" — where, on which input? "Educate developers" — not a remediation. The cheapest report I ever saw read like a to-do list someone could act on this sprint, and it got fixed in a sprint. The expensive ones sat in a backlog for a year.
One finding, two versions
Same bug, same severity, two ways of writing it up:
Bad: "Reflected XSS detected in parameter q on /search. Tool output attached. CVSS 6.1."
The engineer has to open Burp, re-run the scan, figure out where it matters and what the exposure is. That report goes into a ticket titled "pentest findings" and nobody touches it for a quarter.
Good: "Unescaped user input in the q parameter of /search reflects directly into the page HTML, so a crafted link executes JavaScript in the victim's session. Reachable unauthenticated from the public login page. Impact: session cookie theft against any user who opens the link — including admins, since the cookie has no SameSite attribute. Fix: apply output encoding at search.jsp:41 and set SameSite=Lax on the session cookie. Payload in Appendix B."
Same finding. One of them gets fixed.
What kills reports
I have written all of these mistakes at least once:
The tool dump. Five pages of nuclei output pasted verbatim. It looks thorough. It is not analysis — the client already has a scanner, they hired me because scanners produce noise. Curate, deduplicate, explain.
Burying the critical. Findings listed in the order they were discovered, with the domain-admin chain on page 14. Severity order is not a style preference, it is the difference between a fix happening and a fix being filed.
Screenshots without words. An image of a Burp tab proves nothing to someone who does not run Burp. The screenshot supports the text; it never replaces it.
Vague scope. "Testing was performed against the application." Which application? Which subdomains? What dates? Six months later nobody can say whether the thing that was found was even in scope. This section is your insurance policy — I write it first, before anything else.
Findings with no owner. A report with no remediation timeline, no severity rationale, no named contact for follow-up — it will be read once and archived.
How I work while the test is running
The report is written during the engagement, not after it. That sounds obvious and almost nobody does it, including past me.
My routine: every finding gets its title written down the moment I confirm it, with the reproduction steps while they are still in my scrollback. Five minutes on day one. The alternative is reconstructing a chain from three weeks of shell history, which is how reports end up with steps that do not actually work — and a client cannot reproduce your finding is the fastest way to lose the argument about severity.
I also keep a running scope log. Tested X, skipped Y, Z was dead. When the final report comes together, the scope section writes itself.
Severity: use the framework, then use your head
CVSS is a starting point, not a verdict. It gets you a defensible common language — and yes, clients ask for it. But I have never sent a report where the vector string was the end of the conversation. A stored XSS in an internal tool used by twelve people is not the same incident as the same bug on a public login page, whatever the calculator says.
What I do: rate with the framework, then adjust with a written justification in the report itself. If my rating differs from the raw score, the reader sees why, in one sentence. That sentence has never once been challenged.
The part nobody teaches: the rewrite
First draft goes out to nobody. I read it the next morning, and every time I find at least one finding whose impact I inflated at 1 a.m. and one whose remediation is too vague to act on. Then I ask myself the only question that matters:
If an engineer reads only this one finding, can they fix the bug without emailing me?
If the answer is no, the finding is not finished.
After you hit send
The report is not the end of the engagement. In practice I budget for two things after delivery: questions and a retest.
Questions are cheap when the report is clear and expensive when it is not — and every question you get about finding number three is a small signal that finding number three was written for you, not for the engineer. I keep a short "assumptions and limitations" block at the end of the scope section, and it kills most of these emails before they are sent.
The retest deserves a line in the original report too: which findings are eligible, how long the retest window is, what counts as fixed. Spelling that out at delivery is the difference between a closed engagement and a six-month email thread.
Further reading