AI Blog

OPA vs Cedar vs OpenFGA vs SpiceDB: who is trusted to supply the facts

All four can express the policy. Only two of them answer without the caller supplying the facts — and when the caller is an agent reading attacker-controlled text, that is the entire security property. The second question is the check budget: an agent makes dozens of authorization calls per task, and filtering a retrieval set makes thousands.

By Agentic AI Wiki 14 min read

Every one of these four engines can express "may this subject take this action on this resource", so the syntax argument is a distraction. The question that actually separates them is who supplies the facts the decision is made from — and when the caller is an agent whose context contains text an attacker wrote, the facts the caller supplies are precisely the ones you cannot trust.

At a glance

Two of these are policy engines that evaluate a request you hand them. Two are Zanzibar-style relationship stores that hold the data and answer from their own graph. That split, not the policy language, is the decision.

EngineModelOriginWhat it holds
OPA / Rego Policy-as-code, ABAC and RBAC Styra; CNCF graduated Policy only — data arrives with the request or in a bundle
Cedar Policy-as-code, purpose-built for authorization AWS; open-sourced 2023 under Apache 2.0 Policy only — entities are passed in per call
OpenFGA ReBAC, Google Zanzibar lineage Created at Auth0; CNCF project The relationship graph, as tuples
SpiceDB ReBAC, Google Zanzibar lineage AuthZed The relationship graph, in a distributed datastore
Two places the deciding facts can live Architecture diagram contrasting two authorization shapes. In the attribute-passing shape used by OPA and Cedar, the agent's request carries the subject, resource and context attributes to the policy engine, so the caller supplies the facts the decision is made from. In the relationship-store shape used by OpenFGA and SpiceDB, the engine holds its own relationship graph and the caller supplies only the tuple to check, so the decision reads facts the agent cannot influence. Where the deciding facts come from ATTRIBUTE-PASSING — OPA (REGO), CEDAR Agent context is reachable Enforcement point builds the request Policy engine evaluates a rule subject + resource + context travel with the request The engine is stateless and fast. It knows only what the caller told it, so every attribute in the decision has to be assembled by something the agent cannot influence — or the check is theatre. Cedar is built to be analysed; Rego is built to be general. Neither one stores your data. RELATIONSHIP STORE — OPENFGA, SPICEDB Agent context is reachable Enforcement point asks one question Decision service walks the graph Relationship graph owned by the engine The caller supplies a tuple to check, not the facts to decide from. Delegation is a relationship the store already holds, so an agent cannot assert its way into an answer — and the same store can enumerate what a subject may reach, which is what filtering a retrieval set needs. BOTH SHAPES CAN EXPRESS THE SAME POLICY. THEY DIFFER ON WHO IS TRUSTED TO SUPPLY THE FACTS, WHICH IS THE WHOLE QUESTION ONCE THE CALLER IS A MODEL READING ATTACKER-CONTROLLED TEXT
The same policy, two trust boundaries. The lower lane is the one an agent cannot argue with.
Where each authorization engine leans hardest Feature matrix with four rows for OPA with Rego, Cedar, OpenFGA and SpiceDB, and five columns: holds the data, enumerates resources, models delegation, supports policy analysis, and operational footprint. OPA and Cedar are strong on analysis and light operationally but hold no data and cannot enumerate. OpenFGA and SpiceDB hold the relationship graph and can enumerate, at the cost of running a stateful service. Five axes that decide it, and nobody is strong on all five HOLDS THE DATA ENUMERATES DELEGATION ANALYSABLE LIGHT TO RUN OPA / Rego CNCF graduated No — you pass it No Expressible Rego is general Sidecar + bundles Cedar AWS, Apache-2.0 No — you pass it Batch, not list Expressible Designed for it Library or hosted OpenFGA Zanzibar, CNCF Owns the graph ListObjects Documented Model review Service + store SpiceDB Zanzibar, AuthZed Owns the graph LookupResources Modellable Model review Distributed DB STRONG MEDIUM WEAK POSITIONS AS OF AUGUST 2026. "LIGHT TO RUN" IS AN OPERATIONS JUDGEMENT, NOT A BENCHMARK
Where each one leans hardest. Nobody is strong on all five, and the two you need depend on what your agent does all day.

The agent broke an assumption every one of these was built on

Conventional application authorization assumes a trustworthy enforcement point. Your API handler knows who called it, because a token said so; it assembles the subject, the resource and whatever context the policy needs, and asks the engine. The engine trusts the handler completely, and that is fine, because the handler is code you wrote and an attacker cannot rewrite it by sending a cleverly worded email.

