River
Y CombinatorBacked by Y Combinator
FREE TEMPLATE

Penetration Test Response Template

Your report's findings table is a list of symptoms. This pack keys the programme on the cause, and splits Closed into the three claims it is.

Free download  ·  No account needed

A findings table is a list of symptoms, and the bodies that write the standards say so outright. The PCI Security Standards Council's testing guidance warns that a test may not identify every instance where a control is insufficient. Finding one cross-site scripting flaw in one area of an application may not reveal all of them, and the presence of one often indicates a weakness in process or development practice that replicated the same flaw elsewhere. Close the row and the defect stays.

NIST SP 800-115 arrives at the same place from the other direction. Identifying root cause is what improves posture, because a cause traces to a program-level weakness, and it names eight of them. Eight is a closed list rather than a set of examples, so this pack carries a row for each one whether or not your report touched it. In the worked response 23 findings collapse onto seven causes with 50 instances behind them, and the eighth row says checked and empty.

Then the number everybody reports. Ten of those 23 read as closed, and two of the ten were verified by re-running the test that found them. Two more rest on a check of the single instance the report named, two on an engineer's word, and four on nothing recorded at all. Those are four different claims wearing one status, and every response template on page one of this search collapses them into a closure percentage a reader cannot check.

Ten closures, four different claims

Every total below reconciles: 23 findings, 50 instances, 8 cause rows, 10 closed, 1 accepted, 2 reopened, 10 open.

Finding Register

Rampart Security’s assessment of the Alderwood shipment platform, a fictional logistics company. Three rating columns, because a pentest rating is not CVSS and often is not claimed to be.

IDFindingAs reportedScaleNormalisedCausePathReportedFoundStatus
RS-01Unauthenticated order exportCriticalRampart matrixCriticalRC-0211Closed
RS-02SQL injection, shipment searchCritical / CVSS 9.8Matrix + CVSS v3.1CriticalRC-0216Reopened
RS-06Unpatched Confluence, CVE-2023-22515High / CVSS 10.0Matrix + CVSS v3.1HighRC-0311Closed
RS-07Password policy not enforced on service accountsHighRampart matrixHighRC-08111Open
RS-09TLS 1.0 on the legacy partner endpointMediumRampart matrixLowRC-0113Accepted
RS-11Verbose errors disclose the ORM versionMediumRampart matrixMediumRC-01P-111Closed
RS-19Internal hostname in a response headerLowRampart matrixMediumRC-01P-117Open
RS-21Service account naming is guessableLowRampart matrixMediumRC-08P-111Reopened
RS-22Two staff supplied credentials to the pretextInformationalNot rated. Ratio, 2 of 31MediumRC-06P-111Open
RS-23Four hosts in scope were unreachableInformationalNot a findingScope gapn/a44Open

Read the two rating columns against each other. P-1’s four links were reported Medium, Low, Low and Informational and all four normalise to Medium, because a link in a live attack path is never rated below medium here. RS-09 moves the other way. RS-22 arrived with no severity at all: the report gave a phishing success ratio of 2 of 31, so a register with one severity column would have invented a number or dropped the row. The Found column is the other one nobody ships: a tester found one place in a time box from outside, and RS-07 is the same defect on eleven service accounts.

Root Cause Register

The sheet the programme is actually sequenced from. All eight of NIST SP 800-115’s program-level weaknesses carry a row, whether or not the report reached them.

IDRoot causeFindingsInstancesSymptoms fixedSystemic fixStatus
RC-01Lack of security baselines6173 of 6Hardened base image plus a build-time check on secrets and headersOpen
RC-02Security not integrated into the SDLC5113 of 5Parameterised helper as the only database path, plus an authorisation test per pull requestOpen
RC-03Insufficient patch management444 of 4Not started. No inventory, no scan cadence, no ownerOpen
RC-04Security architecture weaknesses330 of 3Management interfaces move behind the existing identity-aware proxyOpen
RC-05Inadequate incident response procedures220 of 2Detection coverage for the attack classes the test actually ran, replayed quarterlyOpen
RC-08Lack of security policy enforcement2120 of 2Credential policy for non-human accounts, enforced by the identity provider rather than by reviewOpen
RC-06Inadequate training110 of 1Follow-up with the two staff who responded, then a repeat pretext inside 90 daysOpen
RC-07Insufficient threat management00n/aNone required. Nothing in this report traces hereChecked

23 findings, 50 instances, 8 rows. RC-03 is the row this sheet exists for. Four findings, four patches applied, all four verified, systemic fix not started. Every symptom is gone and the next missing patch will be found by the next tester, which is the standard’s line about a root cause never being a patch showing up in your own data. RC-08 is the shape of an unenforced policy from outside: two findings, twelve instances. RC-07 is empty on purpose, because a register that lists only the causes you found cannot tell you which ones you never looked for.

Retest Evidence

One row per claim that something is fixed, keyed on what was actually done rather than on the ticket status.

IDClaimed byVerification classVerified byScope retestedResult
RS-01R. AchebeMirror retestRampart SecurityOriginal test case, every order export routePass
RS-06D. OkaforMirror retestRampart SecurityVersion probe plus the original exploit, all four nodesPass
RS-04L. PetrovSpot check on reported instanceQ. NakamuraThe single endpoint named in the reportPass
RS-14L. PetrovSpot check on reported instanceQ. NakamuraReset endpoint only. Register holds 2 instancesPass
RS-03S. IyerAttestation onlyS. IyerNoneAsserted
RS-11R. AchebeAttestation onlyR. AchebeNoneAsserted
RS-02R. AchebeSpot check on reported instanceQ. NakamuraThe shipment search endpoint named in the reportFail
RS-05NobodyNot verifiedn/an/aNot claimed

