Skip to content

PATTERN Cited by 2 sources

Deny-overrides-allow

What it is

An effect-resolution rule in permissions engines where, given a set of policies that apply to a (principal, action, resource, context) request:

  1. If any policy with effect = deny matches → the request is denied.
  2. Otherwise, if at least one policy with effect = allow matches → the request is allowed.
  3. Otherwise → denied by default (implicit deny).

deny-overrides-allow makes deny policies act as guardrails — adding a new deny never expands any principal's access; adding a new allow never shrinks any principal's access.

Why it matters

  • Compositional safety. Policy authors can add a deny rule without auditing every existing allow — the worst case is that the deny takes effect more broadly than intended, shrinking access. False positives are discovered and fixed; false negatives (missed denies) are the security-critical case to avoid.
  • Deny-by-default floor. Absent an explicit allow, the resource is inaccessible.
  • Matches audit intuition. "Are there any rules that forbid this?" is the question regulators and auditors actually ask.

Canonical instances

  • AWS IAM — identity / resource / SCP policies compose under deny-overrides-allow, with additional SCP ceiling semantics. The foundational modern instance.
  • Cedarpermit + forbid rules; forbid takes precedence. Used in Convera (sources/2026-02-05-aws-convera-verified-permissions-fine-grained-authorization).
  • Figma permissions DSLAllowFilePermissionsPolicy vs DenyFilePermissionsPolicy; evaluation algorithm explicitly bisects policies into deny / allow and short-circuits on any deny returning true. Explicit quote from the 2026-04-21 article: "If applied, should the specified permissions be ALLOWED or DENIED (DENY overrides ALLOW)."

Evaluation shape

function hasPermission(resource, user, permissionName):
  policies         = ALL_POLICIES.filter(p => permissionName in p.permissions)
  [denies, allows] = policies.bisect(p => p.effect == DENY)

  if denies.any(p => ApplyEvaluator.evaluate(loaded, p.applyFilter)):
    return false                      // any deny = explicit deny

  return allows.any(p => ApplyEvaluator.evaluate(loaded, p.applyFilter))
                                      // any allow = allow; none = implicit deny

Notable: denies are evaluated first, and short-circuit evaluation skips remaining work once any deny is true.

Caveats

  • Over-broad denies can silently break product flows. A deny with a too-permissive filter shrinks access for paths the author didn't anticipate. Mitigation: static analysis on policy filters (patterns/policy-static-analysis-in-ci) + staged rollout.
  • "Explicit allow required" can surprise developers coming from implicit-allow systems. Document clearly.
  • Performance consideration. Evaluating all deny policies on every request before any allow can be optimized via short-circuit exit on the first deny — combined with patterns/progressive-data-loading, the engine can evaluate denies incrementally and exit before loading allow-only data.
  • More complex engines (Cedar, IAM) have additional rules (SCP ceilings, permission boundaries, resource-policy intersection) layered on top.

Seen in

Last updated · 200 distilled / 1,178 read