Authorization in 2026: revisit OpenFGA, SpiceDB and Cerbos before you hard-code another permission check

Picture a SaaS company in Nagpur that sells loan-origination software to cooperative banks across Vidarbha and Marathwada (an invented example, not a real client). About thirty-five engineers and a permission story that grew one if at a time. Officers see loan files of their branch, managers approve up to a sanction limit, bank admins see every branch, and a blocked user must never approve. Each rule lives in three places: the Java API, a reporting query and a React screen that hides buttons. When a large cooperative bank asks “who can approve what”, nobody can answer from one place. Three proposals land on the architecture review. One engineer wants OpenFGA because it is a CNCF project inspired by Google Zanzibar. A second wants SpiceDB because its schema language and consistency tokens look rigorous. A third wants Cerbos because the team already writes YAML and the rules sound like policies, not graphs. They are discussing these as three brands of the same thing. They are not.

Authorization services in 2026 come in at least two shapes. OpenFGA and SpiceDB are relationship-based access control (ReBAC) systems in the Zanzibar family: you define a model of types and relations, write relationship tuples into the service’s own datastore, and ask “does subject X have relation Y on object Z?” The service walks the graph. Cerbos is a stateless policy decision point (PDP): you keep your data where it is, send the principal, the resource and its attributes on every request, and Cerbos evaluates YAML policies with CEL conditions. Teams put both shapes in the same shopping cart because both answer “can this user do this?”, but ReBAC systems ask you to move relationship data into them, while a PDP asks you to move decision logic into it. This post compares where OpenFGA, SpiceDB and Cerbos stand on 14 October 2026: history, modelling, consistency, conditions, list filtering, SDKs, observability, security advisories and a decision guide. Facts come from GitHub releases, LICENSE files at tags, official docs and advisory records cross-checked against NVD and cve.org. The drill near the end is my own run of all three binaries on this Linux box with one invented lending model, labelled clearly for what was and was not run.

The short version

  • Decide first which shape your problem has. Permissions that flow through relationships (bank, branch, team, folder, sharing) suit a ReBAC store. Rules over attributes you already hold (role, amount, region, time) suit a policy PDP. Many systems need a bit of both.
  • OpenFGA (openfga/openfga) is at v1.22.0 (7 October 2026, 12:59 AM IST). Apache 2.0. Zanzibar-inspired ReBAC with a modelling DSL, CEL conditions, HTTP and gRPC APIs, PostgreSQL, MySQL and SQLite (beta) storage and SDKs for Node.js, Go, .NET, Python and Java. CNCF project since 14 September 2022, Incubating since 28 October 2025. Auth0 FGA is the managed service built on it.
  • SpiceDB (authzed/spicedb) is at v1.56.2 (11 September 2026, 11:31 PM IST). Apache 2.0. Zanzibar-inspired ReBAC with a schema language, CEL caveats, per-request consistency through ZedTokens, and CockroachDB, Cloud Spanner, PostgreSQL or MySQL datastores. Built by AuthZed, which also sells managed offerings. Not a CNCF project.
  • Cerbos (cerbos/cerbos) is at v0.56.0 (30 September 2026, 12:05 PM IST). Apache 2.0. A stateless PDP that evaluates YAML policies with CEL conditions, answers CheckResources and PlanResources, and loads policies from disk, git, blob storage or a database. Cerbos Hub is the commercial control plane. Still on 0.x.
  • Security history differs by project. OpenFGA has 29 repository advisories since 2022, 11 of them in 2026, many of them authorization bypass or “improper policy enforcement” in specific model shapes, plus an OIDC audience issue (CVE-2026-55689) and a playground key exposure (CVE-2026-40293). SpiceDB has 18 advisories, including secrets leaking through metrics, logs or traces (CVE-2023-29193, CVE-2026-40091, GHSA-jf7w-wx43-32gc). I found no published CVE or GitHub advisory for Cerbos, but its default evaluation mode can silently skip a DENY rule whose CEL condition errors, which the drill shows.

Where things stand on 14 October 2026

Dates are from each project’s GitHub release published_at; times are converted from UTC to IST (UTC+5:30). Licences are from the LICENSE files at the tagged releases. None of the listed releases is marked prerelease.

Project

Release

Date (IST)

Notes

Licence

OpenFGA (openfga/openfga)

v1.22.0

7 October 2026, 12:59 AM

Current release; MySQL read-replica support (--datastore-secondary-uri), gRPC library bump for GO-2026-6443, minimum Go 1.26.0

Apache 2.0

OpenFGA

v1.21.0

20 September 2026, 9:57 PM

Adds experimental “Dynamic Conditions”

Apache 2.0

OpenFGA

