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.
| Engine | Model | Origin | What 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 |
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.
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 with | Because |
|---|---|---|
| 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:
- Policy as code for agents — the pattern these engines implement, independent of vendor.
- Scoped credentials for agents — the other half: narrowing what a token can reach before any policy runs.
- Agent identity & permissions — the concept-level introduction if the vocabulary here is new.
- Delegated access & consent records — what you must be able to show afterwards.