Engineering
Resolving twenty-five permission keys on every request
The safest permission check is the one you cannot skip. We moved every decision to the server and resolve the full key set on each request — here is why the overhead is worth it.
8 min read
A tempting shortcut in any multi-tenant app is to compute a user's permissions once, at login, and cache them on the client. The UI hides what the role cannot do, and everyone moves on. The problem is that the client is not a security boundary — it is a suggestion. Anything gated only in the browser is gated for honest users and no one else.
Every request, from scratch
So Sillops does the unglamorous thing. Each request arrives with an identity, names the resource and action it wants, and the server resolves the relevant keys from the 25-key catalog against that user's roles in that specific organization. Nothing is trusted from the client, and nothing is assumed from a previous request.
The catalog is deliberately small and legible — keys like people.leave.approve or finance.invoice.edit read like sentences. Small enough to reason about, specific enough that a role grant means something precise.
Isn't that slow?
The check is a handful of indexed lookups against data we already have warm, so the per-request cost is measured in microseconds, not milliseconds. Roles and grants are cached; the decision is not. That distinction is the whole game — we cache the inputs and recompute the answer, so revoking a role takes effect on the very next request instead of whenever a token happens to expire.
The same resolver runs whether the caller is the console, the public API, or an assistant. One code path, one place to get authorization right, one place to audit. When there is exactly one door, you can afford to make it a very good door.