Nobody actually buys a penetration test. You buy the document at the end of it, because that is the only part that outlives the engagement. It goes to your board, your customers' procurement teams, sometimes your insurer. It is the artefact.
Which creates an unfortunate incentive. A thin test can be written up impressively. A thorough one can be written up badly. Knowing what belongs in the document is how you tell them apart — before you pay for the second one.
It has to serve two readers who want different things
One reader is an executive deciding whether to be worried and how much to spend. The other is an engineer who has to fix something specific on Thursday afternoon.
Reports that fail usually fail by picking one. All narrative and no evidence, and your engineers cannot act on it. All raw output and no summary, and it never gets read by anyone who controls a budget. The structure below exists to serve both without either having to wade through the other's section.
The sections
1. Executive summary
One page. What was tested, when, what was found, and the overall risk in plain language. It should state the number of checks run and the number that failed, because a bare count of findings is meaningless without a denominator. "Six findings" reads very differently against fifty-two checks than against eight.
It should also be honest about what the assessment covers. An external test tells you about your external surface. It says nothing about what an authenticated user can do, and the summary should say so rather than leaving the reader to assume otherwise.
2. Scope and rules of engagement
Exactly what was in scope, what was excluded, the testing window, and the constraints the tester worked under. This section is what makes the rest of the report interpretable a year later, when nobody remembers the kickoff call.
Exclusions matter as much as inclusions. If denial-of-service testing was excluded — and against production it should be — the report has to say so. Otherwise a future reader reasonably concludes it was tested and passed.
3. Findings, individually
Each finding needs all of the following, and a report that omits any of them is making your team do work the tester should have done:
- A severity rating, and where the weakness can be modelled, a CVSS v3.1 base score with its full vector string
- A classification — the CWE identifier and the relevant OWASP Top 10:2021 category
- What is affected — which routes, which parameter, which environments
- Evidence — the actual request and the actual response, not a paraphrase
- Impact, described in terms of this deployment rather than the vulnerability class in general
- A specific recommendation, not a link to a generic article
- Verification status — how the fix was confirmed, and on what date
The vector string is worth insisting on. CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N is checkable. A number on its own is a claim. The vector lets your team see the tester's reasoning and disagree with it if they have context the tester did not.
4. Controls that passed
This is the section most often missing, and its absence is the single best signal that a test was shallow.
A report that names only what is wrong tells you nothing about what was looked at.
If your report lists six findings and nothing else, you cannot tell whether the tester examined your authorisation model, your session handling or your error behaviour and found them sound — or never opened them at all. Both produce an identical document. The passing rows are what make the failures meaningful.
5. The remediation table
One row per finding: the fix applied, how it was verified, the date, and the resulting status. This is the table that gets extracted and sent to a customer who asked whether you take security seriously, so it needs to stand on its own without the evidence blocks around it.
6. Limitations
What the method could not reach, stated plainly. For an external black box test that typically includes how credentials are stored and compared, anything behind a paywall, cross-account access without a test account, and session lifecycle behaviour.
A tester who does not write this section is either inexperienced or selling you certainty they cannot supply. Their absence from the findings list is not evidence that they are sound.
How severity should be rated
CVSS v3.1 is the common language, and its bands map to expected response like this:
| Severity | CVSS base | What it means for you |
|---|---|---|
| Critical | 9.0 – 10.0 | Direct compromise of the application, its data or its users. Fix immediately. |
| High | 7.0 – 8.9 | Significant unauthorised access or data exposure. Fix before launch. |
| Medium | 4.0 – 6.9 | Real weakness, usually needing a precondition or a second flaw. Next release. |
| Low | 0.1 – 3.9 | Limited impact, or a defence-in-depth control that is missing. Fix when convenient. |
| Informational | Not scored | No security impact. Recorded for completeness. |
Two points on how this gets applied in practice. Ratings should reflect the risk in your deployment rather than the theoretical maximum for the vulnerability class — a missing script-src is a serious matter in an application handling card details inline, and a Low in a brochure site.
And informational items should carry no CVSS score at all. CVSS has nothing to measure when confidentiality, integrity and availability are all unaffected. Assigning a 0.0 to make the table look tidy is a small dishonesty that suggests larger ones.
Red flags when you read one
- No passing checks listed. Coverage is unknowable.
- Findings that are clearly scanner output. Boilerplate description, no reproduction steps, no request or response captured.
- Severities that do not match the deployment. A generic High for a missing header on a static marketing site suggests the tool set the rating, not a person.
- No limitations section. Nobody tests everything. A report claiming otherwise is claiming something impossible.
- Everything closed with no evidence of re-testing. Closed on whose authority, verified how?
- Volume as a proxy for value. Forty low-severity informational findings is a scanner export, not an assessment.
Verification is part of the report, not an add-on
A finding is not closed because somebody merged a change. Configuration drifts between environments, a fix can land on one host and not another, and a route can go quiet without actually being disabled.
The re-test should repeat the original probe, and it should run alongside control requests — a path you know does not exist, and a path you know does. That comparison is how you distinguish "disabled" from "temporarily not answering". It is an easy distinction to get wrong, and it is the reason a verification pass sometimes surfaces findings the original test recorded as clear.
If your report says fixed, it should also say how that was established. See remediation and re-testing for how we handle that stage.
Remediation & Re-Testing
We work through every security finding with your team, then re-test against the live system and record the evidence. Verification, not just a PDF. Free consultation with NEXHUB Technology.
See how we run it →