Author policies
Cedar syntax
The policy language. Three statement types, six built-in operators, three implicit variables. That's it.
Nomos uses Cedar for policy. Cedar is small on purpose:
three statements (permit, forbid, unless), six operators, three variables
(principal, action, resource), one context bag.
Three statements
// 1. permit: only mechanism that allows
permit ( principal, action, resource );
// 2. forbid: overrides any permit
forbid ( principal, action == Action::"/github/repo/put_file", resource );
// 3. unless: inline negation
permit ( principal, action == Action::"/slack/message/post", resource )
unless { resource.channel == "C_RESTRICTED" };
Default: deny. If no permit matches, the request is denied.
The three variables
principal— the agent's identity (DID + role + delegation chain attrs).action— the command being authorized (e.g.Action::"/github/issue/list").resource— the target object (provider-specific fields likeowner,repo,channel,bucket).
The context bag
context is the runtime metadata the PDP adds:
context.cosigner—trueif a passkey-signed cosigner UCAN is attached.context.now—{ epoch, hour, day_of_week, iso }at request time.context.ip— caller IP (set ifEGRESS_TRUST_PROXY=true).context.purpose— the agent's stated reason (dynamic mode only).context.envelope_active—trueif an active grant envelope covers this call.
See Dynamic intent for the full context shape.
Operators
| Operator | Meaning |
|---|---|
| ==, != | equality |
| <, <=, >, >= | numeric / string compare |
| &&, \|\|, ! | boolean |
| in [ … ] | set membership |
| like "pat*ern" | shell-style glob on strings |
| has | attribute existence (resource has team_id) |
That's the whole list. No loops, no user-defined functions.
Common patterns
Read everything, write only specific repos
permit ( principal, action in [
Action::"/github/issue/list", Action::"/github/issue/get",
Action::"/github/repo/get_file", Action::"/github/repo/list_files"
], resource );
permit ( principal, action == Action::"/github/repo/put_file", resource )
when { resource.owner == "acme" && resource.repo in ["app", "infra"] };
Business-hours-only writes
permit ( principal, action == Action::"/slack/message/post", resource )
when { context.now.hour >= 9 && context.now.hour < 18 };
Cosigner-required deletes
forbid ( principal, action like "/*/delete*", resource )
when { !context.cosigner };
Cap delegation depth in a swarm
permit ( principal, action, resource )
when { principal.delegationDepth < 3 };
Pin to an envelope
permit ( principal, action, resource )
when { context.envelope_active == true };
Tooling
- Visual builder — graphic editor with round-trip validation.
- Monaco editor — Cedar syntax highlighting + autocomplete. Hover for action signatures.
pnpm cb policy validate <file>— local validation in CI.pnpm cb policy simulate <file> --request <file>— dry-run an authorize request.
Pitfalls
forbid is not the same as `unless`+
forbid is a top-level statement that overrides any permit. unless is an inline negation inside a permit's when clause. Use forbid when the rule applies regardless of which permit matched.Wildcards in actions+
Cedar matches action by exact identifier OR via `like`. Action::"/github/*" is NOT a wildcard — use `action like "/github/*"`.context.cosigner can be true even when you didn't expect it+
If a parent UCAN in a swarm chain has cosigner=true, it propagates to children. Use `principal.delegationDepth == 0 && context.cosigner == true` to require the cosigner at the leaf, not somewhere in the chain.