Skip to content

[feat] Move RBAC enforcement from ee to oss#4801

Merged
bekossy merged 19 commits into
release/v0.104.3from
feat/move-rbac-to-oss
Jun 25, 2026
Merged

[feat] Move RBAC enforcement from ee to oss#4801
bekossy merged 19 commits into
release/v0.104.3from
feat/move-rbac-to-oss

Conversation

@junaway

@junaway junaway commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Context

RBAC enforcement only ran in EE. In OSS, every permission check was wrapped in if is_ee():, so OSS was effectively allow-all: any member could invite users, edit environments, delete testsets, and so on, regardless of role. Memberships and roles already existed in OSS; only the enforcement was missing.

This moves the permission vocabulary, role catalog, and enforcement service into OSS so RBAC is always-on in both editions. The only edition difference left is the EE plan gate: on a plan that is not RBAC-entitled, EE still grants allow-all. That gate now lives behind a function-local is_ee() import inside the OSS service, so OSS never imports ee.* at module top.

Changes

The RBAC code moved from ee/src/core/access/ to oss/src/core/access/, and EE keeps thin re-export shims at the old paths so existing EE imports keep working.

  • Moved to OSS: permissions/{types,controls,service}.py, the get_role* accessors, and four DB lookups (get_organization, get_workspace_members, get_project_members, get_user_org_and_workspace_id) plus get_project_by_workspace.
  • Entitlement seam: check_project_has_role_or_permission enforces in both editions. The Flag.RBAC-not-entitled allow-all bypass runs only under is_ee() via a function-local import of the EE entitlements service. OSS falls through and enforces.

Before, in an OSS router:

if is_ee():
    if not await check_action_access(..., permission=Permission.EDIT_TESTSET):
        raise FORBIDDEN_EXCEPTION

After:

if not await check_action_access(..., permission=Permission.EDIT_TESTSET):
    raise FORBIDDEN_EXCEPTION
  • Un-gate (21 routers): data-plane routers now call check_action_access unconditionally. Entitlement and meter calls (check_entitlements, Counter) stay is_ee()-gated.
  • Forked management routers: organization_router and workspace_router had separate EE and OSS flows. The enforcement check was hoisted above the edition fork so invite, resend, and member-removal enforce in both editions.
  • Custom roles stay EE: AGENTA_ACCESS_ROLES and AGENTA_ACCESS_ROLES_OVERLAY are an EE feature. The override parsers moved to ee/src/core/access/permissions/role_overrides.py. OSS returns the code-default role catalog and ignores those env vars.
  • Lockout safety: add_user_to_workspace_and_org now writes the explicit role on the org member row instead of silently defaulting to viewer, so an invited admin is actually an admin.

Tests / notes

  • Added cross-edition acceptance tests: owner allowed, viewer denied on invite and member-removal, in both OSS and EE. These test permissions only. The plan-based allow-all bypass is an entitlements concern and is left to the entitlements suite; the two subsystems are tested independently.
  • Added OSS unit tests asserting OSS ignores the custom-role env vars, and repointed the EE role-override unit tests at the new role_overrides module.
  • Verified: ruff format and ruff check clean across api/. Every touched module imports under both AGENTA_LICENSE=oss and =ee. Full suites green in both editions: EE api 1797 passed, OSS api 1527 passed, with SDK and services green.
  • Most of the line count is de-indentation churn from removing the if is_ee(): wrappers. Reviewing with git diff -w shows the real change (~300 lines).

What to QA

  • In an OSS instance, sign in as a viewer member of a project and try to invite a user or edit an environment. You should get 403. Before this change you could do it.
  • As the org owner or an admin, the same actions succeed.
  • Regression (EE): on a plan that is not RBAC-entitled, a viewer can still perform these actions (the allow-all bypass is preserved). On an entitled plan, the viewer is denied.
  • Regression: invite a new user as admin, accept, and confirm they land with the admin role and can manage members (not silently downgraded to viewer).

Copilot AI review requested due to automatic review settings June 23, 2026 10:38
@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
agenta-documentation Ready Ready Preview, Comment Jun 25, 2026 11:30am

Request Review

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. enhancement New feature or request labels Jun 23, 2026
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ab538a9a-e6cc-4f69-a670-f0cc9f1e6fcb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

RBAC enforcement now runs unconditionally in OSS routers and shared services, with the shared role and permission catalog moved into OSS and EE modules reduced to re-exports or EE-only overlays. Tests, docs, and workspace settings UI were updated to match the new edition split.

Changes

RBAC in OSS

Layer / File(s) Summary
OSS RBAC foundations and EE adaptation
api/AGENTS.md, api/oss/src/core/access/*, api/oss/src/services/db_manager.py, api/oss/src/routers/organization_router.py, api/ee/src/core/access/*, api/ee/src/services/db_manager_ee.py, api/ee/src/apis/fastapi/access/router.py, api/ee/src/apis/fastapi/events/router.py
OSS defines the role and permission catalog, builds and hashes effective controls, adds RBAC DB lookup and cache helpers, and moves EE access modules to re-exports or EE-only role overrides.
Ungated OSS permission enforcement
api/oss/src/apis/fastapi/*/router.py, api/oss/src/apis/fastapi/shared/exceptions.py
OSS API routers switch from is_ee()-guarded checks to unconditional permission enforcement, while keeping EE-specific entitlement or metering branches where present and standardizing forbidden responses.
Shared access services, tests, docs, and UI alignment
api/oss/tests/pytest/**, api/ee/tests/pytest/**, docs/designs/rbac-in-oss/*, docs/docs/administration/access-control/*, docs/blog/entries/open-sourcing-agenta.mdx, web/oss/src/components/pages/settings/WorkspaceManage/*, web/oss/src/hooks/*, web/oss/src/state/access/atoms.ts
Adds RBAC acceptance and unit tests, updates EE test fixtures, revises design and product docs, and adjusts workspace-management UI and hooks for always-on RBAC.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • Agenta-AI/agenta#4671: This PR uses OSS membership/project lookup helpers in RBAC enforcement, and #4671 adds the membership data layer those helpers depend on.
  • Agenta-AI/agenta#4673: Both PRs touch OSS membership and role-seeding paths in api/oss/src/services/db_manager.py, which feed the same RBAC enforcement flow.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.99% which is insufficient. The required threshold is 60.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: moving RBAC enforcement from EE to OSS.
Description check ✅ Passed The description is directly related to the RBAC migration and matches the changeset details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/move-rbac-to-oss

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@junaway junaway changed the title [feat] Move RBAC enforcement fromee to oss [feat] Move RBAC enforcement from ee to oss Jun 23, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR moves RBAC permission vocabulary, role catalog, and enforcement into the OSS API layer so permission checks run in both OSS and EE, while preserving the EE-only “RBAC not entitled ⇒ allow-all” bypass via a function-local is_ee()-guarded import.

Changes:

  • Introduces OSS-owned RBAC modules (permissions/{types,controls,service}.py) plus an OSS role-controls composition root (oss/src/core/access/controls.py) and EE re-export shims.
  • Removes is_ee() gating from many OSS routers so check_action_access(...) runs unconditionally, and updates the access router to stop short-circuiting OSS to allow-all.
  • Adds cross-edition tests and design docs to validate/describe the behavior flip and EE entitlement seam.

Reviewed changes

Copilot reviewed 47 out of 50 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
docs/designs/rbac-in-oss/research.md Research writeup capturing current RBAC state pre-move.
docs/designs/rbac-in-oss/proposal.md Proposal for OSS-owned RBAC enforcement + EE entitlement seam.
docs/designs/rbac-in-oss/plan.md Implementation plan and “as implemented” outcome notes.
docs/designs/rbac-in-oss/gap.md Gap analysis covering entitlement, lockout, symbol resolution, and tests.
docs/designs/rbac-in-oss/checklist.md Inventory/checklist used to ensure call-site coverage during un-gating.
api/oss/tests/pytest/unit/environments/test_commit_validation.py Stubs RBAC check to keep environment commit-validation unit tests focused.
api/oss/tests/pytest/unit/access/test_role_controls_oss.py New unit tests asserting OSS ignores custom-role env vars and uses defaults.
api/oss/tests/pytest/unit/access/init.py Package init (no behavioral change).
api/oss/tests/pytest/acceptance/accounts/test_rbac_enforcement.py OSS acceptance tests for invite/member-removal enforcement (owner allow / viewer deny).
api/oss/src/utils/env.py Updates AccessConfig docstring to clarify OSS vs EE env-var behavior.
api/oss/src/services/db_manager.py Adds RBAC-related lookups + fixes org membership role seeding + adds project-by-workspace helper.
api/oss/src/routers/workspace_router.py Hoists enforcement checks above EE/OSS forks for workspace member removal and role listing.
api/oss/src/routers/user_profile.py Makes reset-password permission checks run in OSS too.
api/oss/src/routers/organization_router.py Makes invite/resend permission checks run in OSS too; adjusts role description resolution.
api/oss/src/routers/api_key_router.py Makes API key CRUD permission checks run in OSS too.
api/oss/src/core/access/permissions/types.py New OSS-owned Permission/Role enums used across editions.
api/oss/src/core/access/permissions/service.py New OSS-owned RBAC enforcement service with EE-only entitlement bypass.
api/oss/src/core/access/permissions/controls.py New OSS role catalog builder; EE-only overrides applied via function-local import.
api/oss/src/core/access/permissions/init.py Package init (no behavioral change).
api/oss/src/core/access/controls.py New OSS role-controls composition root + get_role* accessors and hash.
api/oss/src/core/access/init.py Package init (no behavioral change).
api/oss/src/apis/fastapi/webhooks/router.py Removes EE-gating so webhook RBAC checks always run.
api/oss/src/apis/fastapi/vault/router.py Removes EE-gating so vault RBAC checks always run.
api/oss/src/apis/fastapi/tracing/router.py Removes EE-gating for RBAC while keeping EE-only entitlement/meter logic gated.
api/oss/src/apis/fastapi/traces/router.py Removes EE-gating so traces RBAC checks always run.
api/oss/src/apis/fastapi/tools/router.py Removes EE-gating so tools RBAC checks always run.
api/oss/src/apis/fastapi/testcases/router.py Removes EE-gating so testcases RBAC checks always run.
api/oss/src/apis/fastapi/queries/router.py Removes EE-gating so queries RBAC checks always run.
api/oss/src/apis/fastapi/otlp/router.py Removes EE-gating for RBAC while keeping EE-only entitlements gated.
api/oss/src/apis/fastapi/legacy_variants/router.py Removes EE-gating so legacy variant RBAC checks always run.
api/oss/src/apis/fastapi/invocations/router.py Removes EE-gating so invocations RBAC checks always run.
api/oss/src/apis/fastapi/folders/router.py Removes EE-gating so folders RBAC checks always run.
api/oss/src/apis/fastapi/environments/utils.py Moves RBAC imports to OSS so deploy checks can run in OSS.
api/oss/src/apis/fastapi/environments/router.py Removes EE-gating so environments RBAC checks always run.
api/oss/src/apis/fastapi/annotations/router.py Removes EE-gating so annotations RBAC checks always run.
api/oss/src/apis/fastapi/access/router.py Removes OSS allow-all short-circuit; keeps EE-only credits meter gated.
api/ee/tests/pytest/unit/test_access_controls.py Repoints EE unit tests to new plan/role override locations.
api/ee/tests/pytest/acceptance/accounts/test_rbac_enforcement_ee.py EE acceptance tests mirroring OSS enforcement behavior (entitled plan).
api/ee/src/services/db_manager_ee.py Re-exports moved RBAC lookups from OSS db_manager.
api/ee/src/core/access/permissions/types.py EE re-export shim for OSS-owned Permission/Role enums.
api/ee/src/core/access/permissions/service.py EE re-export shim for OSS-owned RBAC enforcement service.
api/ee/src/core/access/permissions/role_overrides.py New EE-only role override parsing and application layer.
api/ee/src/core/access/permissions/controls.py EE re-export shim for OSS role-controls builder/constants.
api/ee/src/core/access/controls.py EE access-controls now focuses on plans; re-exports OSS role accessors.
api/AGENTS.md Documents new RBAC enforcement ownership and OSS↔EE import constraints.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/oss/src/services/db_manager.py
Comment thread api/oss/src/core/access/permissions/service.py
Comment thread api/oss/src/services/db_manager.py
@junaway

junaway commented Jun 23, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@github-actions

github-actions Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Status Destroyed (PR closed)

Updated at 2026-06-25T13:58:14.317Z

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
api/oss/src/apis/fastapi/environments/router.py (1)

1387-1406: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove the leftover EE gate around guarded-environment checks.

Line 1388 and Line 1449 still skip DEPLOY_ENVIRONMENTS checks in OSS. Since guard_simple_environment now runs in OSS, a guarded environment can be edited or archived by a user with only EDIT_ENVIRONMENTS.

🛡️ Proposed fix
-        # If guarded, require DEPLOY_ENVIRONMENTS permission
-        if is_ee():
-            environment = await self.environments_service.fetch_environment(
-                project_id=UUID(request.state.project_id),
-                environment_ref=Reference(id=environment_id),
-            )
-            is_guarded = False
-            if environment and environment.flags:
-                flags = environment.flags
-                if isinstance(flags, EnvironmentFlags):
-                    is_guarded = bool(getattr(flags, "is_guarded", False))
-                elif isinstance(flags, dict):
-                    is_guarded = bool(flags.get("is_guarded", False))
-            if is_guarded:
-                if not await check_action_access(  # type: ignore
-                    user_uid=request.state.user_id,
-                    project_id=request.state.project_id,
-                    permission=Permission.DEPLOY_ENVIRONMENTS,  # type: ignore
-                ):
-                    raise FORBIDDEN_EXCEPTION  # type: ignore
+        environment = await self.environments_service.fetch_environment(
+            project_id=UUID(request.state.project_id),
+            environment_ref=Reference(id=environment_id),
+        )
+        is_guarded = False
+        if environment and environment.flags:
+            flags = environment.flags
+            if isinstance(flags, EnvironmentFlags):
+                is_guarded = bool(getattr(flags, "is_guarded", False))
+            elif isinstance(flags, dict):
+                is_guarded = bool(flags.get("is_guarded", False))
+        if is_guarded and not await check_action_access(  # type: ignore
+            user_uid=request.state.user_id,
+            project_id=request.state.project_id,
+            permission=Permission.DEPLOY_ENVIRONMENTS,  # type: ignore
+        ):
+            raise FORBIDDEN_EXCEPTION  # type: ignore
@@
-        # If guarded, require DEPLOY_ENVIRONMENTS permission
-        if is_ee():
-            is_guarded = False
-            if environment.flags:
-                flags = environment.flags
-                if isinstance(flags, EnvironmentFlags):
-                    is_guarded = bool(getattr(flags, "is_guarded", False))
-                elif isinstance(flags, dict):
-                    is_guarded = bool(flags.get("is_guarded", False))
-            if is_guarded:
-                if not await check_action_access(  # type: ignore
-                    user_uid=request.state.user_id,
-                    project_id=request.state.project_id,
-                    permission=Permission.DEPLOY_ENVIRONMENTS,  # type: ignore
-                ):
-                    raise FORBIDDEN_EXCEPTION  # type: ignore
+        is_guarded = False
+        if environment.flags:
+            flags = environment.flags
+            if isinstance(flags, EnvironmentFlags):
+                is_guarded = bool(getattr(flags, "is_guarded", False))
+            elif isinstance(flags, dict):
+                is_guarded = bool(flags.get("is_guarded", False))
+        if is_guarded and not await check_action_access(  # type: ignore
+            user_uid=request.state.user_id,
+            project_id=request.state.project_id,
+            permission=Permission.DEPLOY_ENVIRONMENTS,  # type: ignore
+        ):
+            raise FORBIDDEN_EXCEPTION  # type: ignore

As per coding guidelines, RBAC enforcement is an OSS feature and handlers should not edition-gate check_action_access(...). Based on PR objectives, the only edition difference should be the EE plan gate inside the enforcement seam.

Also applies to: 1448-1463

Source: Coding guidelines

api/oss/src/apis/fastapi/environments/utils.py (1)

50-51: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not short-circuit guarded deploy checks in OSS.

Line 50 returns before checking guarded environments whenever not is_ee(). This lets OSS commit/deploy paths skip DEPLOY_ENVIRONMENTS even though RBAC enforcement has moved to OSS.

🛡️ Proposed fix
-    if not is_ee() or not environment_id:
+    if not environment_id:
         return

As per coding guidelines, RBAC enforcement is an OSS feature and check_action_access(...) should not be edition-gated. Based on PR objectives, the EE-specific behavior should stay in the enforcement/entitlement seam, not in API helpers.

Source: Coding guidelines

api/oss/src/apis/fastapi/applications/router.py (1)

597-616: 🔒 Security & Privacy | 🟠 Major

Add exclude=[HTTPException] to four @suppress_exceptions(...) decorators to preserve RBAC denial responses.

These handlers raise FORBIDDEN_EXCEPTION (an HTTPException) inside @suppress_exceptions(...) decorators without excluding HTTP exceptions. When exclude is not provided, the decorator suppresses all exception types, including HTTPException, returning 200 with default/empty responses instead of 403 Forbidden. This breaks RBAC enforcement.

Confirm by checking the decorator signatures—they lack exclude=[HTTPException] unlike adjacent protected endpoints in other routers (e.g., folders/router.py, queries/router.py).

Lines affected
  • Line 597: fetch_application
  • Line 736: query_applications
  • Line 1574: query_application_revisions
  • Line 1880: fetch_simple_application

Source: Coding guidelines

🧹 Nitpick comments (2)
api/oss/src/routers/organization_router.py (1)

35-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move the access-controls import to module scope.

_role_description() performs a local import for a catalog/config lookup on every call; this should be a module-level import unless there is a circular dependency.

♻️ Proposed cleanup
 from oss.src.core.access.permissions.service import check_action_access
 from oss.src.core.access.permissions.types import Permission
+from oss.src.core.access.controls import get_role_description
 from oss.src.services.db_manager import get_user_org_and_workspace_id
 
 
 def _role_description(role: str) -> str:
-    """Resolve a workspace-role description from the effective access-controls
-    catalog (env-overridable via AGENTA_ACCESS_ROLES)."""
-    from oss.src.core.access.controls import get_role_description
-
     return get_role_description("workspace", role) or ""

As per coding guidelines, "api/**/*.py: Avoid local imports inside helper functions for configuration lookup; prefer module-level imports unless there is a proven circular dependency."

Source: Coding guidelines

api/oss/src/routers/workspace_router.py (1)

77-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Drop the env-override comment in the OSS router.

This comment narrates the list comprehension and can mislead OSS readers because custom-role env vars are intentionally EE-only. The code is clearer without it.

♻️ Proposed cleanup
-    # Resolve via access-controls (env-overridable via AGENTA_ACCESS_ROLES).
     workspace_roles_with_description = [

As per coding guidelines, "Keep AI-generated in-code comments minimal" and comment only the non-obvious why; the PR objective also states custom-role env vars are ignored in OSS.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3342c318-f160-4e15-8216-12cda2ebd405

📥 Commits

Reviewing files that changed from the base of the PR and between 8b7e319 and dc95342.

📒 Files selected for processing (50)
  • api/AGENTS.md
  • api/ee/src/core/access/controls.py
  • api/ee/src/core/access/permissions/controls.py
  • api/ee/src/core/access/permissions/role_overrides.py
  • api/ee/src/core/access/permissions/service.py
  • api/ee/src/core/access/permissions/types.py
  • api/ee/src/services/db_manager_ee.py
  • api/ee/tests/pytest/acceptance/accounts/test_rbac_enforcement_ee.py
  • api/ee/tests/pytest/unit/test_access_controls.py
  • api/oss/src/apis/fastapi/access/router.py
  • api/oss/src/apis/fastapi/annotations/router.py
  • api/oss/src/apis/fastapi/applications/router.py
  • api/oss/src/apis/fastapi/environments/router.py
  • api/oss/src/apis/fastapi/environments/utils.py
  • api/oss/src/apis/fastapi/evaluations/router.py
  • api/oss/src/apis/fastapi/evaluators/router.py
  • api/oss/src/apis/fastapi/folders/router.py
  • api/oss/src/apis/fastapi/invocations/router.py
  • api/oss/src/apis/fastapi/legacy_variants/router.py
  • api/oss/src/apis/fastapi/otlp/router.py
  • api/oss/src/apis/fastapi/queries/router.py
  • api/oss/src/apis/fastapi/testcases/router.py
  • api/oss/src/apis/fastapi/testsets/router.py
  • api/oss/src/apis/fastapi/tools/router.py
  • api/oss/src/apis/fastapi/traces/router.py
  • api/oss/src/apis/fastapi/tracing/router.py
  • api/oss/src/apis/fastapi/vault/router.py
  • api/oss/src/apis/fastapi/webhooks/router.py
  • api/oss/src/apis/fastapi/workflows/router.py
  • api/oss/src/core/access/__init__.py
  • api/oss/src/core/access/controls.py
  • api/oss/src/core/access/permissions/__init__.py
  • api/oss/src/core/access/permissions/controls.py
  • api/oss/src/core/access/permissions/service.py
  • api/oss/src/core/access/permissions/types.py
  • api/oss/src/routers/api_key_router.py
  • api/oss/src/routers/organization_router.py
  • api/oss/src/routers/user_profile.py
  • api/oss/src/routers/workspace_router.py
  • api/oss/src/services/db_manager.py
  • api/oss/src/utils/env.py
  • api/oss/tests/pytest/acceptance/accounts/test_rbac_enforcement.py
  • api/oss/tests/pytest/unit/access/__init__.py
  • api/oss/tests/pytest/unit/access/test_role_controls_oss.py
  • api/oss/tests/pytest/unit/environments/test_commit_validation.py
  • docs/designs/rbac-in-oss/checklist.md
  • docs/designs/rbac-in-oss/gap.md
  • docs/designs/rbac-in-oss/plan.md
  • docs/designs/rbac-in-oss/proposal.md
  • docs/designs/rbac-in-oss/research.md

Comment thread api/ee/src/core/access/permissions/role_overrides.py
Comment thread api/oss/src/apis/fastapi/evaluators/router.py
Comment thread api/oss/src/apis/fastapi/tools/router.py Outdated
Comment thread api/oss/src/core/access/controls.py
Comment thread api/oss/src/core/access/permissions/service.py Outdated
Comment thread api/oss/tests/pytest/unit/access/test_role_controls_oss.py
Comment thread docs/designs/rbac-in-oss/checklist.md Outdated
Comment thread docs/designs/rbac-in-oss/plan.md Outdated
Comment thread docs/designs/rbac-in-oss/proposal.md Outdated
Comment thread docs/designs/rbac-in-oss/research.md

@mmabrouk mmabrouk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jp-agenta thanks for the pr. Can you please include the docs update (very minor, just making sure it's in sync with reality) with this pr

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8b40be54-ea26-44a7-b82a-a922c597794e

📥 Commits

Reviewing files that changed from the base of the PR and between dc95342 and f3499b5.

📒 Files selected for processing (3)
  • docs/blog/entries/open-sourcing-agenta.mdx
  • docs/docs/administration/access-control/01-organizations.mdx
  • docs/docs/administration/access-control/03-rbac.mdx
✅ Files skipped from review due to trivial changes (1)
  • docs/blog/entries/open-sourcing-agenta.mdx

Comment thread docs/docs/administration/access-control/01-organizations.mdx Outdated
Fixes from the PR #4801 review pass:

- Bind invite/resend authorization to the target org (validate the
  workspace's project belongs to organization_id; reuse that project for
  the service call) so the RBAC check cannot pass for the wrong org.
- Cast workspace_id to UUID in get_workspace_members (was binding a raw
  string to a UUID column).
- Gate evaluator variant/revision-log routes with evaluator permissions
  instead of workflow permissions.
- Move FORBIDDEN_EXCEPTION out of the core RBAC service into
  apis/fastapi/shared/exceptions.py (no HTTPException in core); repoint
  importers and the EE shim.
- Invalidate the check_action_access cache on every membership/role
  mutation and add the controls hash to its key, so revocation takes
  effect immediately instead of after the TTL.
- Reuse the open session for the user lookup in
  get_user_org_and_workspace_id; include role permissions in the controls
  hash; enforce both fields for new overlay roles; tighten the tools slug
  regex to fullmatch.
- Docs: RBAC is always-on in OSS (rbac/organizations mdx + blog); design
  findings ledger.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 54 out of 57 changed files in this pull request and generated 6 comments.

Comment thread docs/docs/administration/access-control/03-rbac.mdx Outdated
Comment thread docs/docs/administration/access-control/01-organizations.mdx Outdated
Comment thread api/oss/src/services/db_manager.py
Comment thread api/oss/src/services/db_manager.py
Comment thread api/oss/src/core/access/permissions/service.py
Comment thread api/oss/tests/pytest/unit/access/test_role_controls_oss.py

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/oss/src/routers/organization_router.py (1)

363-370: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Use the authorized project for the EE resend path.

The RBAC check is scoped to project.id resolved from workspace_id, but the EE resend call still uses ambient request.state.project_id. If those differ, authorization and resend lookup/mutation target different projects. Pass str(project.id) here, matching the invite and OSS resend paths.

🛡️ Proposed fix
         invite_user = await workspace_manager.resend_user_workspace_invite(
             payload=payload,
-            project_id=request.state.project_id,
+            project_id=str(project.id),
             organization_id=organization_id,
             workspace_id=workspace_id,
             user_uid=request.state.user_id,
         )
api/oss/src/apis/fastapi/evaluators/router.py (1)

1281-1286: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require environment read access for environment-backed retrieval.

Line 1281 checks only evaluator access, but this handler also dereferences environment refs/key and can reveal which evaluator revision an environment pins. Add an environment read check inside the environment_lookup_requested branch.

🔒 Proposed conditional environment authorization
         if environment_lookup_requested:
             if not environment_refs_requested:
                 raise HTTPException(
                     status_code=status.HTTP_400_BAD_REQUEST,
                     detail="Environment-backed evaluator retrieve requires environment refs.",
                 )
 
+            if not await check_action_access(  # type: ignore
+                user_uid=request.state.user_id,
+                project_id=request.state.project_id,
+                permission=Permission.VIEW_ENVIRONMENTS,  # type: ignore
+            ):
+                raise FORBIDDEN_EXCEPTION  # type: ignore
+
             if not key:
                 if evaluator_ref and evaluator_ref.slug:
                     key = f"{evaluator_ref.slug}.revision"

Also applies to: 1329-1334


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3df7c0d0-022c-4dab-bf87-490834334ace

📥 Commits

Reviewing files that changed from the base of the PR and between f3499b5 and a56d8e4.

📒 Files selected for processing (36)
  • api/ee/src/apis/fastapi/events/router.py
  • api/ee/src/core/access/permissions/role_overrides.py
  • api/ee/src/core/access/permissions/service.py
  • api/ee/src/services/db_manager_ee.py
  • api/ee/tests/pytest/unit/test_access_controls.py
  • api/ee/tests/pytest/unit/test_controls_env_override.py
  • api/oss/src/apis/fastapi/annotations/router.py
  • api/oss/src/apis/fastapi/applications/router.py
  • api/oss/src/apis/fastapi/environments/router.py
  • api/oss/src/apis/fastapi/environments/utils.py
  • api/oss/src/apis/fastapi/evaluations/router.py
  • api/oss/src/apis/fastapi/evaluators/router.py
  • api/oss/src/apis/fastapi/folders/router.py
  • api/oss/src/apis/fastapi/invocations/router.py
  • api/oss/src/apis/fastapi/legacy_variants/router.py
  • api/oss/src/apis/fastapi/otlp/router.py
  • api/oss/src/apis/fastapi/queries/router.py
  • api/oss/src/apis/fastapi/shared/exceptions.py
  • api/oss/src/apis/fastapi/testcases/router.py
  • api/oss/src/apis/fastapi/testsets/router.py
  • api/oss/src/apis/fastapi/tools/router.py
  • api/oss/src/apis/fastapi/traces/router.py
  • api/oss/src/apis/fastapi/tracing/router.py
  • api/oss/src/apis/fastapi/webhooks/router.py
  • api/oss/src/apis/fastapi/workflows/router.py
  • api/oss/src/core/access/controls.py
  • api/oss/src/core/access/permissions/service.py
  • api/oss/src/core/access/permissions/types.py
  • api/oss/src/routers/organization_router.py
  • api/oss/src/services/db_manager.py
  • api/oss/tests/pytest/unit/access/test_role_controls_oss.py
  • docs/designs/rbac-in-oss/checklist.md
  • docs/designs/rbac-in-oss/findings.md
  • docs/designs/rbac-in-oss/plan.md
  • docs/designs/rbac-in-oss/proposal.md
  • docs/designs/rbac-in-oss/research.md
💤 Files with no reviewable changes (1)
  • api/oss/src/core/access/permissions/types.py
✅ Files skipped from review due to trivial changes (4)
  • api/ee/src/apis/fastapi/events/router.py
  • docs/designs/rbac-in-oss/checklist.md
  • docs/designs/rbac-in-oss/research.md
  • docs/designs/rbac-in-oss/plan.md
🚧 Files skipped from review as they are similar to previous changes (21)
  • api/oss/src/apis/fastapi/environments/utils.py
  • api/ee/src/core/access/permissions/service.py
  • docs/designs/rbac-in-oss/proposal.md
  • api/oss/src/apis/fastapi/legacy_variants/router.py
  • api/oss/src/core/access/controls.py
  • api/oss/src/apis/fastapi/webhooks/router.py
  • api/oss/src/apis/fastapi/testcases/router.py
  • api/oss/src/apis/fastapi/traces/router.py
  • api/oss/src/apis/fastapi/folders/router.py
  • api/oss/src/apis/fastapi/otlp/router.py
  • api/oss/src/apis/fastapi/tools/router.py
  • api/oss/src/apis/fastapi/invocations/router.py
  • api/ee/src/core/access/permissions/role_overrides.py
  • api/oss/src/apis/fastapi/annotations/router.py
  • api/oss/src/apis/fastapi/tracing/router.py
  • api/oss/src/apis/fastapi/queries/router.py
  • api/oss/src/apis/fastapi/applications/router.py
  • api/oss/src/apis/fastapi/evaluations/router.py
  • api/oss/src/apis/fastapi/testsets/router.py
  • api/oss/src/apis/fastapi/environments/router.py
  • api/oss/src/apis/fastapi/workflows/router.py

Comment thread api/oss/src/apis/fastapi/shared/exceptions.py
Comment thread api/oss/src/core/access/permissions/service.py
RBAC enforcement is on in OSS, but roles were invisible in the OSS UI:

- Move the GET /access/roles endpoint from the EE access router to OSS
  (it uses get_roles/SCOPES, which now live in OSS). EE keeps /plans and
  inherits /roles via its additive mount, so both editions serve it.
- Web members UI gated role display/selection behind `isEE() && hasRBAC`,
  which is false in OSS. Switch the gate to `!isEE() || hasRBAC` so OSS
  always shows the role column, the populated invite-role selector, and
  the edit-role dropdown; EE keeps the plan gate (not-entitled EE still
  hides roles and shows the upsell).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
OSS always enforces RBAC; Cloud/Enterprise follow the plan (non-RBAC
plans grant full access). Addresses review feedback that the prior
wording implied EE always enforces roles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 25, 2026 09:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings June 25, 2026 10:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings June 25, 2026 11:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings June 25, 2026 11:29

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Jun 25, 2026
@bekossy bekossy merged commit ef8c8a1 into release/v0.104.3 Jun 25, 2026
47 of 49 checks passed
@junaway junaway mentioned this pull request Jun 26, 2026
12 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm This PR has been approved by a maintainer size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants