Authorization becomes difficult when more than one rule applies to the same request. One policy may grant a developer access to a project while another restricts access to confidential records. If the system has no explicit rule for combining those policies, a small implementation detail can decide whether access is granted.

That is a security problem because developers, administrators, and reviewers may believe different policies take precedence. A later refactor can then change effective access without anyone intending to change the security model.

The defensive goal is to make policy combination part of the authorization design. After reading this article, you should be able to identify policy conflicts, choose a combining rule deliberately, and test the effective decision rather than assuming that individual rules compose safely.

Authorization decides the effective permission, not each rule in isolation

Consider a document service with two policies:

Policy A: members of project-7 may read project-7 documents
Policy B: contractors may not read documents classified as restricted

A contractor who belongs to project-7 requests a restricted document. Both policies match:

project membership -> ALLOW
contractor restriction -> DENY

Neither rule is necessarily wrong. The unresolved question is what the system should do when both are true.

An authorization engine needs a policy combining rule: a defined method for turning multiple applicable policy results into one effective decision.

The important mental model is:

request + relevant security context
              |
              v
       matching policies
              |
              v
       combining rule
              |
              v
     effective decision

Only the final effective decision should control the protected operation. Looking at one matching grant is not enough when another applicable rule can restrict it.

State the conflict rule before writing the implementation

There is no universal combining rule that fits every authorization model. The correct rule depends on what the policies mean.

A common design is deny-overrides: if any applicable policy explicitly denies the request, the effective result is deny. This is useful when deny rules represent boundaries that grants must not cross, such as a suspension, legal hold, data classification restriction, or emergency account quarantine.

Conceptually:

applicable results = [ALLOW, ALLOW, DENY]
combining rule     = deny-overrides
effective result   = DENY

Another system may intentionally use a different model. For example, it may have one authoritative policy layer where a specific resource grant overrides a broader default restriction. That can be valid if the precedence is explicit, constrained, and understood by administrators.

The dangerous design is not choosing the “wrong” universal rule. It is having no stable rule at all.

If precedence depends on rule order, database row order, iteration order, or which service responds first, effective authorization can change for reasons unrelated to security intent.

Keep absence of a grant separate from an explicit deny

Many policy systems need at least three conceptual outcomes:

ALLOW       the policy positively grants this request
DENY        the policy positively rejects this request
NOT_APPLICABLE  the policy does not decide this request

NOT_APPLICABLE is different from DENY.

Suppose a policy grants finance staff access to invoices. A request from an engineer does not match that grant. The policy can return NOT_APPLICABLE; another policy may legitimately grant the engineer access to a particular test invoice.

An explicit deny means something stronger: this policy is intended to reject the request when its conditions match.

Conflating these outcomes makes policy composition hard to reason about. If every non-match becomes an explicit deny, independent grants may become impossible to combine. If every non-match is silently treated as permission, missing authorization coverage can become unintended access.

At the final authorization boundary, the system should still allow a protected operation only when the complete policy evaluation produces an affirmative effective grant. No matching grant, an evaluation failure, and an explicit deny should not accidentally become equivalent to allow.

Bind every decision to the same request facts

Policy combination is meaningful only if the policies are evaluating the same security question.

A useful authorization request identifies at least the relevant subject, action, resource, and security context:

subject  = user-42
action   = read
resource = document-918
context  = current project membership and account state

A cached policy result for read should not become a grant for update. A decision about one document should not automatically apply to another. A grant calculated before an account suspension may no longer be valid after that state changes.

When policy engines or services evaluate different fragments of context, define which facts are authoritative and how fresh they must be. Otherwise the combining rule can be perfectly deterministic while combining decisions made from inconsistent state.

This control does not solve stale authorization data by itself. It makes conflict resolution predictable under the facts supplied to the evaluator. Systems where revocation speed matters also need an appropriate strategy for refreshing or invalidating authorization state.

Separate policy priority from accidental evaluation order

Sometimes precedence really is part of the security model. For example, a system might define these layers:

