Skip to content
Corveek
All articles
Security · 4 min read

Two questions, not one: how a request gets authorized

"May this role edit expenses?" is only half an access check. The other half — which expenses — is where most systems leak.

Most permission systems answer one question: may this role perform this action on this kind of record? It is a necessary question and an insufficient one. Answer only that, and every member of staff who may edit an expense may edit everyone's expenses.

The second question

Corveek asks a second question on every request: which records? Each grant carries a scope. "Own" means the records you created — your expenses, your leave requests, your drafts. "All" means everything in the organization, which is what a manager or an administrator gets. Where someone holds several roles, the widest scope wins.

Enforced where it cannot be skipped

The scope filter is applied in the shared data-access layer, not in individual endpoints. That placement is the whole point: a developer adding a new screen cannot forget to apply it, because there is no path to the data that bypasses it.

The consequence is visible from outside. Ask for a record id you do not own and you get "not found" — not "forbidden". A refusal that distinguishes the two confirms the record exists, which is precisely the fact an attacker was fishing for.

The interface is not the boundary

Buttons, menu items, fields and whole navigation groups are hidden when your roles do not allow them. That is a courtesy, not a control. Every one of those actions is checked again on the server, because a hidden button is still a URL.

The default is deny. A permission that has not been explicitly granted does not exist, and new modules inherit that default rather than opting into it.

See how this works on your data

We will set up a workspace with your modules and roles, and show you the parts that matter to your team.

Keep reading