Enforcer
Permit.io wraps a policy engine in a UI and has recently put an MCP gateway on its homepage. People usually look elsewhere for one of three reasons: they want to self-host without the control plane, they do not want another service in the request path, or they do not want to sync their data into a permissions store.
Where does the check run, and what do you have to keep in sync? Almost every difference that annoys you in month three comes from those two.
| Option | Model | Strength | Trade-off |
|---|---|---|---|
| Cerbos | Policy, YAML | Self-host, stateless, three-command start | List filtering is weak |
| OpenFGA | Relationship | Open source, CNCF | Dual writes into a permissions store |
| SpiceDB | Relationship | Battle-tested at scale | Same dual-write cost |
| Oso Cloud | Policy, Polar | Good docs and tour | Open-source library deprecated; cloud only |
| WorkOS FGA | Relationship plus roles | Bundled with sign-in | Resources registered in their system |
| Enforcer | Policy over a resolved caller | Identity and authorization in one system | No list filtering today |
Most of the above are a decision layer: you hand them a subject id and they answer true or false, which means they need a copy of your data to answer at all. Enforcer already holds identity, so a rule reads a real user object and there is nothing to keep in step.
It also means a group can be earned rather than assigned. Pass an identity check and you land in a group, and that group is what the rule reads, so a denial can say what would make it an allow instead of returning false.