1. emergency account restriction
2. resource-specific restriction
3. role or group grants
4. default deny

If so, represent that priority explicitly. Do not rely on the current order of an array or a sequence of if statements whose security meaning exists only in the programmer’s memory.

An explicit model lets reviewers ask useful questions:

  • Can a normal grant override an emergency restriction?
  • Can a resource owner override a classification rule?
  • Which layer is allowed to create exceptions?
  • What happens when two rules at the same priority disagree?

The answers should come from the authorization model, not from whichever branch executes last.

Be careful with exception mechanisms. An unrestricted “higher priority” grant can quietly become a universal bypass. If exceptions are necessary, constrain who can create them, what resources and actions they cover, how long they last when appropriate, and how they are reviewed.

Evaluate conflicts where the protected action is decided

Applications often collect authorization information from several places: identity claims, group membership, resource ownership, account status, organization policy, and local application rules.

Avoid letting each layer independently perform a side effect after seeing only its own grant. Instead, bring the relevant policy results into a decision point that can calculate the effective permission before the protected action runs.

A simplified evaluator might follow this shape:

results = evaluate_applicable_policies(request)

if evaluation_failed(results):
    return DENY

return combine_according_to_documented_rule(results)

This is teaching pseudocode, not a production authorization library. Real implementations also need to distinguish policy-evaluation failures from ordinary denials, preserve useful audit context, and ensure that only the effective decision reaches the protected operation.

Centralising the combining semantics does not require one global authorization service. A local library, service boundary, or policy engine can all work. The important property is that every component implementing the same policy model follows the same conflict semantics.

Test combinations, not only individual policies

A policy can pass all of its unit tests and still participate in an unsafe effective decision.

For the earlier project example, tests should cover the combinations that define the security boundary:

member + ordinary document       -> ALLOW
non-member + ordinary document   -> DENY
contractor + restricted document -> DENY
contractor + member + restricted -> DENY

The last case is the important conflict test. It proves how a grant and restriction compose.

Add tests when a new policy can overlap an existing one. Pay particular attention to identities that have several roles, resources that belong to several groups, temporary restrictions, suspended accounts, administrative exceptions, and transitions where security state changes.

Test the effective authorization API or boundary as well as individual rule functions. That is where policy combination becomes real access.

For sensitive systems, it can also be useful to log the effective result and stable identifiers for the policies or reasons that materially contributed to it. Do not log secrets or unnecessary personal data merely to make policy debugging easier.

Watch for common composition failures

One failure pattern is first match wins without a documented security reason. Adding or reordering a policy can then change access unexpectedly.

Another is any allow wins. That model is appropriate only when every matching grant is intentionally sufficient and no policy is meant to express a stronger restriction. Introducing a deny rule later may create the appearance of a control while an unrelated grant still overrides it.

A third failure is implementing the same policy set differently in several services. One service may use deny-overrides while another stops at the first allow. An identity can then receive different effective authority depending on which path reaches the resource.

Finally, avoid presenting administrators with policy controls whose precedence they cannot predict. If the interface allows someone to create both grants and restrictions, it should make the resulting effective access understandable before a high-impact change is relied upon.

Choose the simplest model that expresses the real boundary

Not every application needs a general policy engine. If a resource has one owner check and one role requirement, straightforward code with an explicit default deny may be easier to audit than a configurable rule language.

Policy-combination machinery becomes valuable when independent security concerns legitimately overlap: organization policy plus resource grants, account restrictions plus roles, data classification plus project membership, or temporary controls plus normal access.

Complexity should follow the threat model. More policy layers create more combinations to understand and test. Add them when they express a real security boundary, not because flexible authorization sounds more sophisticated.

The practical rule is simple: when more than one authorization policy can apply, define how conflicts resolve as part of the security model. Represent explicit deny, grant, non-applicability, and evaluation failure clearly; bind decisions to the same request facts; and test the effective result for overlapping cases. Predictable policy composition reduces the chance that an innocent rule change becomes an unintended privilege change.