Policy
AWS Cedar authorization. Actions, scopes, actor identity, and the tokens-but-no-policy trap.
Omnigraph integrates AWS Cedar for
attribute-based access control. Policies live in a YAML file referenced by
policy.file in omnigraph.yaml; the engine consults the compiled policy
at the head of every mutating write.
Policy is a property of the engine, not the HTTP transport. Whether the
write originates from omnigraph-server, the CLI, or an embedded SDK
consumer, the same Cedar decision fires.
The eight actions
| Action | Scope kind | Used by |
|---|---|---|
read | branch_scope | Queries, snapshots, branch/commit list |
export | branch_scope | NDJSON export |
change | branch_scope | Mutations |
schema_apply | target_branch_scope | omnigraph schema apply |
branch_create | target_branch_scope | Branch creation |
branch_delete | target_branch_scope | Branch deletion |
branch_merge | target_branch_scope (with branch transition) | omnigraph branch merge |
admin | — | Reserved for future policy-management surfaces. No call site today. |
Each rule must use exactly one of branch_scope or target_branch_scope.
protected_branches is a named list that scope predicates can filter on
(any | protected | unprotected).
Configuration
policy:
file: ./policy.yaml # Cedar rules + groups
tests: ./policy.tests.yaml # declarative test cases
cli:
actor: act-andrew # default actor for CLI direct-engine writesActor identity (signed-claim-only)
For HTTP writes, the actor used for every policy decision comes from the matched bearer token. Clients cannot supply it via request header, query parameter, or body field. The server resolves the token at the auth middleware boundary and overwrites whatever the handler may have placed in the policy request.
This is intentional. Trusting client-supplied identity for authorization is
"asking the attacker if they're an admin." A regression test in the engine
asserts the contract: a request with Authorization: Bearer <token-for-actor-A>
plus X-Actor-Id: actor-B always evaluates as actor A.
For CLI direct-engine writes, the actor comes from the top-level --as <ACTOR> flag, then cli.actor in omnigraph.yaml. With policy configured
and neither set, the engine's footgun guard denies the write rather than
silently bypassing.
omnigraph --as act-alice change --uri ./graph.omni \
--query mutations.gq \
--name complete_task \
--params '{"slug": "auth"}'Remote HTTP writes ignore both knobs.
Tooling
The omnigraph policy subcommand covers the local
developer loop:
omnigraph policy validate. Parse the policy and exit non-zero on errors.omnigraph policy test. Exercise declarative cases inpolicy.tests.yaml.omnigraph policy explain --actor … --action …. Explain a single decision.
Server runtime states
The HTTP server classifies its startup configuration into one of three states based on whether bearer tokens are configured and whether a policy file is set:
| State | Tokens | Policy file | Behavior |
|---|---|---|---|
| Open | none | none | Every request permitted. Refuses to start without --unauthenticated (or OMNIGRAPH_UNAUTHENTICATED=1). The operator must explicitly opt in. |
| DefaultDeny | yes | none | Every authenticated request for an action other than read is rejected with HTTP 403. Closes the "tokens but forgot the policy file" trap. |
| PolicyEnabled | any | yes | Cedar evaluates every request against the configured policy. |
The DefaultDeny state is the safety net for the most common
authorization mistake: configuring tokens and forgetting to point at a
policy file. Without it, that mistake ships the illusion of protection.
Layered enforcement
There are two enforcement points:
| Layer | Question | Where it fires |
|---|---|---|
| Engine-layer (coarse) | Can this actor invoke this action against this branch / branch-transition? | Omnigraph::enforce(action, scope, actor) at the head of every _as writer. One Cedar decision per call. |
| Query-layer (fine) | For the rows / types this action touches, which can the actor see or modify? | Per-row predicates pushed into DataFusion at plan time. Not yet implemented. |
The engine-layer gate keeps ResourceScope at branch granularity (Graph,
Branch, TargetBranch, BranchTransition). Per-type and per-row
authority will be the query layer's job.
Common patterns
Open dev, locked-down prod
Local development with no tokens runs Open (with --unauthenticated).
Production sets bearer tokens and a Cedar policy:
# omnigraph.yaml (prod)
policy:
file: ./policy.yamlPer-team branch ownership
Model agent teams as Cedar entity groups. Rules restrict change and
branch_merge against main to operators in the release-managers group,
while still allowing every team to create and merge feature branches under
their own namespace.
Audit trail
Every commit records the actor that wrote it (actor_id in commit show
output). Combined with branch history, you get an actor-attributed audit
log without any extra logging plumbing.