A BOLA or IDOR fix is correct across an API only when the authorization invariant is enforced wherever the protected object can be read, changed, linked, exported or processed—not merely at the URL named in the report. Start from ownership and permitted relationships, inventory every operation that resolves the object, implement the check at a durable policy boundary, and retest an authorization matrix across endpoints, roles and tenants.
Define the architecture question before testing
Identify what the vulnerable endpoint proved: missing ownership check, trusted tenant parameter, unscoped repository lookup, confused deputy, stale permission or inconsistent policy helper. Then search for every consumer of that failure pattern. Changing one controller response is closure only if that controller was the complete enforcement boundary.
Write the expected security invariant and the prohibited outcome in language that engineering, security and operations interpret the same way. Name the identity, trust transition, protected asset and authoritative state. That statement becomes the basis for scope, evidence and retesting.
Build the minimum evidence pack
- A current component and trust-boundary diagram for the environment under test.
- Identity, credential, network and data-flow details relevant to the decision.
- Representative accounts, workloads and synthetic records with known ownership.
- Logs or traces that follow the action through every material control point.
- Explicit safety limits, stop conditions, owners and restoration steps.
Keep the original proof, object and account relationships, endpoint and resolver inventory, route-to-handler mapping, authorization helpers, repository queries, background consumers, export paths and test accounts for same-role and cross-tenant comparisons. Include the final business state, not only HTTP responses.
Write the invariant before changing code
Developers need a rule that explains why access is allowed. “User is logged in” or “ID exists” is not object authorization.
- Define owner, collaborator, administrator and support relationships.
- List actions separately: view, update, delete, share, export and execute.
- Include tenant, lifecycle state and delegated authority.
- Decide where platform roles may cross tenant boundaries.
- Specify fail-closed behaviour for missing context.
Turn the rule into examples with named actors and objects. Product and security owners should approve exceptions before implementation.
Inventory every object resolution path
The same object can appear in list, detail, nested, bulk, search, GraphQL, export and asynchronous operations. Each path must bind the object to authorized context.
- Search routes, resolvers and handlers using the object ID.
- Trace parent-child and indirect relationship lookups.
- Include bulk arrays, filters and file or export jobs.
- Find administrative and internal service operations.
- Check cached, indexed and soft-deleted representations.
Create a route-to-policy table. A code search for the reported path alone misses alternate names and downstream consumers.
Fix authorization where authoritative context exists
Prefer a domain policy or tenant-aware repository that receives actor, action and object context. Middleware can enforce coarse scope but often cannot know the final object relationship.
- Bind tenant and object in one authoritative query.
- Require the policy call before mutation or side effect.
- Remove client-controlled ownership or tenant trust.
- Ensure services revalidate gateway-provided context.
- Make missing actor or policy result deny by default.
Record the enforcement point and all consumers migrated to it. Avoid scattered boolean checks that drift with new routes.
Prevent information leaks on denied paths
Status, timing, validation order and error bodies can reveal foreign object existence even when content is not returned.
- Compare known foreign, nonexistent and malformed IDs.
- Check list counts, uniqueness errors and relationship validation.
- Test update and delete side effects after a denial.
- Inspect logs and notifications for foreign data.
- Ensure GraphQL partial responses do not expose protected fields.
Consistent external behaviour is useful, but internal logs should retain enough context for investigation without leaking data to the caller.
Build the authorization regression matrix
Use at least two accounts with the same role and accounts in two tenants. Same-role pairs prove horizontal access controls; cross-tenant pairs prove isolation; privileged pairs prove vertical policy.
- Run each action against owned, peer-owned and foreign-tenant objects.
- Cover direct IDs, nested routes and bulk operations.
- Repeat after role removal, transfer and object archival.
- Test both allowed and denied states.
- Verify caches and asynchronous jobs preserve the decision.
Store actor, tenant, object owner, action, expected decision and actual result. Generate cases from the invariant so new endpoints inherit coverage.
Retest the exploit and the shared pattern
The original request proves one path. Pattern retesting establishes whether the root cause is corrected across the API.
- Repeat the exact original proof.
- Vary method, route version and content type.
- Test sibling endpoints using the same repository or policy helper.
- Inspect final state for hidden asynchronous effects.
- Confirm legitimate collaboration and admin workflows still work.
Closure should state original path fixed, related paths sampled, shared control version and any unmigrated exception. A changed status code without state verification is insufficient.
Keep the fix from regressing
Authorization is a product rule that evolves with roles, sharing and tenant features. Make policy change visible and testable in delivery pipelines.
- Add negative unit and integration cases around the policy.
- Require threat review for new object relationships.
- Lint or review direct unscoped repository access.
- Track policy-library adoption and version drift.
- Include authorization cases in release acceptance criteria.
Measure newly introduced bypasses and exceptions, not only the count of past findings. Recurrence indicates the enforcement design is still too easy to evade.
Execute as controlled hypotheses
- Establish the legitimate baseline and capture authoritative state.
- Change one trust variable—identity, route, scope, object, time or environment.
- Observe the decision at each layer rather than relying only on the response.
- Stop at the minimum proof that demonstrates or rejects the hypothesis.
- Restore test state and record residual uncertainty or blocked coverage.
Reproduce with synthetic accounts and canary objects. Preserve the vulnerable proof before changing code, then develop the shared fix behind controlled tests. Avoid using real customer identifiers and verify jobs or notifications cannot create unintended side effects during regression.
Report the architectural consequence
Explain the failed invariant, common root cause, affected paths, chosen enforcement layer, migrated consumers and retest evidence. Keep each original finding traceable even when one architecture change resolves several instances.
Separate observed evidence from inference. State prerequisites, repeatability, blast radius and the shared component responsible for the decision. When multiple findings have one architectural cause, keep the individual proofs but group the remediation around the common control.
Required closure evidence
- The original proof no longer succeeds.
- A legitimate workflow still succeeds under the intended identity and route.
- Alternate consumers of the same pattern enforce the same invariant.
- Telemetry records both allowed and denied decisions with useful context.
- The architecture record and threat assumptions reflect the new control.
Remediate at the durable control point
Centralize object and tenant authorization at a layer with authoritative relationships, provide safe repository and policy interfaces, remove trusted client scope, and add generated negative tests. Use gateway or middleware checks as defence in depth, not a substitute for domain policy.
Retest the invariant, not just the payload
Repeat the original BOLA proof, then test owned, peer and foreign objects across every action class and a representative set of routes. Inspect authoritative state, caches, files and background jobs. Confirm legitimate sharing and administration remain correct.
Use the NIST Technical Guide to Information Security Testing and Assessment for assessment planning and evidence discipline, and the OWASP Web Security Testing Guide for relevant application techniques. Adapt both to the system-specific trust decision described here.
The architecture decision
Do not close an IDOR because one endpoint returns 403. Close it when the object authorization invariant is explicit, every resolution path uses the durable enforcement point, the identity-pair matrix passes and independent retesting confirms both denied and allowed behaviour.
Ask WIMD to validate API-wide authorization remediation across endpoints, tenants, object relationships and downstream effects.
