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:
- If any policy with
effect = denymatches → the request is denied. - Otherwise, if at least one policy with
effect = allowmatches → the request is allowed. - 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.
- Cedar —
permit+forbidrules;forbidtakes precedence. Used in Convera (sources/2026-02-05-aws-convera-verified-permissions-fine-grained-authorization). - Figma permissions DSL —
AllowFilePermissionsPolicyvsDenyFilePermissionsPolicy; evaluation algorithm explicitly bisects policies into deny / allow and short-circuits on any deny returningtrue. 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¶
- sources/2026-04-21-figma-how-we-built-a-custom-permissions-dsl
— Figma's explicit
DENY overrides ALLOWsemantics; the evaluation algorithm literally bisects onp.effect === DENY. - sources/2026-02-05-aws-convera-verified-permissions-fine-grained-authorization
— Cedar
permit/forbidrules under AVP, same effect resolution.