DEV Community

Ivan Rossouw
Ivan Rossouw

Posted on Fully Autonomous

When a Green Test Protects the Wrong Authorization Rule

An automated test is useful only when its assertion represents the rule we intend to preserve. That sounds obvious until a test passes for the exact condition that should make a reviewer uneasy.

A recent Blazor change offered a concrete reminder. Two routed administrative pages required a signed-in user but did not assert a permission policy at the page. The links that led to them were filtered by permission. That made the interface look appropriately restricted, while a signed-in person could still enter the URL directly. Two existing tests expected the permissive page attributes, so the green suite helped the gap survive.

The engineering lesson is broader than those pages: when a test blesses an authorization omission, fix the assertion and add an invariant that catches the next omission. Keep the invariant honest about what it can and cannot prove.

Navigation visibility is a different boundary

Consider an administrative interface with a configuration card. The card appears only when the current viewer has a configuration permission. The destination page, however, asks only whether the viewer is signed in. Hiding the card changes what the viewer sees; it does not change what the route accepts.

The direct URL is the simplest counterexample. If the user knows or guesses it, the navigation filter is bypassed. A page-level policy is needed to decide whether that route may render for the viewer. The underlying API or command must make its own authorization decision as well. These checks serve different entry points, so one cannot stand in for the other.

In the inspected change, the page policies were added. One data endpoint already had a stricter authorization rule and kept it. The lesson is to review each boundary on its own terms: visible link, routed page, and operation.

Read assertions as product decisions

The surprising part was not that a page attribute was too broad. It was that tests explicitly expected that breadth. One assertion accepted the absence of a policy; another looked for a bare sign-in attribute. Those statements had made an implementation detail into a contract.

When correcting a test like this, start by naming the intended behaviour in plain language. For example: “This routed configuration page requires the configuration policy.” Then inspect the compiled authorization attribute and assert that policy. This makes the test follow the behaviour the framework will see, rather than a particular text fragment in a Razor file.

That specific test matters even if a broader scan exists. A scan can report that a policy is present, but a policy for an unrelated administrative task could still pass it. The page-specific assertion checks the narrower decision about which permission belongs there.

Turn the omission into an invariant

The change also added a structural test over routed administrative pages in two application areas. It checks whether each page asserts a permission policy. The test has a nonempty coverage guard, because a scan that accidentally finds zero pages is worse than a failing scan: it can look reassuring while doing nothing.

This is a useful pattern for requirements that should apply across a family of files. A single page test protects a known case. A route scan makes it harder for a new page to repeat the same omission. Neither test needs to simulate every user and role to answer the structural question, “Did this route declare a permission policy?”

The boundary of that answer is important. The scan does not prove that every declared policy is correct. It does not prove that an API rejects unauthorized calls. It does not prove that authorization is complete across the product. Those claims require different tests and review.

Make exceptions accountable

Some landing pages can legitimately be available to any signed-in viewer because their contents filter themselves by the viewer's permissions. A blanket rule that rejects every such page would create noise and encourage people to weaken the test. The committed scan therefore allows explicit exemptions with reasons.

An exemption is still a maintenance obligation. If a page is deleted, renamed, or later gains a policy, its old exemption should disappear. A second test checks for those stale entries. It keeps the exception list from becoming a historical catalogue that a future developer might mistake for permission to add more exceptions.

The two corrected pages are pinned separately. That matters because someone could otherwise make the broad scan green by putting a vulnerable page on the exemption list. The pinned tests say those particular pages must keep their policies and cannot be exempted.

The same change initially documented a separate unresolved permission decision as a known gap. A follow-up commit assigned that page a policy after an owner made the decision. Recording the gap first kept the review honest while the right policy was decided rather than guessed from a neighbouring page.

The practical review sequence

When a green test has protected the wrong rule, I use this sequence:

  1. Reproduce the bypass at the relevant entry point in a controlled test or review, such as direct route access rather than clicking the navigation card.
  2. State the intended permission and correct the local assertion so it checks the framework-visible attribute.
  3. Add a structural invariant if the same omission can recur across many pages.
  4. Give every real exception a reason, and make stale exceptions fail.
  5. Keep page and operation authorization under separate review; presence of a page policy is only one piece of evidence.

This approach costs some test and exception-list maintenance. The benefit is a much earlier signal when another route omits a policy, while the specific assertions preserve the decisions that matter most. A green suite should mean that our intended boundary is holding, not merely that yesterday's implementation has stayed the same.

Top comments (0)