RS-03 and RS-11 have the same name in both columns, which makes the class attestation only whatever was tested. RS-02’s spot check passed on the endpoint the report named, then a second reviewer found the same unparameterised helper on five more, so it is reopened with six instances. The honest sentence is not ten of twenty-three closed. It is ten read as closed, two verified against the original test. RS-05 sits at not claimed, which is the correct state for open work and keeps it out of every count.

What comes in the pack

01

Finding Register, with the firm's rating beside your own

Three rating columns rather than one severity. The firm's word verbatim, the scale that produced it, and your normalised severity. The scale column matters because most reports rate on the firm's own likelihood-by-impact matrix, some print CVSS beside it, and some rows arrive as a success ratio with no severity at all. A single-column register invents a number for those or drops them.

02

Instances the report named, and instances that exist

Two columns, because a tester found one place in a time box from outside and the defect usually repeats. In the worked response 23 findings carry 50 instances, one row went from one reported instance to six under review, and another is the same missing check on eleven service accounts. A retest scoped to the reported instance closes the finding and leaves the defect.

03

Root Cause Register, with a row for all eight causes

The programme runs from here, not from the findings table. Every finding is assigned one program-level weakness, and all eight carry a row so a category with nothing beneath it reads as checked rather than forgotten. Each row holds the instance total, the fraction of its symptoms already fixed, and a systemic fix with an owner and a date of its own. Where a cause lands on architecture, that fix usually needs the design written down before anybody can agree to it.

04

Retest Evidence keyed on the verification class

A mirror retest of the original test case, a spot check on the one instance the report named, an attestation, or not verified. Nothing reaches closed without one, the claimant can never be the verifier, and the count of findings verified against the original test is always smaller than the count reported as closed. Report the smaller one.

05

Attack paths as objects, and links that cannot be accepted

A path gets its own identifier, its own document, and the rating each link carried individually. The worked path reaches cluster administrator through four findings reported Medium, Low, Low and Informational, and two had already been accepted or closed on an engineer's word. A link inside a live path is never rated below medium and cannot be accepted while the path stands.

06

The four documents somebody is actually waiting on

A management response written to be sent, leading with the verification breakdown rather than a closure count. A remediation plan sequenced by cause. An attack path note. A retest request that asks for four different things instead of the single pass a firm will offer. All four ship filled with a worked response rather than as skeletons.

How it works

  1. 1

    Send the report

    The PDF, the portal export, the spreadsheet the firm attached, or the retest letter if you are partway through. The statement of work helps too, because that is where the scoring mechanism and the retest entitlement were agreed.

  2. 2

    Every finding lands with its scale named

    River transcribes each rating exactly as the firm gave it, names what produced it, and normalises separately. Attacks that triggered no alert, negative results and unreachable hosts get rows too, since an alerting gap has no patch to track.

  3. 3

    Causes and paths come out of the report

    Each finding is assigned one program-level weakness, and the causes are ranked by the instances behind them. Every escalation path becomes its own object with the rating each link carried, so its links stop being triaged one at a time.

  4. 4

    Closure gets a class, then a response

    Every claim that something is fixed is recorded as what it really is, then the management response is drafted from the registers with the mirror-retest count leading and the closure count beside it.

Frequently asked questions

Why not just track the findings table?

Because it is a list of symptoms. The Penetration Testing Execution Standard contrasts the systemic issue, lacking an effective patch management process, with the symptomatic one, a single missing patch on one box, then states that the root cause is never a patch. The worked response has a cause with four findings patched, four verified, and the systemic fix not started.

Where do the eight root causes come from?

NIST SP 800-115 names them, in a section arguing that identifying root cause improves posture because a cause traces to a program-level weakness. Baselines, the development life cycle, patch management, architecture, incident response, training, threat management, and policy enforcement. The register carries a row for each so an untouched category reads as checked.

Our report has no CVSS scores. Is that a problem?

No, and it is the normal case. The PCI guidance says a report should document how its ranking was derived, notes that a vulnerability inherent to one environment has no standardised score available, and asks for traceable reasoning behind any custom score. So the register records the firm's rating, the scale behind it, and yours.

What makes a retest different from a spot check?

Scope, and the standard is unambiguous. NIST SP 800-115 says a test team can verify an implementation only where a mirror copy of the original test is performed. It separately permits a signed attestation while naming the risk of not technically verifying. Those are different claims, so they are different values.

Why can a low finding not be accepted?

It can, unless it is a link in a live attack path. Path links are individually low by construction, which is exactly why each is easy to accept on its own merits and why doing so decides nothing about the path. The worked path's links were reported Medium, Low, Low and Informational, and two were already dispositioned before anybody read the chain.

How does this differ from vulnerability management?

Different input, different failure mode. Use vulnerability triage for scanner output, where the volume is the problem, and the secure code review checklist for what a reviewer should have caught before either report existed. A pentest report is human-authored and small, and its hard parts are the escalation path, the detection results, and what Closed actually means. Neither survives inside the other's tracker.

What should we do before the next test?

Two things. Threat model the designs going out, with the threat model pack, so the next report is not full of questions nobody asked. Then reuse the register as evidence when a customer sends a security questionnaire, since the answers they want are the ones you have already verified. Your auditor samples it too, beside the SOC 2 evidence register.

Report the number that is true

Send the report. River keys the programme on the cause, tracks every attack path as its own object, and tells you how many of your closures anybody actually verified.

Install the pentest response pack