Permission System Design
Permission system models including ACL, RBAC, ABAC, DAC, and MAC.
A permission system answers one question repeatedly and under pressure: may this principal perform this action on this resource in this context? A good design makes that answer consistent, explainable, and cheap to evaluate. A bad design spreads ad hoc checks across handlers and background jobs until nobody can predict who can do what.
The core model usually has four parts: principals such as users or service accounts, resources such as documents or projects, actions such as read, edit, or delete, and policies that bind them together. In a simple application, a permission check might be a table lookup. In a large system, it becomes a policy decision point with caching, inheritance, and audit logging.
ACLs, or access control lists, attach rules directly to each resource. A document might say Alice can edit, Bob can read, and everyone else is denied. ACLs are easy to reason about for one object, which is why filesystems and collaboration products use them heavily. The problem appears at scale. If you need to answer "what can Alice access across ten million documents?" you are now searching many object-level lists or maintaining an expensive reverse index.
DAC, or discretionary access control, gives owners the power to grant access to others. It is flexible and familiar because Unix file ownership works roughly this way. The weakness is that owners can accidentally widen access, and ownership chains often become messy in shared workspaces.
MAC, or mandatory access control, shifts control away from owners and toward centrally defined labels. A subject with one clearance level cannot read or write data with incompatible labels even if an object owner wants to allow it. This is strong and predictable, which is why military and high-assurance environments value it. The tradeoff is low flexibility. Ordinary product teams usually find MAC too rigid for daily collaboration workflows.
RBAC, or role-based access control, groups permissions into roles such as viewer, editor, or billing-admin, then assigns roles to principals. This compresses policy management because you update the role once instead of changing every user individually. RBAC works well when job functions are stable. It fails when roles start encoding every exception. Once you have regional-finance-approver-eu-weekend, the role model is carrying context it was never meant to hold.
ABAC, or attribute-based access control, evaluates rules from attributes of the principal, resource, action, and environment. A rule might allow access if the user belongs to the same tenant, the document classification is internal, and the request comes from a managed device during office hours. ABAC is expressive, but that power creates operational cost. Policy evaluation becomes harder to explain, test, and cache. Attribute freshness also matters. If device posture or group membership changes, stale cached decisions can become security bugs.
Real systems often combine these models. RBAC may grant a base role, ACLs may handle per-document sharing, and ABAC conditions may restrict access by tenant or geography. The design question is not which acronym wins. It is where each rule belongs and how conflict resolution works. Deny-overrides versus allow-overrides is not a cosmetic choice. It decides what happens when one policy says yes and another says no.
Two implementation details matter more than many diagrams admit. First, permissions should be evaluated through a central service or library with the same semantics everywhere, including background jobs and internal APIs. Second, every decision should be explainable after the fact. Auditors, support engineers, and developers need to know not only that access was denied, but which rule caused it.
A permission system succeeds when it makes safe access the default, exceptional access deliberate, and policy changes survivable as the product grows.