SwadhyAI Notes

All System Design notes

System Design Security

Access Control Models

A contractor opens a shared folder and should see three design docs, not the payroll spreadsheet sitting two clicks away. Login already succeeded. The remaining question is which model computes that boundary on every read, share, and export.

, , , and answer the same triplet—, action, resource—with different data shapes. Pick the shape that matches how sharing actually works, then make deny the default on every enforcement path.

Authorization journey

1

User, group, or workload—not the IP.

2

Stable verbs on a resource class.

3

Choose the model

List, role, attributes, or graph.

4

No silent bypass around the PDP.

What actually happens

Every request that reads or mutates a protected object should hit a . The PEP asks a : may this perform this action on this object, given current facts? Facts come from role membership, object ACLs, attribute stores, or relation . The answer must be allow, deny, or a required extra proof—not “maybe, the UI hid the button.”

List endpoints are the usual failure: a detail page checks ownership, then GET /docs returns every tenant’s titles because the collection query never applied the same model. Search, exports, admin impersonation, and background jobs are the same class of hole.

Revocation is part of the model. Removing a role, deleting a tuple, or flipping an attribute is incomplete until every cache key that included the old is invalidated or expires within a budget you can defend in an incident review.

Naive design, and the invariant that must hold

Naive claim

“Hide the button in the frontend, store isAdmin on the user row, and add a new role whenever finance wants an exception.”

Counterexample

A mobile client still calls the refund API. Search returns other tenants’ invoices. Six months later you have 400 overlapping roles, nobody can answer “why did this grant work,” and a departed contractor’s group membership still expands into production folders.

Correctness invariant

What must never become false

Every read and mutation of a protected object is authorized with the same model, including lists, jobs, and admin tools. Deny is default. Decisions name a . Revoking a grant cannot leave an unbounded cached allow.

How to choose a model

1. How is sharing described in the product? Org charts favor . Per-document invites favor or . Compliance rules over region and clearance favor .

2. Which questions must be fast? “Can Alice read Doc D?” is a point check. “List every doc Alice can read” needs a reverse index or a carefully bounded expansion. ACL-on-object without a subject index fails that query at scale.

3. How fast must revoke land? Cached role bits in a session cookie can outlive a firing. Graph tuple deletes need cache keys that include object, relation, and snapshot.

Simple rule of thumb

Start with RBAC for coarse product surfaces. Add ReBAC when objects nest and get shared independently. Add ABAC constraints for environment and data-class rules that should not become new roles. Keep ACLs as the leaf grant when a human explicitly shares one object. Hybrids are normal; four disconnected systems are not.

The four models, side by side

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart LR
    subgraph ACL["ACL"]
        R1[Resource] --> L[Per-object grant list]
        L --> D1[Direct allow or deny]
    end
    subgraph RBAC["RBAC"]
        U[User] --> Role
        Role --> Perm[Permission bundle]
        Perm --> D2[Coarse decision]
    end
    subgraph ABAC["ABAC"]
        Attr[Subject resource environment] --> Rule[Policy rules]
        Rule --> D3[Contextual decision]
    end
    subgraph ReBAC["ReBAC"]
        T[Relation tuples] --> G[Sharing graph]
        G --> D4[Reachability check]
    end
ModelStoresShines whenBreaks when
ACLGrants on the objectFew owners, explicit shares, POSIX-like files“What can Alice see?” has no reverse index; lists grow huge
RBACUser → role → permissionStable job functions, auditable bundles from per-exception roles
ABACAttributes + policy docsContext, tenancy, geo, time, data classificationUnreadable policies, missing attributes fail open
ReBACRelation tuples + namespace configFolders, orgs, teams, inherited viewersDeep graphs, cycles, fan-out groups, slow list queries

Decision path at request time

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
sequenceDiagram
    autonumber
    participant Client
    participant PEP as Enforcement point
    participant Cache as Decision cache
    participant PDP as Policy engine
    participant Store as Roles / tuples / attributes
    participant Audit

    Client->>PEP: Action on resource
    PEP->>Cache: Lookup principal, resource, action, policy version
    alt fresh cache hit
        Cache-->>PEP: Allow or deny
    else miss or high-risk
        PEP->>PDP: Evaluate request
        PDP->>Store: Load roles, attributes, or relation tuples
        Store-->>PDP: Facts
        PDP-->>PEP: Decision, reason, policy version
        PEP->>Cache: Store with TTL and version key
    end
    PEP->>Audit: Actor, action, resource, outcome, reason
    alt allowed
        PEP-->>Client: Forwarded result
    else denied
        PEP-->>Client: 403 with stable error code
    end

Cache keys must include principal, resource, action, tenant, and . A five-minute allow cache is a five-minute revoke delay. High-risk actions—payouts, role grants, export of a whole workspace—should skip long caches or re-check live.

Missing attributes in ABAC are a design choice you must name: fail closed for writes and sensitive reads, fail closed for unknown tenants, never fail open because a catalog was empty.

RBAC in production: roles as products

Treat a role as a versioned product: name, owner, permission set, and review cadence. Prefer a handful of job-function roles plus scoped grants over “custom role per ticket.” Bind roles in a tenant: editor@acme is not global editor.

belongs here early. If one role can both submit and approve a wire, the model already lost. Encode exclusions in the PDP, not in a wiki. Time-bound elevation (just-in-time admin) is still RBAC—the role membership has a TTL and an approver, and the audit row must show both.

Sizing sketch: 50k employees × 8 roles each average is 400k memberships, trivial to store. The danger is 8,000 distinct roles with almost-identical permission bitmaps. Count distinct permission sets, not headcount, when you claim RBAC “does not scale.”

