Skip to content

feat(permissions): client-side RBAC capability gating across the UI#3241

Open
rafavalls wants to merge 13 commits intomainfrom
rafavalls/permissions-audit
Open

feat(permissions): client-side RBAC capability gating across the UI#3241
rafavalls wants to merge 13 commits intomainfrom
rafavalls/permissions-audit

Conversation

@rafavalls
Copy link
Copy Markdown
Collaborator

@rafavalls rafavalls commented Apr 30, 2026

What is this contribution about?

This PR introduces a proactive client-side permissions layer that mirrors the server-side AccessControl logic. Custom-role users now see disabled/hidden UI and clean empty states for sections they can't access, rather than hitting API errors. The core primitives are a useCapability / useCapabilities hook, a RequireCapability route-level guard, a PermissionTooltip wrapper for inline disabled states, and a NoPermissionState empty-state component. Capability checks are applied across settings pages, connections, agents, automations, monitoring, and AI providers. Server-side AccessControl also received a small fix to always grant basic-usage tools regardless of role.

Screenshots/Demonstration

UI-only change — capability-gated sections show a clean "You don't have permission" empty state for restricted roles, and action buttons show a tooltip explaining the restriction on hover.

How to Test

  1. Create a custom role with a limited permission set (e.g. no connections:manage).
  2. Assign that role to a member and log in as that member.
  3. Navigate to Connections — expect a no-permission empty state instead of an error.
  4. Hover over any disabled action button — expect the permission tooltip to appear.
  5. Log in as owner/admin — all sections should remain fully accessible.

Review Checklist

  • PR title is clear and descriptive
  • Changes are tested and working
  • Documentation is updated (if needed)
  • No breaking changes

Summary by cubic

Add client‑side RBAC capability gating so users only see or can click what their role allows, with clean “No access” states and helpful tooltips. Introduces a capability model that mirrors server checks and auto‑grants basic usage tools to all members.

  • New Features

    • New primitives: useCapability/useCapabilities, RequireCapability, PermissionTooltip, NoPermissionState.
    • Applied gating across settings (general, brand, members, roles, security, registry/store, monitoring), connections, agents, automations, and AI providers; settings sidebar hides items without access.
    • Added a hidden basic-usage capability; tools under it are always allowed for org members, reducing friction for read‑only/essential actions.
    • Enabled Better Auth dynamic access control in migration to create custom role tables; local‑dev fallback prints invitation accept URL to the console.
  • Refactors

    • Rebuilt the role editor to use grouped “permission capabilities” with simple toggles and “dangerous” indicators; removed per‑tool UI.
    • Centralized capability definitions in PERMISSION_CAPABILITIES with helpers for grouping and toggling.
    • Replaced error‑prone actions with disabled buttons and clear tooltips, plus route‑level guards that show a friendly “No access” empty state.

Written for commit a2ff84f. Summary will update on new commits. Review in cubic

rafavalls and others added 10 commits April 29, 2026 15:02
Introduces high-level permission capabilities mapped to tool names,
a new useMemberPermissions hook, and a refreshed org-role-detail UI
with grouped capability toggles and settings layout integration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Split ai-providers into view (model listing) and manage (key ops), move
tags to Organization, collapse registry browse and publish into a single
"Manage registry" capability, drop threads:manage (threads are a core
chat action, not a manageable permission), move connections:sql to
Developer, and add vm:access for previously ungated VM tools.

Also widens spacing in the org permissions tab (gap-6 to gap-10).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove view-only permission capabilities that all org members should have
by default (connections, agents, automations, AI providers, object storage,
VM previews). Replace ctx.access.check() with ctx.access.grant() in the
relevant tool handlers so any authenticated member can use them regardless
of their role's permission set.

Also fix CI failures: delete unused use-member-permissions.ts hook (which
referenced a non-existent KEYS.myRolePermissions), remove export from
PERMISSION_CAPABILITIES (internal use only), and delete dead
getPermissionOptions function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… defineTool