v1.20.0

9 September 2026, 2:11 AM

Fixes GHSA-h7w8-xr72-cv4r; Go 1.26.8 toolchain

Apache 2.0

OpenFGA CLI (openfga/cli)

v0.8.1

24 September 2026, 9:08 AM

fga CLI used for model tests in the drill

Apache 2.0 (repo)

SpiceDB (authzed/spicedb)

v1.56.2

11 September 2026, 11:31 PM

Current release; fixes GHSA-jf7w-wx43-32gc (secrets in traces), namespace cache TTL, MemDB snapshot reads

Apache 2.0

SpiceDB

v1.56.1

26 August 2026, 11:33 PM

Patch release

Apache 2.0

SpiceDB

v1.56.0

25 July 2026, 1:16 AM

Adds pkg/embedded in-process library, permissionship counter metric, cached compiled caveats

Apache 2.0

SpiceDB

v1.54.0

19 June 2026, 4:57 AM

Previous minor; there is no v1.55.x tag

Apache 2.0

Cerbos (cerbos/cerbos)

v0.56.0

30 September 2026, 12:05 PM

Current release; removes long-deprecated config keys, fixes principal-policy deny precedence in the query planner

Apache 2.0

Cerbos

v0.55.0

13 August 2026, 1:44 PM

CEL library upgrade (stricter), strict evaluation mode, multiple JWTs in auxData

Apache 2.0

Cerbos

v0.54.0

20 July 2026, 3:08 PM

Logs CEL runtime errors and adds them to audit entries

Apache 2.0

Practical notes on the table. All three ship a checksums.txt with release binaries (OpenFGA and Cerbos also attach SBOMs); the drill verified SHA-256 for every archive it used. OpenFGA has released roughly every two to three weeks this year, often with an advisory fix, so “we are on 1.1x, it works” is not a safe position. SpiceDB skipped the 1.55 number; fix automation that expects consecutive minors. Cerbos is still 0.x, so read each release note for breaking changes, as v0.55.0 (CEL library) and v0.56.0 (removed config keys) both had.

Timeline

Date (IST)

Event

July 2019

“Zanzibar: Google’s Consistent, Global Authorization System” presented at USENIX ATC 2019

24 March 2021

First Cerbos tag (v0.0.0-alpha0); v0.0.1 follows on 18 May 2021

30 September 2021

SpiceDB v1.0.0

10 June 2022

OpenFGA v0.1.0

14 September 2022

OpenFGA accepted into the CNCF

13 April 2023

SpiceDB CVE-2023-29193: preshared key visible through the metrics port’s pprof cmdline

15 April 2023

OpenFGA v1.0.0

10 August 2024

OpenFGA CVE-2024-42473: Check bypass in v1.5.7 and v1.5.8

28 October 2025

OpenFGA moves to CNCF Incubating

9 April 2026

OpenFGA CVE-2026-40293: preshared key in playground HTML

15 April 2026

SpiceDB CVE-2026-40091: datastore DSN in startup logs

19 June 2026

OpenFGA CVE-2026-55689 (OIDC audience) and SpiceDB CVE-2026-55866 (caveat result under concurrency) published

13 August 2026

Cerbos v0.55.0 with strict evaluation mode

11 September 2026

SpiceDB v1.56.2 with GHSA-jf7w-wx43-32gc fix

29 September 2026

OpenFGA GHSA-h7w8-xr72-cv4r published (fixed in v1.20.0)

30 September 2026

Cerbos v0.56.0

7 October 2026

OpenFGA v1.22.0

14 October 2026

This post’s research and the three-engine drill

The history explains the shapes. The Zanzibar paper described how Google stores access control as relationships and checks them consistently at scale; SpiceDB and OpenFGA are two open-source answers to it. Cerbos started around the same time (first tag in March 2021) from a different question: how do we stop scattering role and attribute rules across services without moving our data?

First question: what are you actually trying to centralise?

Before comparing logos, the Nagpur team should write down which of these hurts most:

  1. Hierarchy and sharing. “Members of the branch that owns this file can view it”, “bank admins see all branches”. The answer depends on chains of relationships: ReBAC territory, OpenFGA or SpiceDB.
  2. Rules over attributes you already have. “Managers approve up to their sanction limit”, “no approvals after 8 PM”. The data sits in your loan table and identity token: PDP territory, Cerbos, or ReBAC conditions if the rules are few.
  3. “Show me only what I can see” lists. Designs break first on list screens. ReBAC systems answer with ListObjects or LookupResources; Cerbos returns a query plan you turn into a database filter.
  4. Identity is a separate problem. None of the three logs anyone in. Authentication and SSO belong to your identity provider, covered in the Keycloak and alternatives post.

If you install OpenFGA and pass every rule as attributes, you have built an expensive PDP. If you install Cerbos and push the whole bank and branch graph into every request, you have rebuilt a graph database in your API layer. The first design choice is where relationship data lives.

What each project actually is

OpenFGA 1.22.0

OpenFGA describes itself as a high-performance, flexible authorization engine inspired by Google Zanzibar. You create a store, write an authorization model in its DSL (or JSON), write relationship tuples such as user:ravi is officer of branch:sitabuldi, and call Check, ListObjects, StreamedListObjects or ListUsers. The DSL builds permissions with or, and, but not and X from Y. Conditions attach CEL expressions to tuples, evaluated against request context.

Operational facts from the README and docs:

  • Storage: in-memory (development only), PostgreSQL 14+, MySQL 8 and SQLite (beta). PostgreSQL already supported a read replica; v1.22.0 adds it for MySQL, with a documented read-after-write risk.
  • APIs and auth: HTTP and gRPC; authentication none, preshared key or OIDC.
  • Tooling: the fga CLI (with fga model test), a Terraform provider, a Go library for embedding, and a local playground on port 3000 that the production guide tells you to disable.
  • Governance: CNCF Incubating. The README says Auth0 FGA has run it in production since December 2021.

OpenFGA fits when you want a Zanzibar-style service under foundation governance with a friendly DSL and good SDK coverage. Its trade-off shows in the advisory list: correctness in complex models (exclusions, wildcards, caching) is hard, and the project discloses and fixes such issues often.

SpiceDB 1.56.2

SpiceDB calls itself the most mature open-source project inspired by Zanzibar and stays closest to the paper’s vocabulary. A schema uses definition, relation and permission, combining relations with + (union), & (intersection), - (exclusion), -> (arrow) and .any / .all. Caveats are typed CEL expressions on relationships, which is how SpiceDB mixes ABAC into ReBAC. Writes return a ZedToken; reads can ask for minimize_latency, at_least_as_fresh, at_exact_snapshot or fully_consistent, SpiceDB’s answer to the paper’s “new enemy problem”.

Operational facts from the README and docs:

  • Datastores: CockroachDB (recommended for high throughput or multi-region), Cloud Spanner, PostgreSQL (single-region), MySQL (only if you cannot use PostgreSQL, per docs) and in-memory for tests.
  • APIs: gRPC first, optional HTTP gateway with OpenAPI, and a Watch API for relationship changes.
  • Tooling: the zed CLI, a playground, validation files with assertions for CI, an LSP, a Kubernetes operator, and since v1.56.0 pkg/embedded for in-process checks.
  • Commercial edge: AuthZed sells Cloud, Dedicated and Enterprise products; audit logging is documented as exclusive to them.

SpiceDB fits when you need tight consistency control, very large relationship sets or multi-region datastores. Its trade-off is operational weight: a distributed service with its own datastore and dispatch layer, and configuration secrets that need care, as its advisories show.

Cerbos 0.56.0

Cerbos describes itself as an authorization layer with context-aware rules in YAML policies managed through git. The open-source Cerbos PDP is a stateless service that holds compiled policies in memory and answers two questions:

  • CheckResources: can this principal perform these actions on these resources?
  • PlanResources: which resources of kind X can this principal perform action Y on? The answer is a filter expression tree you convert into a database query, with adapters for Prisma, SQLAlchemy and other ORMs listed in the docs.

Policy kinds are resource policies, derived roles (roles computed from conditions, such as “same branch”), principal policies (per-user overrides), role policies and scoped policies for multi-tenant hierarchies. Conditions are CEL over request.principal, request.resource and auxiliary data such as JWT claims.

  • Policy storage: disk, git (the docs’ preferred method), blob storage, Cerbos Hub, overlay, or SQLite3, PostgreSQL or MySQL for policies managed through the Admin API.
  • Deployment: Kubernetes service, sidecar or daemonset, systemd or serverless. Default ports HTTP 3592 and gRPC 3593 on all interfaces.
  • Safety defaults: Admin API off by default and authenticated when on; requests limited to 50 resources and 50 actions per resource.
  • Testing: cerbos compile validates policies and runs *_test.yaml suites.
  • Commercial edge: Cerbos Hub adds playgrounds, fleet distribution and embedded PDP bundles.

Cerbos fits when your data already holds the facts and the hard part is keeping rules consistent, reviewable and testable across services. Its trade-off: it knows only what you send. If “admin of the bank that owns the branch that owns this file” needs three lookups, your application does them before calling Cerbos.

Model comparison: tuples and graphs vs policies and attributes

Concern

OpenFGA 1.22

SpiceDB 1.56.2

Cerbos 0.56

Shape

ReBAC service with its own tuple store

ReBAC service with its own relationship store

Stateless PDP; no business data stored

What you write

Model (DSL or JSON) plus tuples

Schema plus relationships

YAML policies (and optional schemas)

Where facts live

Inside OpenFGA’s datastore

Inside SpiceDB’s datastore

In your systems; sent per request

Attribute rules

Conditions (CEL) on tuples, context per request

Caveats (CEL) on relationships, context per request

CEL conditions are the core feature

Typical question

Check, ListObjects, ListUsers

CheckPermission, LookupResources, LookupSubjects

CheckResources, PlanResources

List filtering

Ask the service for object IDs

Ask the service for resource IDs, or bulk-check a page

Translate a query plan into your own SQL or ORM filter

Consistency concern

Cache staleness vs HIGHER_CONSISTENCY

ZedTokens and per-request consistency levels

Policy reload freshness; data freshness is yours

Change stream

ReadChanges

Watch

Not applicable (no relationship data)

Licence (tag)

Apache 2.0

Apache 2.0

Apache 2.0

Governance

CNCF Incubating

AuthZed-led open source

Cerbos-led open source

Symptom to shape

Symptom

Likely fit

Sharing, folders, teams, nested organisations, “inherit from parent”

OpenFGA or SpiceDB

Rules over amount, status, region, time, risk score that already sit in your tables

Cerbos

Strict “a revoked user must lose access before the next read” requirement

SpiceDB ZedTokens, or OpenFGA with caching off or HIGHER_CONSISTENCY on sensitive paths

List screens over millions of rows with mixed rules

Cerbos query plans if rules map to SQL; ReBAC lookups if access is mostly relationship-driven

Modelling the Nagpur example three ways

These snippets come from the drill, trimmed. Same rules each time: branch staff and admins of the owning bank can view a file; a listed approver can approve within their limit, unless blocked.

OpenFGA DSL:

model
  schema 1.1

type user

type bank
  relations
    define admin: [user]

type branch
  relations
    define bank: [bank]
    define manager: [user]
    define officer: [user]
    define member: officer or manager or admin from bank

type loan_file
  relations
    define branch: [branch]
    define blocked: [user]
    define approver: [user with within_limit]
    define viewer: member from branch
    define can_approve: approver but not blocked

condition within_limit(amount: int, limit: int) {
  amount <= limit
}

SpiceDB schema:

definition branch {
  relation bank: bank
  relation manager: user
  relation officer: user
  permission member = officer + manager + bank->admin
}

caveat within_limit(amount int, limit int) {
  amount <= limit
}

definition loan_file {
  relation branch: branch
  relation blocked: user
  relation approver: user with within_limit
  permission view = branch->member
  permission approve = approver - blocked
}

Cerbos resource policy (derived role branch_staff checks request.principal.attr.branch == request.resource.attr.branch):

apiVersion: api.cerbos.dev/v1
resourcePolicy:
  resource: "loan_file"
  version: "default"
  importDerivedRoles: [lending_roles]
  rules:
    - actions: ["view"]
      effect: EFFECT_ALLOW
      derivedRoles: [branch_staff, bank_admin_same_bank]
    - actions: ["approve"]
      effect: EFFECT_ALLOW
      roles: [manager]
      condition:
        match:
          all:
            of:
              - expr: request.principal.attr.branch == request.resource.attr.branch
              - expr: request.resource.attr.amount <= request.principal.attr.approval_limit
    - actions: ["approve"]
      effect: EFFECT_DENY
      roles: ["*"]
      condition:
        match:
          expr: request.principal.id in request.resource.attr.blocked

Side by side the difference is plain. OpenFGA and SpiceDB store “Meera can approve LF-1001 up to ₹5 lakh” as a conditional relationship, and the request brings only the amount. Cerbos stores nothing about Meera; the request brings her branch, her limit and the file’s attributes. Both are valid; they move different responsibilities to different teams.

Consistency and caching

This is where Zanzibar-style systems earn their complexity, and where teams most often misread the docs.

  • OpenFGA: queries accept MINIMIZE_LATENCY (default) or HIGHER_CONSISTENCY. Caching is off by default, and then every query reads the datastore, so results are strongly consistent in either mode. Once you enable the Check or iterator caches, MINIMIZE_LATENCY may miss a tuple you just wrote; HIGHER_CONSISTENCY skips the cache. Read replicas (PostgreSQL, and MySQL from v1.22.0) add another lag source.
  • SpiceDB: every write returns a ZedToken and most reads default to minimize_latency. To make a check see a given write, pass at_least_as_fresh with that token, and store the token with the resource when it changes. fully_consistent exists but costs latency.
  • Cerbos: there is no relationship data to go stale. The questions are how quickly a policy change reaches every PDP (git or blob poll interval, Hub, or Admin API reload) and whether the attributes you send are current. v0.56.0 fixed an Admin API reload with wait=true that returned before compilation finished.

The dangerous case for Nagpur is revocation: a manager moved out of a branch at 11 AM approves a file at 11:01 AM. In a ReBAC system, decide per API path whether that window is acceptable. In a PDP design, the window is however stale your own user data is when you build the request.

Conditions and ABAC: three CEL dialects

All three use Google’s Common Expression Language (CEL) for attribute rules, but they treat missing data differently, and the drill made that concrete:

  • OpenFGA conditions have typed parameters. Docs cap tuple condition context at 32 KB, requests at 512 KB and CEL evaluation cost at 100 by default. A Check without the required amount returned HTTP 400 with a validation_error, not allowed: false.
  • SpiceDB caveats are typed too. A check without context returned **PERMISSIONSHIP_CONDITIONAL_PERMISSION** with missingRequiredContext: ["amount"]. Treat that as “not allowed yet”.
  • Cerbos conditions are the core language. By default a CEL runtime error (missing attribute, mismatched types) makes the condition “not satisfied”. For an ALLOW rule that fails closed; for a DENY rule the deny is skipped. The engine docs say this plainly and offer engine.strictEvaluation: true (default false) and cerbos compile --strict-evaluation. In the drill, a file without risk_score was approved under defaults despite a “deny if risk score above 80” rule.

Three systems, three behaviours for the same mistake: an error, a third state, and a silent skip. Whichever you choose, write tests for missing attributes and make your client code fail closed on anything that is not an explicit allow.

Performance notes without invented benchmarks

I am not publishing latency or throughput numbers. Vendor pages carry their own claims (SpiceDB’s README links one from AuthZed’s blog); those are the vendor’s own runs, and the drill below measured no performance. What the docs say is still useful:

  • OpenFGA: the production guide recommends a small pool of larger servers for better cache hit ratios, datastore pool settings matched to the database’s connection limit, caching only where staleness is acceptable, and RPC histograms for per-endpoint latency.
  • SpiceDB: performance depends on the datastore and the dispatch cluster, where servers share sub-problems through a consistent hash ring. Very wide relations once triggered an overflow bug (CVE-2024-27101). For lists, docs compare LookupResources (small result sets) with CheckBulkPermissions over a page of candidates.
  • Cerbos: evaluation is in-memory over compiled policies, so the costs are request size, policy count and the round trip. A sidecar removes a network hop at the cost of one PDP per pod.

Measure with your model and traffic. A Check that walks a five-level hierarchy with exclusions is a different workload from a flat RBAC check.

Language SDKs and integration patterns

Area

OpenFGA

SpiceDB

Cerbos

Official SDKs (docs)

Node.js, Go, .NET, Python, Java

gRPC clients for Go, Node, Python, Ruby, Java, .NET

Go, Java, JavaScript, .NET, PHP, Python, Ruby, Rust

Protocols

HTTP and gRPC

gRPC; optional HTTP gateway with OpenAPI

HTTP and gRPC

CLI

fga

zed

cerbos, cerbosctl

Embedded option

Go library

pkg/embedded (since v1.56.0)

Embedded PDP via Cerbos Hub (commercial)

Config as code

Terraform provider; model files in git

Schema files and validation YAML in git

Policies in git; cerbos compile in CI

Integration patterns that work regardless of choice:

  1. Enforce in one layer per request path. A middleware or interceptor calls the service and fails closed on timeout or error. A gateway can do coarse checks; object-level checks belong near the code that loads the object.
  2. Keep relationship writes in step with business writes. With OpenFGA or SpiceDB, creating a loan file also means writing its branch relationship. Choose outbox, change-data-capture or dual-write, and test the failure path.
  3. Separate permissions from release toggles. Feature flags decide whether a feature exists for a cohort; authorization decides whether a person may act on a record. The feature flags post covers the other half.
  4. Test models and policies like code. fga model test, SpiceDB validation files and cerbos compile suites run without a cluster. Put them in CI from the first pull request.

Observability

  • OpenFGA: Prometheus metrics on 0.0.0.0:2112/metrics by default, with openfga_* series for caches and datastore plus gRPC server metrics; --metrics-enable-rpc-histograms adds per-endpoint latency. OTLP tracing is off until --trace-enabled; the production guide suggests a low sample ratio and JSON logs.
  • SpiceDB: a metrics server (default :9090) exposes /metrics and /debug/pprof. Since v1.56.0 a counter splits Check results by permissionship (spicedb_v1_check_permissionship_total). OpenTelemetry tracing and JSON logs are built in. Keep that port private: CVE-2023-29193 came from pprof cmdline on it.
  • Cerbos: Prometheus metrics at /_cerbos/metrics by default, OTLP metrics and traces through OpenTelemetry environment variables (traces stay off until an endpoint is set). Audit logs record decisions to a file, Kafka or Cerbos Hub, and each response carries a cerbosCallId to join with your application logs.

A minimum dashboard: request rate and latency by API, error rate separately from deny rate, cache hit ratio, datastore latency for the ReBAC options, and a count of “conditional” or “CEL error” outcomes. A sudden rise in denies after a deploy is as much an incident as a rise in 5xx.

Security (verified advisories)

I pulled each project’s GitHub security advisories through the API on 14 October 2026 and, for every CVE ID below, confirmed one NVD record and an HTTP 200 on cve.org. Severity labels are GitHub’s.

OpenFGA (selected from 29 advisories)

ID

Severity

Affected

Fixed

Notes

CVE-2024-42473 / GHSA-3f6g-m4hr-59h8

High

v1.5.7, v1.5.8

v1.5.9

Check bypass with but not, from and a userset

CVE-2026-40293 / GHSA-68m9-983m-f3v5

Medium

v0.1.4 to v1.13.1

v1.14.0

Preshared key embedded in /playground HTML when the playground is enabled and reachable

CVE-2026-48096 / GHSA-8396-jffm-qx4w

Medium

up to v1.15.1

v1.16.0

Cache key collision when shared and ListObjects iterator caches are enabled

CVE-2026-55689 / GHSA-hcxc-wf8j-23hv

Medium

up to v1.17.1

v1.18.0

OIDC aud not validated when no audience configured; 1.18.0 refuses to start without one

CVE-2026-61709 / GHSA-g3pg-frfm-pr2m

Medium

up to v1.18.0

v1.18.1

ListUsers returns an excluded user in a specific intersection, exclusion and wildcard shape

GHSA-5278-rrxc-mgf7 (no CVE in API)

Medium

v1.15.0 to before v1.19.0

v1.19.0

StreamedListObjects returns an excluded object when the subtract side errors

GHSA-h7w8-xr72-cv4r (no CVE in API)

Medium

before v1.20.0

v1.20.0

ListUsers with nested exclusion over a wildcard

The pattern matters more than any row. Most OpenFGA advisories are wrong answers for particular model shapes, especially exclusions (but not) with wildcards or list APIs. For an authorization service a wrong “yes” is a security bug, and the project treats it that way. If you use exclusions and enforce with ListObjects or ListUsers, stay close to the latest release.

SpiceDB (selected from 18 advisories)

ID

Severity

Affected

Fixed

Notes

CVE-2023-29193 / GHSA-cjr9-mr35-7xh6

High

before 1.19.1

1.19.1

Preshared key visible through /debug/pprof/cmdline on an exposed metrics port when passed as a flag

CVE-2024-27101 / GHSA-h3m7-rqc4-7h9p

High

before 1.29.2

1.29.2

Integer overflow with more than 65,535 relationships of one kind; missed results or panic

CVE-2026-40091 / GHSA-jf4f-rr2c-9m58

Medium

1.49.0 to 1.51.0

1.51.1

Datastore DSN with password printed in the startup log at info

CVE-2026-55866 / GHSA-4vrg-r928-h5vv

Low

after 1.34.0, before 1.54.0

1.54.0

Under concurrency, a caveated permission could return HAS instead of CONDITIONAL

GHSA-5784-6qcr-48fq (no CVE in API)

Low

up to 1.54.0

1.56.0

in_cidr caveat did not match IPv4-mapped IPv6 addresses

GHSA-jf7w-wx43-32gc (no CVE in API)

Low

1.54.0 to before 1.56.2

1.56.2

Traces include serve arguments, so datastore secrets passed as flags leak; rotate credentials

SpiceDB’s pattern is different: secrets leaking through metrics, logs or traces when passed as command-line flags. Pass secrets through environment variables or files, keep metrics ports private, and rotate credentials after upgrading; the Vault and OpenBao post covers the secrets side.

Cerbos

The repository advisories API returned no entries, the GitHub advisory database had nothing for github.com/cerbos/cerbos, OSV returned nothing for the Go module, and NVD keyword search for “cerbos” returned zero results on 14 October 2026. I am not claiming any CVE for Cerbos. Two non-advisory items still matter: the default behaviour that skips an erroring DENY rule (use strictEvaluation), and the v0.56.0 fix for principal-policy deny rules ignored by the query planner. If you use PlanResources with principal-policy denies, upgrade.

Shared controls

Whatever you pick: keep admin, playground and metrics ports private; authenticate the API (preshared key or OIDC with an audience for OpenFGA, preshared key for SpiceDB, mTLS or a private network for Cerbos); disable telemetry if policy requires (--telemetry-endpoint="" for SpiceDB, CERBOS_NO_TELEMETRY=1 for Cerbos); and treat every non-allow, error or timeout as deny.

Decision guide

Situation

Prefer

Avoid thinking

Nested organisations, sharing, teams, folders, inheritance

OpenFGA or SpiceDB

“We will pass the whole hierarchy as attributes”

Rules over attributes already in your database and tokens

Cerbos

“We must copy every loan row into a tuple store”

Foundation governance is a procurement requirement

OpenFGA (CNCF Incubating)

Assuming every open-source project is foundation-owned

Multi-region, strict revocation, very large relationship sets

SpiceDB (with CockroachDB or Spanner)

“Eventually consistent is fine for authorization” by default

Small team, one database, mostly RBAC with some ABAC

Cerbos

Running a distributed ReBAC cluster for ten roles

List screens must filter in SQL

Cerbos PlanResources

Calling Check per row in a loop

List screens over relationship-driven access

OpenFGA ListObjects or SpiceDB LookupResources

Assuming list APIs are as battle-tested as Check; read the advisories

Need both relationships and rich policies

ReBAC for relationships, PDP or conditions for rules; keep the boundary written down

Three engines with overlapping rules

For the invented Nagpur SaaS: start with Cerbos for approval limits and risk rules, because the loan table already holds those facts and the banks want reviewable policy files. Adopt OpenFGA or SpiceDB only if cross-bank sharing and delegation grow into real graphs; then OpenFGA if CNCF governance and simpler operations matter more, SpiceDB if consistency controls and multi-region datastores matter more.

Checklist

  1. Write down which permissions depend on relationships and which depend on attributes before choosing a product.
  2. Pin exact versions (OpenFGA, SpiceDB, Cerbos tags) and verify release checksums in your build.
  3. Confirm the LICENSE at the tag you deploy (all three are Apache 2.0 today) and note which features are commercial-only.
  4. Decide your consistency requirement per API path, especially for revocation, and configure caches or ZedTokens to match.
  5. For ReBAC, design how relationship writes stay in step with business writes, including failure and replay.
  6. For Cerbos, decide where policies live (git, blob, database) and how quickly every PDP must pick up changes.
  7. Add model or policy tests to CI (fga model test, SpiceDB validation, cerbos compile), including missing-attribute cases.
  8. Make clients fail closed on errors, timeouts, conditional results and anything that is not an explicit allow.
  9. Keep playground, admin, metrics and pprof ports private; pass secrets by environment or file, never as flags.
  10. Re-read advisories on upgrade day, especially if you use exclusions, wildcards, list APIs or caveats.

Common mistakes

  1. Treating OpenFGA, SpiceDB and Cerbos as three interchangeable products and running a bake-off on a flat RBAC example that favours none.
  2. Moving every attribute into relationship tuples, then fighting stale copies of data you already had.
  3. Sending the whole organisation graph to a PDP on every request because “Cerbos is simpler”.
  4. Leaving the OpenFGA playground enabled on a reachable host (CVE-2026-40293).
  5. Running OpenFGA with OIDC but no audience on versions before 1.18.0 (CVE-2026-55689).
  6. Passing SpiceDB datastore URIs or preshared keys as command-line flags and exposing metrics, logs or traces (CVE-2023-29193, CVE-2026-40091, GHSA-jf7w-wx43-32gc).
  7. Treating SpiceDB CONDITIONAL_PERMISSION or an OpenFGA condition error as “allowed”.
  8. Relying on a Cerbos DENY rule whose condition can error on missing attributes, with strict evaluation off.
  9. Calling Check in a loop for list screens instead of using ListObjects, LookupResources or PlanResources.
  10. Using feature flags or UI button hiding as the authorization layer.

A drill I ran on 14 October 2026

Environment: shared Linux VM (x86_64), no Docker, no Kubernetes, no external database. Port 8766 left alone for the existing static file server. All drill servers bound to 127.0.0.1 on high ports and were stopped afterwards. Artefacts are under /workspace/revisit/drill38/. Times: about 6:28 AM to 6:33 AM IST.

What I did:

  1. Downloaded the official Linux amd64 archives for OpenFGA v1.22.0, the fga CLI v0.8.1, SpiceDB v1.56.2 and Cerbos v0.56.0; every SHA-256 matched the release’s checksums.txt.
  2. OpenFGA offline: fga model test on the model above with eight tuples passed 7 of 7 checks and 1 of 1 ListObjects. A copy with one deliberately wrong assertion failed with exit code 1, as CI needs.
  3. OpenFGA live (memory datastore, preshared key, playground off): /stores without the key returned 401. Checks gave the expected answers: officer can view, outsider cannot, manager can approve ₹3 lakh but not ₹9 lakh, blocked approver cannot. A Check without amount returned HTTP 400. /metrics returned 200.
  4. SpiceDB live (memory datastore, key from an environment variable, telemetry off): unauthenticated schema read returned 401. Using the write’s ZedToken as atLeastAsFresh, checks matched OpenFGA’s, and the check without context returned CONDITIONAL_PERMISSION. LookupResources returned the file; the permissionship counter showed 3 HAS, 3 NO and 1 CONDITIONAL.
  5. Cerbos tests: cerbos compile ran 20 tests, all passing. The default suite deliberately expects ALLOW for a file without risk_score (the erroring deny rule is skipped); the strict suite expects DENY. The default suite run with --strict-evaluation gave 9 passes and 1 failure.
  6. Cerbos live (disk storage, decision audit to file): CheckResources allowed ₹3 lakh, denied ₹9 lakh, and allowed the file without risk_score while logging Error evaluating CEL expression ... no such key: risk_score. PlanResources returned a conditional filter tree. /_cerbos/metrics returned 200.

What I did not run: no real datastores or replicas, no multi-node dispatch, no Kubernetes, no OIDC or TLS, no Cerbos Hub, no SDK code, no load test and no advisory reproduction. No latency or throughput number comes from this drill.

The useful finding is not speed but failure semantics: the same missing attribute produced an error in OpenFGA, a third state in SpiceDB and a silently skipped deny in Cerbos under defaults. That alone justifies a fail-closed client and a missing-attribute test, whatever you choose.

Unlearn

  • Unlearn the idea that “authorization service” is one product category. ReBAC stores and policy PDPs move different things.
  • Unlearn that a Zanzibar-style system is automatically consistent. Caches, replicas and consistency modes decide what you see.
  • Unlearn that CEL conditions behave the same everywhere. Missing data is an error, a third state or a skipped rule, depending on the product.
  • Unlearn hiding buttons in the UI as access control.
  • Unlearn passing secrets as command-line flags to infrastructure services.

Re-learn

  • Re-learn the Zanzibar vocabulary: objects, relations, subjects, tuples, and the new enemy problem.
  • Re-learn RBAC and ABAC as policy patterns you can express, review and test as code.
  • Re-learn version pins from GitHub releases, with IST timestamps and checksums recorded in your runbook.
  • Re-learn advisory reading for authorization engines: a wrong “yes” in a rare model shape is a security bug.
  • Re-learn that list filtering is part of authorization design, not an afterthought.

Closing

Authorization in 2026 is still a wording trap. OpenFGA and SpiceDB are Zanzibar-inspired relationship stores that walk a graph you keep inside them, OpenFGA under CNCF governance and SpiceDB with finer consistency control and heavier datastores. Cerbos is a policy decision point that keeps no business data and evaluates YAML and CEL rules over the facts you send. The Nagpur review becomes productive only after separating those jobs. Learn what each shape centralises. Unlearn three-way bake-offs on a toy RBAC example. Re-learn consistency, conditions and failure semantics on the exact version you deploy. Practise with a small model, a test file and a deliberately missing attribute on a loopback port. Apply the shape that matches where your permission data actually lives, and stop hard-coding the next permission check.

Sources

OpenFGA

SpiceDB

Cerbos

Background

Internal

comments powered by Disqus

Releted Posts

Data pipeline orchestration in 2026: revisit Apache Airflow, Dagster and Prefect before you schedule another DAG

Picture a two-wheeler loan company in Coimbatore with about ninety branches (an invented example, not a real client). Its data team of five runs about 140 cron jobs on two Linux VMs: shell scripts that pull collections files from a payment gateway SFTP, Python scripts that clean them, and SQL that loads a PostgreSQL reporting database every night.

Read more

Feature flags in 2026: revisit Unleash, Flagsmith and GrowthBook before you hard-code another release toggle

Picture a test-preparation and online classes platform in Jaipur (an invented example, not a real client). Students use an Android app, an iOS app and a React web app.

Read more

Lakehouse table formats in 2026: revisit Apache Iceberg, Delta Lake and Apache Hudi before you pick another format

Picture a payments and lending analytics platform in Hyderabad (an invented example, not a real client). Card authorisations and EMI schedules still land in PostgreSQL 16.

Read more