Case Study · Methodology

Retesting a Fixed Program — Verifying the Patch, Finding the Gap

Aug 2026 · 7 min read · Imperonlabs Research

Target class: SaaS platform with mobile clients, returning for a retest after a prior engagement. Details generalized.

A retest is not a formality. It's a chance to confirm fixes actually hold and to notice what the fix pattern tells you about the bugs that weren't in scope last time. This engagement did both — every prior finding was genuinely closed, and the way they were closed pointed straight at a class of issue nobody had checked.

The prior findings, verified closed

The client had remediated a previous round. We re-ran each to confirm — never assuming a "fixed" label:

  • Insecure local storage → keystore. Session material previously written to plain app preferences was now in the platform secure store. Confirmed: the old cleartext location was empty; tokens lived in the encrypted store.
  • IDOR on delete → 401. An endpoint that had let one user delete another's object now returned 401 and required the correct role. We confirmed with two accounts we controlled: the cross-user delete was rejected.
  • Broken message authorization → enforced. A manager-messaging feature that had leaked across boundaries now enforced role checks. Confirmed denied from the wrong role.

Three for three. The patches were real, not cosmetic. That alone is worth stating clearly in a report — clients rarely get unambiguous confirmation that their fixes worked.

What the fix pattern revealed

Here's the part that matters. Every one of those fixes shared a shape: they added an authorization check to a state-changing action — a delete, a message send, a write. The remediation team had, understandably, gone through the write operations and locked them down.

That is exactly the pattern that leaves read paths unguarded.

So we tested reads with the same low-privilege account, systematically — every collection and object endpoint the app touched. And there it was: authorization enforced on writes, absent on reads. The lowest-privilege user could enumerate records they had no business seeing (covered in detail in our directory-disclosure case study). The retest scope was "verify the fixes," but the fixes themselves were a map to the next finding.

The methodology

A retest we run in three passes:

  1. Confirm each prior fix with controlled accounts and the smallest reproduction — and state plainly whether it holds. A verified "closed" is a deliverable.
  2. Test the fix's neighborhood. A patch on DELETE /x invites you to test GET /x, PUT /x, and the collection GET /x-list. Fixes are local; the underlying flaw class rarely is.
  3. Read the remediation's shape. If every fix is a write-side check, reads are your next hypothesis. If every fix is input validation on one parser, the other parsers are your next hypothesis. The way a team fixes tells you how they think — and where they didn't look.

The lesson

"Fixed" is a claim to verify, not a fact to accept — and the manner of a fix is intelligence. Confirm the patches with rigor, then let their pattern point you at the class of bug the patch never touched. On this engagement, the write-side fixes were solid work; the read-side gap they left was the highest-severity finding of the retest.