Add basicUsage?: boolean to ToolBinder. When set, the execute wrapper in
defineTool automatically grants access before the handler runs, removing
the need for explicit ctx.access.grant() calls in individual handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add basicUsage flag to PermissionCapability. Capabilities marked basicUsage
are automatically granted to all authenticated org members via BASIC_USAGE_TOOLS
set checked in AccessControl.checkResource(). Tool handlers uniformly call
ctx.access.check() — the registry is the single source of truth.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…stry capability

Replace per-capability basicUsage flag with a single 'basic-usage' capability
listing all tools all org members can access by default. Hidden from the role
editor UI via getCapabilitySections() filter. BASIC_USAGE_TOOLS derives from
this capability by id.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tool handlers don't need any changes for the basic-usage refactor — access
is granted via the registry capability and the AccessControl layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sion set

Revert access-control.ts runtime override. Instead, buildPermission() in the
role editor always includes BASIC_USAGE_TOOLS in the saved 'self' permission,
so Better Auth's normal permission check returns true for those tools without
any special-case logic at the access layer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move the basic-usage check above the auth/role checks in checkResource so
it runs first. Custom roles never need to include these tools in their
saved permission set — the registry alone defines them, the UI hides the
capability, and access-control auto-grants at runtime.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces useCapability/useCapabilities hooks and RequireCapability/
PermissionTooltip/NoPermissionState components so the UI proactively
hides or disables actions the current user's role doesn't permit,
mirroring server-side AccessControl logic. Applies capability checks
across settings pages, connections, agents, automations, monitoring,
and AI providers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Benchmark

Should we run the Virtual MCP strategy benchmark for this PR?

React with 👍 to run the benchmark.

Reaction Action
👍 Run quick benchmark (10 & 128 tools)

Benchmark will run on the next push after you react.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

Release Options

Suggested: Minor (2.297.0) — based on feat: prefix

React with an emoji to override the release type:

Reaction Type Next Version
👍 Prerelease 2.296.1-alpha.1
🎉 Patch 2.296.1
❤️ Minor 2.297.0
🚀 Major 3.0.0

Current version: 2.296.0

Note: If multiple reactions exist, the smallest bump wins. If no reactions, the suggested bump is used (default: patch).

@rafavalls rafavalls changed the base branch from main to rafavalls/permissions-redesign April 30, 2026 00:37
rafavalls and others added 2 commits April 30, 2026 13:12
…ants and UI gating

Adds grant-resource-access helper, access-denied utility, settings index redirect,
and wires capability checks throughout UI components, hooks, layouts, and tool handlers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…save

- virtual-mcp: roll form back to persisted agent on no-permission auto-save
  instead of adopting the user's input as the new defaults.
- access-denied: tighten regex to AccessControl/Better-Auth wording so
  unrelated upstream errors aren't relabeled as permission failures.
- use-capability: rolePermits now also accepts wildcard shapes
  ({ "*": ["*"] }, { "self": ["*"] }) and aggregates actions across
  resource buckets so non-self grants aren't silently denied client-side.
- grant-resource-access: skip when the role already has full access via
  any wildcard shape, not just permission["*"].
- thread/list: throw ForbiddenError when a non-self userId filter is
  requested without THREADS_VIEW_ALL_MEMBERS instead of silently
  rewriting it; document the constraint on the input schema.
- org-role-detail: admin and user are now fully customizable. Built-in
  admin/user permission overrides are persisted as organizationRole rows
  and surfaced via useOrganizationRoles().builtinOverrides so the editor
  loads them with a stored id and updates in place. First save recovers
  from "role name already exists" by listing roles and updating the
  existing row, so re-saving admin or user no longer errors.
- registry-metadata: drop dead TOOL_LABELS table left over from the
  removed getPermissionOptions helper.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@viktormarinho viktormarinho force-pushed the rafavalls/permissions-redesign branch from 7966e33 to bfbc99c Compare April 30, 2026 19:16
Base automatically changed from rafavalls/permissions-redesign to main April 30, 2026 19:57
Previous fix aggregated grants across all resource buckets, but the
auto-grant helper writes { [connectionId]: ["*"] } for every connection
on every custom role. Aggregating that turned the "*" action into a
global wildcard, which gave every member with any connection grant
full UI access.

Capability tools are static org-level permission names, so the gating
only needs to look at the `self` bucket. Per-connection wildcards mean
"all tools on this connection" — not "wildcard every capability".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant