Skip to main content

Policies overview

When ABAC policies are needed beyond roles, how targets, rules, conditions, and combining algorithms layer, and where each topic is documented

A policy is a named list of rules with a combining algorithm. Roles answer "which subject may do which action on which resource"; policies answer everything that depends on attributes, time, network, or relationships between the subject and the resource.

When to use / When not to use

Roles cover the common case: "editors can update posts." The diagram shows which requirements fit roles and which need a policy.

Loading diagram...

Use a policy for:

  • Time-based restrictions - deny writes on weekends or outside business hours (A1).
  • IP / geo-fencing - allow access only from trusted networks (A2).
  • Cross-attribute checks - allow updates only when subject and resource share a department (A3, see $-variable references).
  • Dynamic deny rules - block specific subjects or suspicious behaviour without changing role assignments (A4).
  • Maintenance mode - deny all writes globally when a feature flag is on (A5).
  • Attribute-gated grants - only senior staff may publish (A6).

Do not use a standalone policy when the condition belongs to a single role's permission. Use grantWhen() on the role instead; it compiles into the same rule shape.

Roles and policies share one pipeline: rolesToPolicy() turns roles into a synthetic policy (id: '__rbac__', algorithm: 'allow-overrides') that the engine evaluates next to yours. By default the engine combines policies with policyCombine: 'and' - a deny from any applicable policy is final. See cross-policy combining.

How the layers filter a request

A request passes through concentric filters. Each layer decides whether the next one runs.

Loading diagram...

  • T is the target check: actions, resources, roles.
  • SHAPE is the rule action/resource match described in rule matching. A policy with no rule for this action/resource pair is NotApplicable, exactly like a target miss.
  • COND evaluates conditions and nested groups.
  • COMB is the policy's combining algorithm. When the policy applied but no rule matched, defaultEffect is folded in (DEF) - this is the one place a policy "votes" without a matching rule.
  • CROSS is the engine-level merge described in evaluation pipeline and cross-policy combining.

Put broad preconditions in targets, rule-local matching in actions/resources, and contextual logic in conditions.

Reading order

PageCovers
Building policiesdefinePolicy(), defineRule(), when(), what build() validates and throws
RulesRule anatomy: effect, actions, resources, priority, forScope, metadata
TargetsPre-filter a policy by action, resource, or role; NotApplicable semantics
ConditionsThe When builder, every operator with edge semantics, field resolution
Nesting and/or/notand(), or(), not(), whenAny(), depth limit, empty groups
Combining algorithmsdeny-overrides, allow-overrides, first-match, highest-priority with decision diagrams
$-variable referencesCompare two fields on the same request; resolution rules
Layered exampleRoles plus three policies, wired to an engine, with a walkthrough

Quick example

import { definePolicy } from '@gentleduck/iam'

const weekendDeny = definePolicy('deny-weekends')
  .name('Deny on Weekends')
  .desc('Block all write operations on weekends')
  .version(1)
  .algorithm('deny-overrides')
  .rule('r-deny-weekends', (r) =>
    r
      .deny()
      .on('create', 'update', 'delete')
      .of('*')
      .when((w) => w.env('dayOfWeek', 'in', [0, 6])),
  )
  .build()

This policy:

  • Uses deny-overrides - any matching deny rule makes the policy deny.
  • Only has a rule for writes, so a read request finds no rule shape and the policy is NotApplicable for it.
  • Denies when environment.dayOfWeek is Sunday (0) or Saturday (6). The value [0, 6] is a literal array; see conditions for how in treats arrays.

FAQ

Why use policy targets instead of only rule conditions?

Targets are a fast pre-filter. They let the engine skip a whole policy before it inspects rules or conditions, and they make a policy NotApplicable rather than a defaultEffect vote when they miss. See targets.

Can a policy deny something that a role allows?

Yes. Roles compile into an allow-only policy (allow-overrides), but your policies are evaluated alongside it. Under the default policyCombine: 'and', a deny from any applicable policy wins. See cross-policy combining.

grantWhen() on a role or a standalone policy rule?

Use grantWhen() when the condition belongs to a single role's permission. Use a standalone policy when the rule spans many roles, acts as a global deny layer, or needs its own combining algorithm and lifecycle. See conditional permissions.

How do targets, rule action/resource filters, and conditions layer?

Concentric filters, as in the diagram above: targets decide whether the policy runs, rule actions/resources decide which rules are candidates, conditions are the final contextual check on those candidates.

See also