Fixing an authorization bug in one controller blocks one demonstrated path; fixing the authorization design defines a reusable policy, places it where authoritative identity and object context exist, migrates every consumer and prevents new routes from bypassing it. A controller patch is appropriate for immediate containment, but closure requires proving whether the failure is local or systemic and testing the shared pattern.

Define the architecture question before testing

Ask why the controller could access the object without the correct decision. Was a policy call missing, tenant context trusted from input, repository unscoped, gateway assertion over-trusted, or business relationship unavailable at the enforcement point? That root cause determines whether local code is enough.

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, route and handler map, shared middleware and policy libraries, repository queries, identity and tenant propagation, sibling endpoints and jobs, intended role-object rules, existing tests, immediate containment, migration inventory and retest criteria.

Use a controller patch for immediate containment

A narrow deny or ownership check can reduce urgent exposure while the durable change is designed.

  • Block the known actor-object path.
  • Preserve legitimate owner behaviour.
  • Add a regression for the original proof.
  • Deploy and verify the exact artifact.
  • Mark the patch as local or temporary.

Do not describe containment as pattern closure unless inventory proves the controller is the sole consumer.

Identify the failed invariant

Write authorization as actor, tenant, object relationship, action and state. This separates business policy from route syntax.

  • Define allowed owners and collaborators.
  • List privileged and support exceptions.
  • Separate read, write, share and export.
  • Include lifecycle and revocation.
  • Specify missing-context behaviour.

Product and security owners should approve the rule. Developers can then test implementation against one shared oracle.

Choose the authoritative enforcement layer

Middleware sees identity and route; the domain or repository often knows object ownership and state. Put each decision where its required context is trustworthy.

  • Verify actor and tenant are server-derived.
  • Bind object and scope in one data query.
  • Require domain policy before side effects.
  • Revalidate downstream and async consumers.
  • Fail closed when context is absent.

Document which responsibility belongs at gateway, service, domain and data layers. Duplication should be deliberate defence, not inconsistent copies.

Inventory and migrate every consumer

Search beyond controllers for GraphQL resolvers, bulk operations, exports, jobs, admin tools and old versions.

  • Map routes to policy and repository.
  • Find direct unscoped object access.
  • Include queues and scheduled jobs.
  • Check mobile, partner and legacy APIs.
  • Track migration and exception owners.

Keep a consumer inventory with adoption state. Unknown or unmigrated paths remain residual risk.

Build the policy as a supported paved road

A shared helper reduces recurrence only when its interface is clear, context is complete and adoption is easier than bypass.

  • Expose actor, action and object policy calls.
  • Provide tenant-aware repository methods.
  • Add secure examples and migration tooling.
  • Version and monitor library adoption.
  • Deprecate unsafe direct access.

Measure usage and exceptions. Centralization without governance creates one more optional library.

Add architectural guardrails

Prevent new endpoints from repeating the bug through tests, review rules and pipeline signals.

  • Generate role-object negative tests.
  • Flag direct unscoped repositories.
  • Require policy declaration for new actions.
  • Test missing identity and tenant context.
  • Review exceptions with expiry.

Tune guardrails to the invariant and framework. A noisy textual rule will be disabled and provide false confidence.

Retest local proof and systemic pattern

Closure needs both the original endpoint and representative consumers of the new design.

  • Repeat the original exploit.
  • Test peer and foreign-tenant objects.
  • Sample alternate route and async consumer.
  • Verify legitimate sharing and admin.
  • Inspect authoritative state.

Report endpoint status, pattern coverage, migrated control version and remaining exceptions separately.

Execute as controlled hypotheses

  1. Establish the legitimate baseline and capture authoritative state.
  2. Change one trust variable—identity, route, scope, object, time or environment.
  3. Observe the decision at each layer rather than relying only on the response.
  4. Stop at the minimum proof that demonstrates or rejects the hypothesis.
  5. Restore test state and record residual uncertainty or blocked coverage.

Contain urgent exposure first, then hold a short architecture review with domain, platform and security owners. Avoid blocking release on a vague rewrite; define phased migration, compensating controls and measurable closure.

Report the architectural consequence

State local proof, root cause, failed invariant, chosen enforcement layer, consumer inventory, rollout and retest evidence. Preserve finding-level traceability even when one design change resolves multiple issues.

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

Use tenant-aware data access and domain authorization, standardize safe policy interfaces, remove client-trusted scope and add pipeline guardrails. Keep edge checks as defence in depth where useful.

Retest the invariant, not just the payload

Verify the original controller, then sample every consumer class using the shared control. Test allowed and denied relationships, lifecycle changes and asynchronous effects against the deployed build.

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

Patch the controller to contain risk; fix the authorization design when the failed invariant can recur. The evidence—not a preference for centralization—determines the required remediation depth.

Ask WIMD to validate authorization root-cause remediation from the reported controller through every shared consumer.