Use this regression proof record
Recreate the original attack before trusting the fix. Use the same role, object, and state, then inspect the actual business effect. A 403 response can hide a queued task that still posts a payment or sends an email.
| Item | Check or owner | Evidence |
|---|---|---|
| Reproduction | Run original steps | Same role and object |
| Variant | Change one input | Check sibling endpoint |
| State | Inspect stored result | No forbidden change |
| Release | Capture version | Tie proof to deployed build |
Test and retest
Vary the route and timing after the first denial. For a tenant bug, try search, export, cache, and direct object lookup. For a money bug, compare provider, ledger, and event stream. Close only against a named build and a saved negative result.
Evidence to keep
Set up a clean baseline before the retest: original object, related object, authorized role, and denied role. After each request, compare the response and stored state. If a background job is involved, wait for its completion or failure and capture its ID. A patch can be correct in the API and wrong in an old worker version. Record deployed versions for both. The expected outcome should be written before the test, so a surprising partial success is not redefined as a pass.
One denied request and one allowed request on the same build make the result easier to trust.
Prove the side effect stopped
Synthetic finding: a user without refund rights sent a request and received a 403, but a background job still made the refund. The fix must stop the job, not just change the response. Reuse the original seeded payment and user role. Capture the API result, queue message, worker trace, provider sandbox calls, and ledger records. Expect no refund execution job, no provider call, and no balance change for the forbidden request. A security audit job can still record the denial.
Next, run a valid refund and make sure it still works once. Test the web route, mobile route, and direct API call because a UI-only guard does not protect the server. If the denied request still creates a refund execution item, mark the finding open even when the provider rejects it later. Record the deployed build ID and test time. OWASP’s authorization testing guidance supports testing role and data access across routes and releases.
Observe every path that can finish the action
Record API build, queue-worker build, support-service build, job ID, and the test observation window. Run the denied request, then advance or drain its sandbox retry path so later work cannot escape the check. A zero-effect result before a delayed worker runs is incomplete proof. Security audit jobs are allowed; a job capable of the forbidden refund is not.
For a data leak, test the denied user before and after an allowed user warms the cache. Include cache key, tenant context, export links, and file access expiry. A correctly denied direct route still fails the finding if a cached response or old export lets the denied user read the record. Remove or invalidate unsafe cached copies and rerun the same access sequence.
Use one small result table with case ID, expected effect, observed effect, component version, and proof link. Mark blocked paths as untested with the blocker and owner. Keep a passing allowed-user case next to every denied case. If one sibling route remains unsafe, retain partial closure and extend the fix to that path. The evidence covers the named tested release and paths.