Skip to content

Policy rules

← Documentation home

A policy rule is the atomic unit of authorization in Sentinel. The engine holds an ordered list of rules; evaluation finds the first matching rule after sorting by priority and applies its effect (allow or deny).


Anatomy of a rule

Built with RuleBuilder:

typescript
allow()
  .id("admin-full-access")
  .roles("admin", "owner")
  .actions("invoice:approve", "invoice:read")
  .on("invoice")
  .when(ctx => ctx.resourceContext.status !== "locked")
  .priority(5)
  .describe("Admins can work with unlocked invoices")
  .build();
FieldMeaning
idStable identifier for tests and explain traces
effect"allow" or "deny"
rolesSpecific roles, or "*" for any role
actionsSpecific actions, "*", or patterns with *
resourcesSpecific resources, or "*"
conditionsOptional predicates — all must pass
priorityHigher runs first (default 0)
descriptionHuman-readable text in decisions and audits

Allow vs deny

Both are first-class. Use deny for broad safety rails and allow with higher priority for exceptions:

typescript
deny()
  .anyRole()
  .actions("user:impersonate")
  .on("user")
  .describe("Impersonation disabled by default")
  .build(),

allow()
  .roles("owner")
  .actions("user:impersonate")
  .on("user")
  .priority(10)
  .describe("Owners may impersonate for support")
  .build(),

Priority and deny resolution


Matching dimensions

A rule candidate must match on three axes before conditions run:

  1. Role — subject's resolved roles intersect rule roles (or rule is anyRole())
  2. Action — requested action equals listed action, matches wildcard regex, or rule is anyAction()
  3. Resource — requested resource is listed or rule is anyResource()

If any axis fails, the rule is skipped for that evaluation.

typescript
// Matches: admin role ∧ invoice:approve action ∧ invoice resource
allow()
  .roles("admin")
  .actions("invoice:approve")
  .on("invoice")
  .build();

engine.evaluate(viewer, "invoice:approve", "invoice").allowed; // false — role axis fails
engine.evaluate(admin, "invoice:read", "invoice").allowed;    // false — action axis fails
engine.evaluate(admin, "invoice:approve", "project").allowed;   // false — resource axis fails

Use explain() to see which axis failed for a given request.


Multiple conditions

Each .when() appends a condition. All must return true (AND semantics).

typescript
allow()
  .roles("member")
  .actions("invoice:update")
  .on("invoice")
  .when(ctx => ctx.subject.id === ctx.resourceContext.ownerId)
  .when(ctx => ctx.resourceContext.status !== "finalized")
  .build();

Rule lifecycle

typescript
engine.addRule(rule);
engine.addRules(ruleA, ruleB);
engine.removeRule("admin-full-access");
engine.getRules();  // readonly frozen copies
engine.clearRules();

Rules are frozen on add. The engine clears the evaluation cache when rules change.


Released under the MIT License.