An agent dissolves that assumption in a specific way. The agent's context window contains retrieved documents, tool results, web pages and user messages — some of which an attacker controls. The agent then decides which tool to call and with what arguments. So if your enforcement path lets the agent's own output determine any input to the authorization decision, you have built a system where an injected instruction can widen its own permissions. This is not hypothetical: it is the shape of most of the high-severity agent CVEs of the past year, which is the argument we made in agent CVEs are authorization bugs.

Three consequences fall out, and they map directly onto the four products.

1. The facts must come from outside the loop

With OPA or Cedar, the decision is a pure function of the request. That is a genuine virtue — it is why Cedar can be formally analysed and why both are fast and stateless. But it means the quality of the check is exactly the quality of whatever built the request. Put the enforcement point inside the agent process, let it populate attributes from the model's tool-call arguments, and the engine will faithfully authorize whatever the injected text asked for. Both engines are perfectly capable of enforcing correct policy here; the failure is architectural, and it is easy to commit.

With OpenFGA or SpiceDB, the caller passes a tuple — subject, relation, object — and the engine answers from a graph it owns. The agent can ask any question it likes and cannot change the answer, because the facts were written by your provisioning path, not by this request. That property is not unavailable to the policy-as-code engines, but you have to build it; in the relationship stores it is the default.

2. "On behalf of" is not "as"

The mistake that produces the worst incidents is giving the agent the user's token and calling it delegation. An agent acting for Dana should hold its own identity, carry a subset of Dana's authority scoped to this task, and be revocable without touching Dana's account. OpenFGA publishes explicit modelling guidance for exactly this — the agent as a distinct subject type related to the principal it serves — which as of August 2026 is the most developed treatment among the four. Auth0's hosted FGA sits on top of the same engine, alongside the on-behalf-of token exchange it shipped in 2026.

Cedar has the clearest production story on the other side of the split: Amazon Bedrock AgentCore Policy, generally available since March 2026, evaluates Cedar policies at a gateway on each tool invocation, with the caller identity and the call's arguments as the unit of control. That is the right enforcement point — outside the agent, on the action — and it demonstrates that the attribute-passing shape works fine for agents when the attributes are assembled somewhere the model cannot reach.

3. The check budget is a different order of magnitude

A web request makes one authorization check. An agent task makes one per tool call, plus one per resource each tool touches, plus — if you are filtering retrieved documents by permission, which you must be — one per candidate chunk. That last case is where naive designs fall over.

Authorization latency per unit of work, at five milliseconds a check Horizontal bar chart on a logarithmic scale showing how much authorization latency accumulates per unit of work when each check costs five milliseconds. A single web request spends five milliseconds. A short agent task making twelve checks spends sixty milliseconds. A tool-heavy agent task making sixty checks spends three hundred milliseconds. Filtering a thousand retrieved chunks one check at a time spends five seconds, which is why bulk enumeration matters more than single-check speed. One check is cheap. The unit of work is what changed. Web request 1 check 5 ms Short agent task 12 checks 60 ms Tool-heavy task 60 checks 300 ms Filtering retrieval 1,000 chunks, one at a time 5,000 ms 1 ms 10 ms 100 ms 1 s 10 s LOG SCALE. ILLUSTRATIVE AT A FLAT 5 MS PER CHECK — REAL RELATIONSHIP CHECKS VARY WITH GRAPH DEPTH THE BOTTOM ROW IS THE ONE A BULK ENUMERATION CALL COLLAPSES; SINGLE-CHECK SPEED DOES NOT SAVE IT
At a flat 5 ms a check, the bottom row is the one that ends the design review.

The fix for the bottom row is not a faster check — it is a different call. Both relationship stores can enumerate: ask "which documents may this subject read" and filter the retrieval candidates against the answer, or feed the list into the vector query as a pre-filter. OpenFGA calls it ListObjects; SpiceDB calls it LookupResources. Neither policy-as-code engine can do this, because neither holds the data to enumerate from — with Cedar or OPA you either pre-compute the accessible set yourself or accept per-item batch evaluation. If retrieval-time permission filtering is central to your product, this single capability decides the comparison and nothing else in this post matters much.

Cross-cutting comparison

Where the latency comes from

