Skip to content

Sample report

What you get at the end of a test.

A shortened example of our penetration test report: the summary your leadership reads, the findings your engineers fix, and the retest and evidence mapping your auditor asks for.

Example reportSimulated environment, real workflow.

Executive summary

Web application and API penetration test

Example report
Client
Example organization
Scope
Web app, REST API, 2 roles
Method
OWASP WSTG, ASVS L2
Report
v1.1, after retest

We found 8 issues over two weeks of testing. One is critical: any customer account could read every other customer's invoices through the API. Engineering fixed it within two days and our retest confirmed the fix. Two high-severity issues are also fixed. Two medium issues are in fix with a retest booked, and one low issue was accepted as a documented risk.

  • Fix first: object-level authorization. The same lookup pattern appears in three other services we did not test.
  • Strength: authentication, session cookies and TLS on the main domains are configured well.

Severity breakdown

  • Critical1
  • High2
  • Medium3
  • Low2
5 of 8 fixed and verified by retest at report v1.1.

Findings index

Every finding, ranked and owned.

Severity uses CVSS 3.1 adjusted for your context. You follow each fix in the portal through to a passed retest, not only in the PDF.

IDFindingSeverityCVSSStatus
RP-0142Broken object-level authorization on invoices APICritical9.1Fixed · retest passed
RP-0140Password reset token can be reusedHigh8.1Fixed · retest passed
RP-0139Verbose errors leak stack traces and hostnamesHigh7.5Fixed · retest passed
RP-0137No rate limit on /loginMedium5.3In fix · retest booked
RP-0138Session stays valid after logoutMedium5.4Fixed · retest passed
RP-0135Content Security Policy missingMedium4.7In fix · retest booked
RP-0133Server version disclosed in headersLow3.7Fixed · retest passed
RP-0131Outdated TLS cipher suite on legacy hostLow3.1Risk accepted

Simulated environment, real workflow.

Finding detail

Reproducible, with the fix written down.

Each finding gives your engineers the steps and evidence to reproduce it, the business impact, a specific fix and where it maps in your audit frameworks. Three are shown here.

RP-0142CriticalFixed · retest passed

Broken object-level authorization on invoices API

api.example.com · GET /v2/invoices/{id} · CVSS 9.1 · AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N

The invoices endpoint looks an invoice up by its numeric ID only. Any signed-in user can read any other tenant's invoices by changing the ID.

Steps to reproduce

  1. Sign in as a user of tenant B and copy the bearer token.
  2. Request an invoice ID that belongs to tenant A.
  3. The API returns 200 with the full invoice, including billing contact and line items.

Evidence

$ curl -H 'Authorization: Bearer <tenant_B>' \
    https://api.example.com/v2/invoices/981
HTTP/1.1 200 OK
{"tenant":"A","contact":"billing@…","total":"18,400.00"}

Impact

Every invoice on the platform is readable by any customer account. That exposes customer names, contacts and amounts, and is reportable under most customer contracts.

Recommended fix

Check the tenant on every object lookup in the service layer, not in the route, and add a regression test that requests another tenant's object.

Retest

Retested day 9: tenant B now gets 403 for tenant A's invoices.

Evidence mapping

OWASP API Top 10
API1:2023 Broken Object Level Authorization
CWE
CWE-639
SOC 2
CC6.1 logical access
ISO 27001
A.8.3 information access restriction
RP-0139HighFixed · retest passed

Verbose errors leak stack traces and hostnames

app.example.com · POST /api/search · CVSS 7.5 · AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

Malformed search input raises an unhandled exception. The response carries the full stack trace, framework version, internal hostnames and a database query.

Steps to reproduce

  1. Send a search request with a JSON array where a string is expected.
  2. The API answers 500 with the exception and stack trace in the body.

Evidence

POST /api/search  {"q": ["x"]}
HTTP/1.1 500 Internal Server Error
TypeError: q.trim is not a function
  at search (/srv/app/search.js:41)
  db: pg-primary.internal:5432

Impact

The internal hostnames, versions and query structure shorten an attacker's reconnaissance and make other findings easier to chain.

Recommended fix

Return a generic error with a correlation ID, log the detail server-side, and validate input types at the API edge.

Retest

Retested day 9: malformed input now returns a generic 400 with a correlation ID.

Evidence mapping

OWASP ASVS
V7.4.1 generic error messages
CWE
CWE-209
SOC 2
CC7.1 detection of configuration issues
ISO 27001
A.8.28 secure coding
RP-0137MediumIn fix · retest booked

No rate limit on /login

app.example.com · POST /login · CVSS 5.3 · AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

The login endpoint accepts unlimited attempts per account and per source address, with no lockout, delay or challenge.

Steps to reproduce

  1. Send 500 login attempts for one account from one address in a minute.
  2. Every attempt is processed; none is delayed or blocked.

Evidence

500 × POST /login  [email protected]
500 × HTTP/1.1 401 Unauthorized
median response 84 ms · no 429 returned

Impact

Password spraying and credential stuffing run at full speed. Accounts without MFA are exposed to takeover.

Recommended fix

Rate-limit per account and per address, add a progressive delay, and alert the SOC on spikes in failed logins.

Retest

Fix in review. Retest booked within the free retest window.

Evidence mapping

OWASP ASVS
V2.2.1 anti-automation
CWE
CWE-307
SOC 2
CC6.6 boundary protection
ISO 27001
A.8.5 secure authentication

Want this report for your own systems?

A 30-minute call with a senior tester. You leave with a scope, a timeline and a fixed quote.