What does a penetration test report look like?
Nobody should buy an audit without knowing what the deliverable looks like. This page follows the exact structure of the PDF we hand over at the end of an engagement: same sections, same level of detail. Only the content differs, because it is rebuilt and anonymised here.
A report read by two people who want different things.
An audit report has two readers, and they do not open it for the same reason. Leadership wants to know what is at risk, how long they have, and where to start. The engineering team wants the exact path, the request to replay, and the line to change. A report that only speaks to one of them ends up in a drawer.
That is why the executive summary is written without jargon, with a glossary where a term cannot be avoided, and the technical detail comes after it in full, raw requests included. Both live in the same document: nobody has to ask for the long version.
Severity is not a score copied out of a tool. Every finding carries a vectorised CVSS, but the priority reflects what the flaw allows in your environment. A critical on an isolated staging box is not handled like a high on the service that carries your payments.
The eight sections, in order.
The contents page is the same on every engagement. You know where to look before you open the document.
Rules of engagement
The exact target, the written authorisation covering the test, the dates, the duration, the method and the tooling used. It also carries a declaration of what was touched: test accounts created and then deleted, reversible changes, third-party data masked. Nothing is left behind.
Executive summary
What is well protected, what is not, the main risks and the recommended order of action. Written to be read by a board without a security background, with a glossary for the terms that cannot be translated away.
Findings summary
The summary table: a number, a severity, the component affected, the vulnerability, the impact and the status. One page to see everything, and the way into the detail.
Findings in detail
One entry per flaw. Severity and vectorised CVSS, a precise description of the defect, reproduction evidence with the raw requests, demonstrated impact, exploitation prerequisites, numbered remediation and status.
What holds
Most reports list only problems, which paints a false picture of a system that is often sound overall. Knowing what is already solid stops you from rebuilding what did not need rebuilding.
Non-blocking recommendations
The hardening that does not belong in the findings list but still shrinks the surface: headers, configuration, logging, rate limiting. Kept separate so it never drowns the priorities.
Retest
The result of replaying the tests after your fixes, finding by finding, with the date and what was observed. This is the section that turns a list of problems into a closed file, and it arrives as version 1.1 of the report.
Appendices
The full requests and evidence, gathered at the back so the body of the report stays readable. Screenshots and sensitive access are kept separately and sent over an encrypted channel, never pasted into the document.
The findings summary.
A rebuilt extract. Every row points to a detailed entry in the next section of the report.
| No. | Severity | Component | Vulnerability | Impact |
|---|---|---|---|---|
| 4.1 | Critical | api.example.com/v1/orders/{id} | Object-level access control missing (BOLA) on order reads | Any customer's order readable from its identifier alone |
| 4.2 | High | api.example.com/v1/account | role field accepted on write (mass assignment) | A standard account promotes itself to admin in one request |
| 4.3 | High | www.example.com, password reset | Reset token never expires | An intercepted link still works weeks later |
| 4.4 | Medium | www.example.com, login | No rate limiting on authentication attempts | Credentials from public breaches replayed at scale |
One finding, in full.
This is the complete entry for row 4.1 above. It is the level of detail your engineering team receives, for every flaw.
4.1 Any customer's orders readable without authorisation
CriticalGET /v1/orders/{id} on api.example.com200 either way.GET /v1/orders/4172 HTTP/1.1
Host: api.example.com
Authorization: Bearer <test account token>
HTTP/1.1 200 OK
{"id":4172,"customer":"test account","total":"149.90", ...}
GET /v1/orders/4173 HTTP/1.1
Host: api.example.com
Authorization: Bearer <the same token>
HTTP/1.1 200 OK
{"id":4173,"customer":"a different customer","total":"318.00", ...}- Check server-side, on this route and on every route that takes an object identifier, that the object requested belongs to the session's user.
- Handle access control in a shared layer rather than route by route, so the omission stops being possible.
- Replace sequential identifiers with unguessable ones (UUID v4). Defence in depth, not a fix on its own.
- Audit the access logs for past enumeration against this route.
What the report does not contain.
As much as what it holds, what is deliberately absent from it is part of the engagement.
- None of your customers' real data. Evidence is truncated and masked, and extracted datasets are never copied into the document.
- No live credentials or tokens. Anything that has to reach you goes over an encrypted channel, separately from the report. And if real account credentials have leaked and we come across them during the engagement, we flag it right away so you can change them, without waiting for delivery.
- No padding. No raw tool output pasted in, no unverified theoretical finding added to inflate the count.
- No circulation. The report is yours; we share it with nobody and we never name a client.
A report like this one, on your application.
Describe the perimeter and we come back with a scope and a quote. First conversation with no commitment, complete confidentiality.