The two families fail differently under load. OPA and Cedar are effectively constant-time on the evaluation itself — the cost sits in assembling the request, which means fetching the attributes, which means the database calls you did not count. OpenFGA and SpiceDB do the fetching for you, so their latency is dominated by graph traversal depth: a shallow "user is a member of a team that owns this document" check is quick, and a deeply nested hierarchy with several levels of inherited groups is not. Neither family is faster in general; they move the cost to different places, and only one of those places shows up on the authorization service's dashboard.

What each one costs you to operate

Here the ordering is clean and it runs opposite to the capability ordering. Cedar as a library is close to free — it evaluates in-process, and if you want it managed, Amazon Verified Permissions exists. OPA is a sidecar plus a bundle-distribution pipeline you have to build and monitor, which is more moving parts than teams expect. OpenFGA is a service with a datastore behind it. SpiceDB is a distributed database with the consistency semantics that implies, and it is reportedly what OpenAI runs for ChatGPT Enterprise permissions at a scale measured in tens of billions — which tells you it goes the distance, and also that it is a system you should expect to staff. Pick the lightest one whose model actually answers your questions, not the heaviest one you can justify.

How wrong you can be, and how fast you find out

Cedar was designed for analysability, and it is the only one of the four where "prove this policy change grants nothing new" is a tractable question rather than an aspiration. Rego's expressiveness is the mirror image: it will let you write policy no tool can reason about, which is why it is the standard when one engine has to cover infrastructure and applications at once, and why it is a poor default when the policy is only about application resources. The relationship stores put the risk somewhere else entirely — a mis-modelled relation quietly grants a whole class of access, and you find it by reviewing the model and testing it, not by analysing a policy file. All three failure modes are survivable; they need different review rituals, and choosing an engine is partly choosing which ritual your team will actually perform.

When to pick which

If your situation is…Start withBecause
Agents filtering retrieved documents per user OpenFGA or SpiceDB Bulk enumeration is the only thing that makes per-chunk filtering affordable
Gating tool calls at a gateway, AWS-centric stack Cedar Analysable policy, in-process evaluation, and an existing agent-gateway integration
One policy engine across Kubernetes, CI and the app OPA The breadth is the product; nothing else covers infrastructure as well
Deep sharing hierarchies, folders inheriting from folders SpiceDB The most mature Zanzibar implementation, proven at very large permission counts
You need a delegation model before you need scale OpenFGA Published agent-authorization modelling, and a hosted path via Auth0 FGA
Small team, simple RBAC, wants it done this week Cedar, or Cerbos Lowest operational floor; revisit when a relationship question appears

One thing does not vary with the choice: the enforcement point belongs outside the agent. Put it at the tool gateway, populate the subject from the token rather than from the model's arguments, and log the decision with the trace. An engine cannot save a design that lets the caller name itself.

FAQ

Is this the same thing as agent authentication?

No, and the two get conflated constantly. Authentication establishes which agent is calling and whose authority it carries — the identity-provider layer we compared in Auth0 vs Descope vs Stytch vs WorkOS. Authorization decides whether that established identity may perform this specific action on this specific resource. You need both, and a perfect token proves nothing about whether the call should be allowed.

Can I just put the permission rules in the system prompt?

No. A prompt is a request, not a control: the same context window that carries your rules carries the attacker's text, and the model arbitrates between them. Prompt-level rules are useful for making the agent behave sensibly and useless as a security boundary. The check has to happen in code the model cannot influence.

Do I need one of these at all, or is my framework's permission check enough?

If your agent touches one system with a handful of roles, your existing checks are probably fine — adding a policy engine has a real cost and buys little. The signal that you have outgrown it is the arrival of a relationship question: "can this agent read a document because it was shared with a group its principal belongs to". That question is answerable with a hand-rolled check exactly once, and then it multiplies.

What about Cerbos, Oso, Permify or Ory Keto?

They are real options and they fall on the same split. Cerbos and Oso are policy-as-code alongside OPA and Cedar; Permify and Ory Keto are Zanzibar-style alongside OpenFGA and SpiceDB. Permit.io is a different category again — a management platform that runs OPA, Cedar or OpenFGA underneath, which is worth knowing because it means choosing it does not exempt you from the decision in this post.

Does the relationship store become a single point of failure?

Yes, deliberately, in the same way your identity provider already is. Plan for it: cache decisions with a short TTL where staleness is acceptable, fail closed on write paths and consider failing closed everywhere for agents, and load-test the authorization tier along with everything else — an agent workload puts an order of magnitude more traffic through it than the web application it sits next to.

Further reading

On this wiki:

Project sources: