Skip to content

Permission guards

Quorum enforces a layered permission model on every approval action. When a user attempts to approve or reject a request, each of these checks is evaluated in order:

Guard Scope Description
Self-approval prevention Built-in The maker of a request cannot approve their own request. Always enforced.
Eligible reviewers Per-request An optional allowlist of user IDs set when creating the request. If provided, only those users can act.
Allowed checker roles Per-stage Each approval stage can restrict which roles are permitted. The checker must hold at least one matching role.
Allowed permissions Per-stage Each approval stage can restrict which permissions are required. The checker must hold at least one matching permission.
Authorization mode Per-stage When both allowed_checker_roles and allowed_permissions are set, controls whether either suffices ("any") or both are required ("all").
Reviewer scope Per-stage scope rules require the checker’s identity attribute to equal the maker’s attribute of the same name, or a value at a payload_path. See Reviewer scoping.
Dynamic authorization URL Per-policy An external HTTP endpoint called before each approval. The endpoint receives the full request context and returns {"allowed": true/false}. Use this for custom business logic your domain requires.
Duplicate vote prevention Per-stage A checker can only vote once per stage. They may vote on different stages of the same request.
Request fingerprinting Per-policy Configurable identity_fields extracted from the payload and hashed. Prevents duplicate pending requests for the same resource.
Bypass rules Per-policy bypass_rules describe requests that need no review. A matching request is approved on creation and audited as bypassed. See Bypass rules.

Approve or reject arrives

  1. Self-approval checkalways
    403 when the maker cannot approve their own request
  2. Eligible reviewersif configured
    403 when the checker is not in the request’s allowlist
  3. Allowed checker rolesif set on the stage
    403 when the checker lacks a required role
  4. Allowed permissionsif set on the stage
    403 when the checker lacks a required permission
  5. Reviewer scopeif set on the stage
    403 when the checker is outside the stage’s scope
  6. Dynamic authorization URLif configured
    403 when your endpoint returned allowed: false
  7. One vote per stagealways
    409 when the checker already voted on this stage

Vote recorded

A rejected guard returns 403. The viewer_can_act field on a request reflects the same checks for the current caller, which is how the widgets know whether to render the approve and reject buttons.

When a policy has a dynamic_authorization_url, Quorum POSTs the full request context to your endpoint before allowing the approval: request ID, type, checker and maker IDs, their identity attributes (maker_attributes and checker_attributes), and the payload. Your endpoint responds with {"allowed": true} or {"allowed": false, "reason": "..."}.

This lets you implement arbitrary business rules without modifying Quorum: department separation, conflict-of-interest checks, rate limiting, or a lookup in a system Quorum knows nothing about.

Quorum is built by .