Retesting Vulnerabilities After a Pen Test Fix
Confirming a fix actually worked requires testing beyond the original exploit.

A pen test report marked "remediated" means a developer pushed a fix and closed the ticket. It does not mean the vulnerability is gone. Those are two different claims, and the gap between them is where breaches happen. Vulnerability exploitation rose 34% year-over-year, which says something uncomfortable: plenty of organizations think they've closed their loops when they haven't. Retesting is the step that turns "we fixed it" into "we checked, and it's fixed." Skip it, and every remediation is just an assumption wearing a checkmark.
What needs to be verified when a fix is retested
A real retest confirms three separate things, and most teams only check the first one.
First, the original attack path has to be dead. Not just the specific payload from the report, the whole vulnerability class behind it. If a tester flagged SQL injection using a single crafted string in a login form, blocking that exact string proves nothing. The database layer either handles untrusted input safely now or it doesn't.
Second, the fix can't have broken something else. Code doesn't live in isolation. A patch to one module routinely touches shared libraries, shared authentication logic, shared anything, and a fix that closes one door can crack open another one down the hall.
Third, the fix needs to survive variants beyond the one technique used the first time around. An attacker who reads the same public advisories your team does won't retry the identical exploit. They'll rotate the encoding, change the injection point, try a slightly different header.
Automated scanners can't do most of this. A scanner that reports "no SQLi detected" checked known signatures against known patterns, that's it. It has no idea whether the parameterized query got implemented at every query site in the codebase or just the one that showed up in the report. And business logic flaws sit entirely outside scanner reach: a scanner has no concept of "authorization," it can't tell you whether a payment approval step that used to skip verification now actually enforces it. That takes a person thinking like an attacker rather than a tool matching strings.
There's a middle category too: risk-reduced but not closed. A fix that lowers the severity of a finding without eliminating it. That's not remediation; it's a smaller version of the same open door, and it needs to stay logged as open until it's actually shut.
Retest approaches by vulnerability class
Different vulnerability classes fail in different ways, so the retest can't be one script run against every finding.
SQL injection is fixed by parameterized queries or prepared statements, applied everywhere the database gets touched, not only at the flagged endpoint. Retesting means throwing the original payload first, then trying variants against sibling endpoints hitting the same database layer. And any ORM or query-builder upgrade that shipped alongside the fix deserves a second look: those upgrades sometimes leave a raw-query escape hatch open somewhere nobody thought to check.
Cross-Site Scripting is fixed through sanitization on input and encoding on output, applying to every field rendered in that same template context, not just the one that got flagged. Retesting covers stored, reflected, and DOM-based variants, plus a check that Content Security Policy headers are actually present and actually restrictive. In a microservice setup, XSS that steals a session token in one service can get replayed against every other service trusting that token, so retest scope has to follow the token, not stop at the service boundary.
SSRF requires the fix to validate every URL-consuming input, webhooks, callbacks, data import fields, against an allowlist. A blocklist is not a fix; it's a suggestion an attacker will route around. Retesting means attempting to reach internal services and cloud metadata endpoints directly. On AWS, that means trying to reach the instance metadata service to confirm credentials are no longer exposed through it. Any new API endpoint that accepts a URL and shipped in the same release cycle belongs in scope too, whether or not it was part of the original finding.
Insecure deserialization is fixed by ensuring untrusted data no longer gets deserialized without integrity checks, and unsafe functions, such as unpickling untrusted input, get fully replaced. Retesting means attempting to pass crafted serialized objects back through the application, and confirming the fix landed at every deserialization site in the service, including those beyond the single one named in the report. Even where full remote code execution isn't achievable anymore, check whether a tampered object can still be replayed to climb privileges. Partial credit isn't a pass here.
Structuring the retest process so nothing falls through the cracks
Retesting without a process is how findings quietly disappear. A four-step structure keeps that from happening.
In step one, every finding gets a retest ticket before a developer writes a line of the fix. The ticket names the original attack vector, spells out what "closed" actually looks like, and lists which adjacent surfaces fall inside retest scope. Writing this after the fix ships means writing it to match whatever got built, which defeats the purpose.
In step two, confirm the fix landed in the right environment before anyone retests it. Testing against a staging environment that doesn't mirror production is a waste of a testing slot, since staging almost never mirrors production configuration exactly. This matters even more for cloud fixes. IAM role changes, security group rules, that kind of thing has to get verified in the actual environment where the exploit worked the first time.
In step three, the person retesting should not be the person who wrote the fix. A developer testing their own patch tends to walk the same path they coded against, and misses whatever they didn't think to check. The original pen tester is the strongest choice here, since they already hold the exploit chain in their head and know exactly which variants to try next.
In step four, every retest ends in one of three states, and they need to stay distinct. Closed means the original path and its reasonable variants are confirmed unreachable. Partially remediated means severity dropped but the surface isn't fully shut, and that gets tracked as open, full stop. Regression introduced means the fix broke something unrelated: a new finding opens, and the original finding stays open until both get resolved. Marking the original "closed" because a different bug appeared instead is a bookkeeping error that lets old vulnerabilities survive an audit.
Where continuous pull-request scanning fits into the retest workflow
Point-in-time retesting has a timing problem baked into it: a fix confirmed clean today can get undone by a commit shipped next week. A codebase that passed a clean retest in February can carry a fresh vulnerability by April, introduced by an update in between that nobody retested because nobody thought to.
Scanning code at the pull-request level closes part of that gap. Every change gets checked against the vulnerability classes the pen test already surfaced, before that change merges. A hardcoded secret dropped into a new commit gets caught before it's sitting in git history for good. An SSRF-prone pattern in a newly added endpoint gets flagged during code review instead of waiting to surface in next year's annual test.
What PR scanning can't do is confirm a complex, chained exploit path is actually closed. That still takes a person working the attack chain step by step, thinking about how one weak link connects to the next. PR scanning also doesn't produce the kind of attestation letter an auditor will accept as proof a retest happened.
The two aren't competing options, they're complementary layers. Automated scanning at the PR level catches regressions as they're introduced, continuously. Human-led retesting confirms the original findings are actually closed and produces the paper trail auditors want to see. Running only one of the two leaves a real gap.
What auditors need to see from a retest
Compliance frameworks don't accept "remediated" as a self-reported status. They want a record.
Under SOC 2, CC4.1 expects evidence of ongoing monitoring that checks whether controls are actually present and functioning, not just documented as present. CC4.2 goes further, requiring that deficiencies get evaluated and communicated so corrective action can happen. A finding marked "remediated" in the original report with nothing behind it doesn't satisfy either control.
ISO 27001:2022 requires a documented process for managing technical vulnerabilities, and the retest record is a required part of that chain of documentation.
HIPAA currently requires ongoing risk management under the Security Rule. A 2025 proposed update from HHS would make annual penetration testing an explicit requirement, but as of September 2026 that rule remains proposed, not final. Organizations shouldn't wait on that rule to take effect. Document retests now, under the existing risk management obligation.
Across all three frameworks, a usable retest record needs the same core pieces:
- A reference back to the original finding: finding ID, severity, description
- The date and environment where the retest happened
- The retester's name and credentials, since an OSCP or similar certification is what shows the retest was done by someone qualified to actually attempt exploitation, not someone who ran a scanner and called it a day
- The attack method used in the retest and what it produced
- A clear status: closed, partially remediated, or open with a revised severity
- Screenshots or logs showing the attack path is shut, functioning as proof-of-fix the same way the original report carried proof-of-concept
Skip any of these and the record reads as an assertion, not evidence, and auditors know the difference.
Evaluating whether your pen testing vendor's retest offering is real
Some retest offerings exist mostly on paper. A few signs give that away fast.
No stated retest policy at all is the clearest one. A vendor that never mentions retesting is a vendor optimized to deliver a report and move on, not one built around confirmed outcomes. Retesting done purely through an automated scanner is another: scanner output can't confirm a chained exploit path is closed, and it definitely can't verify a business logic fix. And a retest scoped only to the exact flagged endpoint, ignoring the surrounding attack surface, proves one payload is blocked while leaving every variant path wide open.
A short set of questions separates a real offering from a checkbox one:
Will the same tester who found the vulnerability be the one retesting the fix? Continuity matters, since that person already holds the exploit chain in their head.
What credentials does the retester actually hold? OSCP, OSWE, or GXPN point to hands-on exploitation skill beyond mere familiarity with a scanning tool.
What does the retest deliverable actually contain? It should include proof-of-fix evidence, screenshots, logs, something concrete, as opposed to a status flipped from red to green on a dashboard.
Is the retest bundled into the original engagement fee, or billed as an extra? A separately billed retest creates an incentive to close findings fast rather than close them right.
And will an updated attestation letter come out of it? That's the document that ends up in front of auditors and enterprise security reviewers, and without it, a clean retest result has nowhere to go.


