skip to content

A login account is a member of role A, which is itself a member of role B, and B holds SELECT on a table. How does the engine decide whether that login may read the table, and could a later grant ever take access away?

level: middleimportance: should knowfreq 40%

answer

  1. transitive union over membership edges
  2. plus PUBLIC, plus owner-implicit rights
  3. inherit automatically vs assume with a role switch
  4. grant-only: nothing subtracts
  5. review = graph reachability, not a lookup

basics

~20 s

Effective privileges are the union of what the account holds directly, everything reachable transitively through role membership, and everything granted to PUBLIC. Standard SQL grants are additive with no deny, so a new grant can never remove access -- only REVOKE or dropping a membership can.

solid answer

~50 s

The engine computes the transitive closure of role memberships and unions the privileges found along the way: the account's own grants, plus A's, plus B's, plus anything granted to PUBLIC. If SELECT appears anywhere in that closure, the read is allowed. One caveat: some engines distinguish *inherited* membership (privileges apply automatically) from membership that must be activated with a SET ROLE-style switch. In the latter case, holding the membership is not the same as using it, and the account must assume the role first. Because the model is grant-only -- there is no DENY in standard SQL -- adding a grant anywhere can only widen access. Removing it requires an explicit REVOKE on the right edge or removing the membership. That is exactly what makes access review hard: 'who can read this table' is a graph reachability question, not a lookup, and a direct-grants-only review under-reports.

go deeper

for a junior

State that privileges accumulate through role membership and that grants only add, never take away.

for a middle

Describe the transitive union including PUBLIC, and note the inheriting versus assumable membership distinction.

for a senior

Discuss access review as reachability, privilege creep through nesting, and revoke-plus-terminate when cutting off access.

for a principal

Constrain the graph by design -- shallow, domain-scoped group roles -- and make effective-access reporting a standing capability rather than an incident-time query.

## Roles as a graph A role in a relational engine is both a subject (something that can hold privileges) and a group (something other roles can be members of). Membership edges form a directed graph, usually acyclic because engines refuse to create membership cycles. When a session runs a statement, the engine needs the **effective privilege set** for the current authorization identifier. It computes it as the union of: 1. privileges granted directly to the account; 2. privileges granted to every role reachable from the account through membership edges, transitively -- so membership in A that is a member of B pulls in B's privileges; 3. privileges granted to PUBLIC, which every role holds implicitly; 4. implicit rights the account has as the **owner** of an object, which are not stored as grants at all. If the required privilege appears in that set, the statement proceeds. ## Inheritance versus assumption There are two flavours of membership and they behave differently: - **Inheriting membership**: the member automatically uses the role's privileges in every statement. Convenient, and the usual choice for grouping application permissions. - **Non-inheriting membership**: the member may *become* the role via a role-switch statement, but does not use its privileges otherwise. This is the model behind 'you have admin rights but must explicitly step into them', and it is valuable for privileged roles because it makes elevation deliberate and auditable. An interviewer probing this will ask why you would ever want the second. The answer is blast-radius control: a compromised or careless session running as the login role cannot accidentally exercise the powerful role's privileges without an explicit act. ## Additive semantics: no DENY Core SQL privileges are monotone: every grant adds, nothing subtracts. That has several consequences worth stating: - You cannot carve an exception into a broad grant. If a role can read a table, you cannot deny it one column by adding something; you must revoke the broad grant and re-grant narrowly. - Adding a role membership for convenience is irreversible in effect until someone revokes it -- there is no override to fence it. - Some engines add an explicit DENY that overrides grants, which changes the algebra (precedence rules, deny-wins) and is a vendor-specific extension rather than the portable model. Row-level policies are also a separate mechanism layered on top rather than a negative grant. ## Practical consequences **Review is a reachability problem.** Answering 'which accounts can read the customers table' means walking the membership graph backwards from every role holding the privilege, and remembering PUBLIC and ownership. Tools and hand-written queries that list direct grants systematically under-report. Any organisation with nested roles needs a canonical query or report that computes the closure. **Chains drift.** Nesting roles is how privilege creep happens: a role created for one purpose is made a member of a broader role 'temporarily', and everything downstream silently gains the broader set. Depth is the enemy; two levels (a group role per data domain, service accounts as members) keeps the closure readable. **Timing.** Privilege changes generally take effect for statements executed after the change, but a session already inside a transaction, or an execution plan already prepared, may not observe the change immediately. If you are cutting off access urgently, revoke *and* terminate the sessions. ## Answering well Say the algorithm (transitive union, plus PUBLIC, plus ownership), name the inheritance-versus-assumption distinction, state that the model is grant-only so a new grant cannot revoke, and land on the operational consequence: access reviews must compute the closure, and revocation must target the right edge in the graph.

  • How would you answer 'which accounts can read this table' in a system with nested roles?
    Start from the set of roles holding the privilege on that table, then walk membership edges backwards transitively to every login account that reaches one of them, and add every account if the privilege is held by PUBLIC. Include the object's owner, whose rights are implicit rather than granted, and include access reachable through views owned by privileged roles. A report that lists only direct grants will miss most of the real answer.
  • You need to cut off a compromised account's access to a table immediately. What do you do?
    Revoke on the correct edge -- which may mean removing a role membership rather than a direct grant, and checking for PUBLIC grants and other grantors -- and then terminate the account's existing sessions, since a session already running may hold state or an in-flight transaction. If the account is an owner, revoking grants will not help and you must change ownership or disable the login itself.

saying these in an interview costs you the question

  • Believing a new grant can restrict access
  • Ignoring PUBLIC when computing who has access
  • Treating role membership as automatically active in engines that require assuming the role
  • Reviewing access from direct grants only in a nested-role system
  • Assuming a revoke instantly affects sessions already open

context