ReBAC checks: walk the graph, bound the work

Diagram text: the exact Mermaid definition below preserves the nodes, labels, and connections. Enable JavaScript to view the rendered diagram.
Read the diagram definition
flowchart TD
    Q["Check: Alice can view Doc D?"] --> P["Doc D#viewer includes Alice?"]
    P -->|direct tuple| Y[Allow]
    P -->|via parent| F["Folder F#viewer includes Alice?"]
    F -->|group| M["Eng#member includes Alice?"]
    M --> Y
    F -->|no path| N[Deny]
    P -->|no path| N

A check asks whether a userset expansion from the object’s relation includes the subject. Namespace config defines rewrite rules:viewer = owner + parent.viewer + viewer. Direct tuples are O(1) lookups; inherited viewers require parent walks and group expansion.

Bound depth and fan-out. A group of 80k members used as folder viewer must not expand fully on every document check—store reverse indexes or check membership of the subject in candidate groups instead. Detect cycles; treat them as deny plus an ops alert, not as a hang.

List-objects is a different API from check. Interviews that only describe check will fail a drive-share product: you still need a way to page “docs Alice can open” without scanning every object in the tenant.

ABAC without becoming unreadable

Good ABAC policies are small, named, and tested. Bad ABAC is a 2,000-line document nobody can simulate. Keep attributes sourced from systems of record (HR, inventory, data catalog) with freshness SLAs. A stale “contractor=false” is an authorization bug, not an HR inconvenience.

Combine ABAC with RBAC rather than replacing it: roles select the feature; attributes constrain where and on which data class it applies. That pairing stops without forcing every button into a custom policy language.

Worked numbers

2 million docs, average ACL of 12 entries, no subject index: listing Alice’s docs scans 2e6 × 12 = 24 million grant rows. With a subject-to-object index, the same query is Alice’s grant set (say 400) plus ReBAC expansion of her groups (say 20 groups × 50 docs).

Point checks at 40k QPS with p99 5 ms: a remote PDP hop of 3 ms leaves 2 ms for local PEP work. Cache hits must dominate; measure hit ratio by risk class, not a single global number. If 1% of checks miss and each miss loads 15 tuples, budget the tuple store for ~6k complex lookups/sec at peak, not for the full 40k.

Policy rollout: dual-run new version in shadow, compare allow/deny diffs, then flip . A 0.2% unexplained allow regression on 40k QPS is 80 extra allows per second—stop the rollout.

Trade-offs and failure modes

Explainability vs expressiveness. RBAC is easy to audit; ABAC and deep ReBAC explain “why” only if you persist reason traces. Interviews should say how a support admin reconstructs a grant.

Consistency vs latency. Strongly consistent tuple writes make share-then-open work; eventual membership can briefly leak or briefly 403. Pick per action: share links may tolerate seconds; payroll must not.

Cache TTL vs revoke. Longer caches cheaper and smoother; they are also the window after a contractor is removed. Document the window per data class.

Central PDP vs local evaluation. Central is consistent and observable; local compiled policy is faster and survives PDP brownouts if you accept stale snapshots. Fail closed on snapshot expiry for writes.

UI hiding vs enforcement. The UI is not a PEP. Clients are hostile. Tests must call APIs with stolen object ids.

Still fails when…

  • Search, analytics, and “export all” skip the model.
  • Admin impersonation has no ticket id or TTL.
  • Group expansion has no cycle or fan-out limit.
  • Deny lacks a stable reason code, so product retries forever.

What to measure

  • Check latency, cache hit ratio, and revoke propagation time.
  • Shadow-policy disagreement rate during rollouts.
  • Count of distinct roles and unused permissions.
  • Authorization denials that later succeeded via a bypass route.

Choosing and explaining access control models

A complete explanation connects the mechanism to a requirement. Describe what improves when you introduce it, which new failure cases become possible, and how you would know that the trade-off is worthwhile.

Explain the core idea

Authorization is a per-request decision over principal, action, and resource, not a login flag.

ACL, RBAC, ABAC, and ReBAC store the same decision in different shapes; hybrids are common.

Enforcement must cover lists, jobs, and admin paths, and every decision should carry a policy version.

When it belongs

More than one actor can touch the same objects, especially across tenants or shared folders.

You need explainable grants, SoD, or time-bound elevation.

Revoke and “what can this user see?” are product requirements, not afterthoughts.

How it fails

Role explosion from encoding every exception as a new role.

Collection endpoints that skip object-level checks.

Cached allows that outlive membership or tuple deletes.

Unbounded ReBAC expansion or ABAC fail-open on missing attributes.

Trade-offs to weigh

RBAC simplicity versus ABAC/ReBAC expressiveness and operational cost.

Decision cache speed versus revoke delay.

Central PDP consistency versus local evaluation during brownouts.

Point-check latency versus list-objects completeness.

Common misunderstandings

Treating frontend routing as authorization.

Confusing authentication success with authorization.

Saying ReBAC is “just RBAC with groups” without check vs list-objects.

Designing only the allow path and ignoring default deny and reason codes.

Wrap-up

Choose the model that matches how people actually share, enforce it on every path, and keep grants versioned and revocable. Roles, attributes, lists, and graphs are tools—not competing religions.

Key takeaway

Access control is a data model plus an enforcement path. Match ACL, RBAC, ABAC, or ReBAC to the product’s sharing shape, default deny, version every decision, and never let search or jobs skip the check the detail page performs.