Policies and Conditions

Policies let you express authorization rules that are broader or more conditional than a direct role permission.

Use policies when:

  • The rule depends on runtime context.
  • The same rule should attach to multiple entities.
  • You want to author statements in Cedar and compile them into the RBAC engine.
  • You need linting, history, rollback, or delete impact around policy changes.

JSON statements

Create a policy with JSON statements:

POST /policies
Content-Type: application/json

{
  "id": "policy-own-profile",
  "statements": [
    {
      "effect": "can",
      "actions": ["read", "write"],
      "resources": ["profile/*"],
      "when": {
        "resource.id": { "eq": { "$ref": "principal.id" } }
      }
    }
  ]
}

Attach the policy to a role:

POST /policies/policy-document-owner/attachments
Content-Type: application/json

{
  "entityType": "ROLE",
  "entityId": "role-member"
}

Conditions

Conditions evaluate against facts with a known source (full table in docs/platform/CONTEXT-KEYS.md):

  • Engine facts — resource.type, resource.id, resource.scope, principal.id, subject.tenant, and the server clock time.hour / time.dayOfWeek (UTC, 0 = Sunday).
  • Request facts — request.mfa, request.mfaAge, request.ip, derived by the server from the verified token and the deployment's client-IP source. Never settable by a caller.
  • Resource attributes — resource.<attr> for attributes your catalog manifest declares on the type (required or optional, typed string, number, boolean, string[] or principalRef). Your backend supplies them as an attestation on the broker or attested check routes; end users never can.
  • Trusted context — context.<name> for products that set "trustedContext": true and declare a typed contextSchema (e.g. clientIp of type ip); accepted only from service credentials.

Read an optional attribute only behind an exists guard in the same $and (a required one needs no guard) — writes that read an optional attribute unguarded are refused:

{ "$and": [ { "resource.pinned": { "exists": true } }, { "resource.pinned": { "eq": true } } ] }

An owner-only rule reads an attested attribute:

{ "resource.createdBy": { "eq": "${subject.id}" } }

Supported condition patterns include:

  • Equality: eq, notEq
  • Lists: in, contains, containsAll, containsAny
  • Comparison: gt, gte, lt, lte
  • Strings and patterns: like
  • Presence: exists
  • Network checks: cidr (IPv4 and IPv6; an address never matches a range of the other family)
  • Logic: $and, $or, $not
  • Runtime references: $ref

Office hours on engine facts only:

{
  "$and": [
    { "time.dayOfWeek": { "in": [1, 2, 3, 4, 5] } },
    { "time.hour": { "gte": 8 } },
    { "time.hour": { "lt": 18 } }
  ]
}

A check that reaches a rule needing a fact it doesn't have is denied without evaluating it (attestation_required, request_fact_unavailable, context_required, with the missing keys). A malformed condition never allows and always denies.

Attestations are the entity snapshot. An omitted optional attribute (or null) is absent; every required attribute must be present. In JavaScript, undefined is dropped by JSON.stringify, so an undefined value is an omitted attribute, not null. Declare security-relevant facts (classification, author) as required.

Rollout order. Deploy the attesters that send a new required attribute before catalog apply makes it required; otherwise their checks fail validation.

Cedar-authored policies

Policies can also be created with Cedar source:

{
  "id": "policy-expense-approval",
  "cedar": "permit (principal, action == Action::\"approve\", resource == Resource::\"expenses\") when { resource.amount < 5000 };"
}

SaaSLayer Permissions compiles Cedar into JSON statements at write time. The hot permission-check path still uses the materialized permission document.

Use Cedar when your team prefers a policy language. Use JSON statements when your product needs direct control over the generated rules.

Policy lifecycle

Policy endpoints support:

  • Create, list, update, and delete
  • History
  • Rollback
  • Attachments
  • Linting
  • Delete impact
  • Dry-run behavior where supported

Before changing a widely attached policy, inspect its attachments and preview impact. A single policy update can affect many principals.

Practical guidance

  • Keep simple permissions on roles.
  • Put conditional logic in policies.
  • Prefer positive rules plus a small number of hard denies.
  • Declare the attributes your rules read in the catalog manifest, and attest them from your backend.
  • Test policy behavior with both allowed and denied examples.
  • Use explain mode during policy rollout.