Skip to content

ADR-0006: Cedar as policy engine

  • Status: Accepted
  • Date: 2026-04-12

Context

Capability invocations need authorization decisions: "may this principal invoke this capability on this resource with this context?" Options:

  1. Code-based guards. if not user.is_admin: raise. Rails-default. Invisible to framework, no audit trail, policy logic scattered.
  2. OPA / Rego. Industry standard for policy-as-code. Gopher-based, widely adopted. Rego is Turing-complete and notoriously hard to read/debug.
  3. Cedar (AWS). Typed policy language, formally verified decision procedure, designed for authorization specifically (not general policy).
  4. XACML. Enterprise standard. Heavyweight, XML-based, poor DX.
  5. Custom DSL. Tailored but yet-another-policy-language in the world.

Requirements: - Decisions must be fast (< 1 ms typical). - Decisions must be explainable — the decision log entry should say why this was permit/deny. - Policies should be static-analyzable — we want to know at framework-level whether a capability is reachable with valid policies. - Policy bodies should be separate from Python code — readable by non-developers (security, compliance). - Policy language should be bounded (total, terminating) — no Turing-completeness DoS risk.

Decision

Cedar as the v1 policy engine.

Policies live in policies/*.cedar. Capabilities reference them via @policy("file::rule_name"). The framework's PEP evaluates decisions before handler execution. Decisions are logged to an append-only decision log with full Cedar diagnostics.

Cedar is embedded as a Rust dependency in trails-policy; PyO3 exposes its authorize() surface to Python decorators.

Consequences

Positive

  • Typed policy language. Cedar policies have schemas; misuse is caught statically.
  • Fast. Cedar is designed for sub-ms decisions.
  • Explainable. Cedar's decision includes which policies were determining — drops directly into the decision log.
  • Bounded. Cedar is total; no infinite loops.
  • Formally verified. AWS has formalized Cedar's decision procedure; reduces "did we get policy right" risk.
  • Readable. Cedar syntax is closer to English than Rego; compliance reviewers can audit.
  • Trait-abstracted. PolicyEngine trait means Cedar can be swapped later without API changes.

Negative

  • Cedar is newer than OPA. Smaller community, less tooling, less prior art in agent contexts. Mitigated by AWS's stewardship + formal basis.
  • Cedar is AWS-branded. Some orgs may resist Amazon-affiliated dependencies. Mitigated by Cedar being open source under Apache 2.0 with no AWS-coupling.
  • Policy authoring ergonomics still require a learning curve. Mitigated by shipping policy library presets (GDPR, HIPAA, EU AI Act) as templates.
  • Rust-native — Python-based admin UIs for policy editing don't exist off-the-shelf. Mitigated by policies being plain text files.

Non-consequences

  • Application code doesn't import Cedar — only the framework does.
  • Policies are decoupled from handler code; changing a policy doesn't require Python changes.

Revisit conditions

  • If the Cedar ecosystem stalls or AWS de-invests, evaluate OPA or returning to in-Python policy DSLs.
  • If Cedar's expressivity proves insufficient for agent-specific patterns (e.g., capability-chain authority), consider layering a higher-level abstraction that compiles to Cedar.