From harness-claude
Designs RBAC authorization: define resource:operation permissions, group into roles for users. For new apps, refactoring ad-hoc perms, multi-tenancy, auditing least privilege.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Assign permissions to roles, assign roles to users -- simple, auditable, and sufficient for most applications when combined with resource-level checks
Guides selection and implementation of authorization models including RBAC, ABAC, ACL, ReBAC, and policy-as-code for permission systems and access control design.
Implements Role-Based Access Control (RBAC), permissions management, and authorization policies. Guides for Node.js, Python ABAC, Java Spring Security. Use for multi-tenant apps, APIs, admin dashboards.
Share bugs, ideas, or general feedback.
Assign permissions to roles, assign roles to users -- simple, auditable, and sufficient for most applications when combined with resource-level checks
Authorization failures are the number one vulnerability category in the OWASP Top 10 (A01:2021 -- Broken Access Control), having risen from fifth place in the 2017 edition. The category encompasses multiple distinct attack classes:
The 2019 Capital One breach exploited a misconfigured IAM role that had overly broad S3 access permissions, exposing 106 million customer records. The 2021 Parler data scrape was possible because the API lacked authorization checks on content enumeration -- sequential IDs with no ownership verification allowed downloading every post. The 2023 Microsoft Power Platform vulnerability exposed customer data because an API endpoint checked authentication (is the user logged in?) but not authorization (does the user have permission to access this specific tenant's data?).
A well-designed RBAC system with enforced resource-level checks eliminates these attack classes by ensuring that every operation is checked against both the user's permissions (what operations can they perform?) and the resource's ownership (do they have access to this specific resource?).
Start with the permission model, not the roles.
Identify the resources in your system (users, orders, projects, documents, invoices, reports) and the operations that can be performed on them (create, read, update, delete, approve, publish, export, archive).
Define permissions as resource:operation pairs:
orders:read, orders:create, orders:approveusers:manage, users:readreports:export, reports:createBe granular enough to enforce least privilege (separating orders:read from orders:approve prevents a viewer from approving orders) but not so granular that the model becomes unmanageable (do not create separate permissions for every field of every resource).
Define roles as named collections of permissions. A role is a set of permissions that corresponds to a job function. Common starter roles for a business application:
viewer: Read-only access to relevant resources (orders:read, reports:read)editor: Read and write access (orders:read, orders:create, orders:update)manager: Editor permissions plus approval capabilities (orders:approve, users:read)admin: Full access to the application's resources (users:manage, settings:update, plus all lower permissions)owner: Admin plus billing, subscription, and organization-level settingsPermission checks evaluate: "Does the union of the user's assigned roles include the required permission for this operation?"
Apply the principle of least privilege rigorously. Every user, service account, API key, and automated process should have the minimum permissions required for its current function -- nothing more. Default to no access and grant permissions explicitly through role assignment.
When an employee changes roles, revoke the old role and assign the new one; do not accumulate roles. Review role assignments quarterly and after every organizational change. Dormant accounts (no login in 90 days) should be flagged for review and potential deactivation.
Implement role hierarchies with explicit documentation. Role inheritance (admin inherits all manager permissions, manager inherits all editor permissions, editor inherits all viewer permissions) reduces duplication and simplifies assignment.
However, hierarchies create implicit permissions that are difficult to audit -- when an admin inherits from a chain of four roles, the effective permission set is not immediately visible. Mitigate this by:
Add resource-level checks (object-level authorization).
RBAC roles alone answer the question "Can this user perform this operation type?" but not "Can this user perform this operation on this specific resource?"
A user with orders:read permission must only be able to read orders that belong to them or their organization, not all orders in the system.
Implement ownership checks on every data access:
user.hasPermission('orders:read') AND order.belongsTo(user.organization)
This prevents IDOR vulnerabilities, which are the most common authorization flaw in web applications. The permission check and the ownership check are both mandatory -- neither alone is sufficient.
Enforce authorization at the API layer, not the UI layer. Hiding a button in the frontend is a UX decision, not a security control. Every API endpoint must independently verify that the authenticated user has the required permission for the requested operation on the requested resource.
Assume that attackers will call your API directly using curl, Postman, or custom scripts -- they will never see your UI restrictions. UI visibility should mirror the permission model (do not show buttons the user cannot use) but the UI must never be the only enforcement mechanism.
Scope roles to tenants in multi-tenant systems.
In SaaS applications, a user may be an admin in Organization A but a viewer in Organization B.
Model this with role assignments that include a tenant scope: (user_id, role_id, tenant_id) triples.
Permission checks must include the tenant context:
user.hasRole('admin', currentOrganization)
Never allow a role in one tenant to grant access to resources in another tenant. Tenant isolation is a hard boundary -- cross-tenant access is a critical vulnerability, not a feature.
The NIST RBAC standard (INCITS 359-2012) defines four levels of increasing sophistication:
Most production applications need RBAC1 with selective RBAC2 constraints for sensitive operations (financial approvals, user administration, data deletion).
As features and teams grow, organizations create new roles for every combination of permissions: marketing-editor, marketing-viewer, sales-editor, sales-viewer, marketing-sales-editor, marketing-admin-except-delete.
This combinatorial explosion makes the role model unmanageable, unauditable, and error-prone.
Solutions to role explosion:
marketing-editor-with-export, assign the editor role plus the reports:export permission directly.editor, viewer) that are scoped to a context (module, department, project). The role definition is reusable; the scope limits its application.Critical operations should require two distinct roles held by two distinct users.
Example: the person who creates a payment (role: payment-creator) must not be the same person who approves it (role: payment-approver).
Two implementation approaches:
RBAC is sufficient when authorization decisions depend primarily on who the user is and what their job function is.
ABAC (Attribute-Based Access Control) is needed when decisions depend on multiple attributes:
The diagnostic signal for needing ABAC: if you find yourself creating roles that encode attribute combinations (e.g., us-east-daytime-senior-analyst), you are encoding policy in role names.
Switch to ABAC and express the policy as rules over attributes.
ReBAC (Relationship-Based Access Control) models authorization as relationships in a graph. Instead of assigning roles, you define relationships: "User X is an editor of Document Y," "Team A owns Project B," "Organization C is the parent of Team A."
ReBAC is appropriate when:
Systems like Google Zanzibar (and its open-source implementations: SpiceDB, OpenFGA) implement ReBAC with global consistency and low-latency evaluation.
Implement authorization as middleware or decorators that execute before the business logic:
app.get('/orders/:id', authorize('orders:read'), checkOwnership, handler)@permission_required('orders.view_order') decorator plus object-level check in the view@PreAuthorize("hasPermission(#id, 'Order', 'read')") annotation with a custom PermissionEvaluatorThe authorization check must happen on every request, at the framework level, before any business logic executes.
The "god role" with all permissions and no audit trail.
A single super-admin or root role that bypasses all permission checks entirely.
If this role's credentials are compromised, the attacker has unlimited, unlogged access to every resource.
Instead: even the highest-privilege role should be subject to permission checks and full audit logging.
Administrative actions should require MFA step-up authentication.
Permission checks only in the UI. Hiding buttons and menu items in the frontend without corresponding server-side checks. Attackers use API clients (curl, Postman, custom scripts) and never see the UI. Every API endpoint must independently verify authorization.
Hardcoded role names in business logic.
Code like if (user.role === 'admin') scattered throughout the codebase.
This couples the authorization model to the codebase, making role changes and additions extremely fragile.
Check permissions, not role names: if (user.hasPermission('orders:approve')).
This allows role definitions to evolve without code changes.
No resource-level checks (IDOR vulnerability).
Verifying user.hasRole('editor') but not verifying that the user has access to the specific resource being edited.
This enables horizontal privilege escalation: User A edits User B's order by changing the order ID in the API request.
The role check passes (User A is an editor), but the ownership check is missing.
Roles that only grow, never shrink. Over time, roles accumulate permissions as features are added, but permissions are never removed when features are deprecated or projects end. This violates least privilege through entropy. Audit roles quarterly: review every permission in every role, remove permissions that are no longer needed, and document the rationale for each permission that remains.
Implicit deny not enforced as default. Authorization systems that default to "allow" when no rule matches, rather than defaulting to "deny." The safe default is always deny -- if there is no explicit permission grant, access is denied. This prevents new endpoints or resources from being accidentally accessible before permission rules are written.
No audit logging of authorization decisions. Authorization decisions (both grants and denials) must be logged with the user identity, the requested resource, the requested operation, the decision (allow/deny), and the reason (which permission or lack thereof). Without audit logs, detecting privilege escalation, investigating breaches, and demonstrating compliance are impossible.