WardenAuthAgent Security
PricingDocsCompareBlogLearnChangelog
Sign inGet started free
All posts
Deep Dive
July 10, 2025
11 min read

How to Prevent Role Explosion in RBAC (Without Switching to ABAC)

When role names start encoding regions, thresholds, and ownership, you have role explosion. Here are three techniques — resource-scoped permissions, role composition, and promoting data to attributes — to keep an RBAC model maintainable at scale.


Role explosion is the slow death of an RBAC system. It starts innocently: you add a manager role. Then finance needs a manager who can only see their own region, so you add manager-us-east. Then a spending cap, so manager-us-east-under-10k. Eighteen months later you have 1,400 roles, most assigned to exactly one person, and nobody can answer “what can this user actually do?” This post is about preventing that — while staying in RBAC.

What Actually Causes Role Explosion

Role explosion has a single root cause: encoding data into role names. A role should represent a job function (“support agent”), not a data slice (“support agent for enterprise accounts in EMEA”). Every time a region, a dollar threshold, or an ownership rule ends up in a role name, you have multiplied your role count by the cardinality of that dimension.

Diagnostic test: if your number of roles is growing roughly linearly with your number of users, you have role explosion. Healthy RBAC systems have far fewer roles than users.

Fix 1: Resource-Scoped Permissions and Wildcards

Most role duplication comes from resource granularity. Instead of a role per resource instance, use permissions that match patterns. A wildcard permission collapses hundreds of explicit grants into one:

typescript
// Instead of one role per project...
// role: "editor-project-a", "editor-project-b", "editor-project-c" ...

// Use a single role with a wildcard permission:
const editorRole = {
  id: 'editor',
  permissions: [
    { resource: 'project:*', action: 'read',   effect: 'allow' },
    { resource: 'project:*', action: 'update', effect: 'allow' },
    { resource: 'project:*', action: 'delete', effect: 'deny'  }, // deny-wins guardrail
  ],
}

One editor role now covers every project, and the explicit deny on delete holds even if another role grants it.

Fix 2: Role Composition

Assign multiple small, orthogonal roles to a subject instead of minting one combined role per permutation. A user who is both a billing viewer and a support agent gets two roles — not a new billing-viewer-support-agent role. The permutations live in the assignment, not the role catalog.

typescript
// Additive assignment — permissions are the union of all assigned roles.
await assignRoles(scopeId, userId, ['support-agent', 'billing-viewer'])

Fix 3: Promote Data Dimensions to Attributes

When a dimension is truly dynamic — the user’s region must equal the resource’s region — it does not belong in a role at all. Promote it to an attribute-based condition layered on top of the role. This is the hybrid RBAC + ABAC model we cover in our RBAC vs. ABAC deep-dive.

typescript
function canApprove(req) {
  // Coarse RBAC gate:
  if (!hasPermission(req.subject.id, 'invoice', 'approve')) return false
  // Fine-grained condition instead of a "manager-<region>" role:
  return req.subject.region === req.resource.region
}

A Rule of Thumb

  • Job function → role.
  • Resource pattern → wildcard permission.
  • Combination of duties → multiple role assignments.
  • Runtime data comparison → attribute condition.

How WardenAuth Helps

WardenAuth supports resource- and action-level permissions, wildcard matching (project:*, *:read), additive multi-role assignment, and deny-wins semantics out of the box — the exact primitives that keep a role catalog small. You model job functions once and let patterns and assignments handle the combinatorics.

Start free at wardenauthz.com/register and model your first scope in minutes.


Back to blogTry WardenAuth free →
© 2026 ecarrizo. All rights reserved.
PricingDocsCompareBlogLearnChangelogStatusGlossaryContactTermsPrivacy