feat: gate workspace ACL grants on matching member capability - #27281
Closed
jscottmiller wants to merge 6 commits into
Closed
feat: gate workspace ACL grants on matching member capability#27281jscottmiller wants to merge 6 commits into
jscottmiller wants to merge 6 commits into
Conversation
Docs preview📖 View docs preview for |
This was referenced Jul 15, 2026
jscottmiller
force-pushed
the
scott/gateway-2-ai-gateway-role
branch
from
July 16, 2026 20:28
2e8e016 to
60c04e2
Compare
jscottmiller
force-pushed
the
scott/gateway-3-use-shared
branch
from
July 17, 2026 02:03
12f701f to
9864191
Compare
jscottmiller
force-pushed
the
scott/gateway-2-ai-gateway-role
branch
from
July 17, 2026 02:04
8c0e6ca to
2e8e016
Compare
jscottmiller
force-pushed
the
scott/gateway-3-use-shared
branch
2 times, most recently
from
July 19, 2026 23:00
d405949 to
061ff57
Compare
jscottmiller
force-pushed
the
scott/gateway-2-ai-gateway-role
branch
3 times, most recently
from
July 20, 2026 15:51
77954f0 to
99b478e
Compare
jscottmiller
force-pushed
the
scott/gateway-3-use-shared
branch
2 times, most recently
from
July 20, 2026 21:29
3593109 to
5a894e0
Compare
…ility ACL entries on gated resource types (GatedACL in the policy permission definition; workspace and workspace_dormant) only take effect per action: the subject must hold the granted action, or the wildcard, at the member level in the object's organization (acl_action_orgs, with standard vote semantics). Revoking the roles that carry an action revokes it on everything shared with the subject, evaluated live on every authorization; subjects holding only some of an entry's actions receive partial effect. The gating flag reaches the policy as the derived input field acl_use_gated, computed from the object type in Go and injected on all three rego input paths, so the precondition resolves at prepare time and list-query SQL filters enforce it too. Ungated types (templates, chats) keep ACL-only grants.
patchWorkspaceACL authorizes each granted action for the recipient against the workspace's RBAC object with the proposed ACL entry attached, rejecting recipients for whom no action would authorize so shares don't silently grant nothing. Partial capability is accepted with partial effect, matching the per-action policy. Recipient existence and role resolution run as system, failing closed when roles cannot be resolved; group entries stay unvalidated since membership changes after the share and is enforced per-member at access time. The dormant-workspace sharing test documents desired behavior and is skipped until DormantRBAC() carries workspace ACLs.
…pace seats A user whose roles carry any member-level workspace action can operate workspaces shared with them even without workspace-create, so seat counting probes the full workspace action set. This also counts workspace-access users under a creation ban, who keep their existing workspaces.
…d to end A recipient holding every workspace action except a member-level-negated update receives an admin share: the share is accepted, read and list work, and the negated action is denied at access time. The recipient is built from a system custom role because dbauthz only permits member-key permissions on system roles, and negation avoids the MinimumImplicitMember global that concurrent test servers reset. putWorkspaceAutostart now maps authorization denials to 404 like its sibling handlers; it previously returned 500 for subjects with read but not update, which per-action ACL grants make a common state.
…idens Evaluates the policy with raw JSON input omitting the derived acl_use_gated field: an ungated type's ACL grant is denied for a subject without matching member-level actions (with a present-field control proving the deny comes from the missing field), while gated-type grants keep working for capable subjects via acl_action_orgs. Guards the explicit == false comparison in acl_use_precondition against truthiness refactors and missed injection sites.
Deduplicates the gating definition (canonical on GatedACL), drops feature-framing from the rego and share-validation comments, and removes external doc references from the skipped dormancy test.
jscottmiller
force-pushed
the
scott/gateway-3-use-shared
branch
from
July 21, 2026 04:12
5a894e0 to
77c9afc
Compare
Contributor
Author
|
This is now out of scope for the gateway accounts MVP. This work may be resurrected later. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Gates workspace ACL entries on a per-action capability precondition: the rego policy only grants an ACL entry's action while the subject holds that same action (or the wildcard) at the member level in the object's organization. This prevents sharing workspaces with users who lack workspace capabilities (such as AI-Gateway-only accounts) and revokes shared access live when the capability is revoked, without deleting the ACL entry; regaining the capability restores the share. Subjects whose roles carry only some of an entry's actions receive partial effect.
There is no new action or schema change: the precondition matches the granted action itself against the recipient's member-level permissions ("you may receive X on others' workspaces only where your roles grant you X on your own"), so capability and receipt cannot drift apart. Design discussion and alternatives considered (binary
use_sharedsentinel, separate incoming-mask permission key, universal matching) are written up in coder-internalscott-misc:rfcs/gateway-accounts/design-acl-capability-gating.md; an earlier sentinel-based implementation is preserved onscott/gateway-3-use-shared-v1.Mechanism
Opt-in per type via an explicit
GatedACL boolon the policyPermissionDefinition; no per-type rego edits. The flag is derived from the object type in Go (rbac.ACLUseGated) and passed as the input fieldinput.object.acl_use_gated, using an explicit== falsecomparison so input paths that omit the field fail closed. Becauseacl_action_orgsdepends only on subject roles, object type, and the action — all known at prepare time — the precondition resolves during partial evaluation and list-query SQL filters enforce it; residual queries and the regosql conversion are unaffected. The field is included in the AST fast path, the partial input, andObject's JSON encoding so all input paths stay consistent.Currently gated:
workspaceandworkspace_dormant. Member-level workspace actions travel withorganization-workspace-accessand, whileMinimumImplicitMemberis off, with the elevation bundled intoorganization-member, so default deployments see no behavior change.acl_action_orgsfollows standard vote semantics: a negated member-level permission for an action removes the org for that action even when a positive grant exists, while other granted actions keep working. Ungated types (templates, chats) keep ACL-only grants.API surface
PATCH /workspaces/:id/aclvalidates user recipients by simulating the grant: each granted action is authorized against the workspace's RBAC object with the proposed entry attached, and recipients for whom no action would authorize are rejected with a validation error, so shares don't silently grant nothing. Partial capability is accepted with partial effect. Recipient existence and role resolution run as system and fail closed when roles cannot be resolved. Group entries are intentionally not validated at share time (membership changes after the share); eligibility is enforced per-member at access time.user_limitunder permission-based licensing) counts users holding any member-level workspace action, since such users can operate workspaces shared with them even without workspace-create.Includes a skipped desired-behavior test (
TestWorkspaceSharingDormancySurvivesACL) documenting a pre-existing bug whereDormantRBAC()drops ACLs; the precondition neither causes nor fixes it. Documented incoderd/rbac/POLICY.md.Part of the gateway-accounts feature.
Stack
Part 3 of the gateway-accounts stack. Each PR builds on the previous:
permission-based-licensingexperiment and gated on the AI Governance add-on,user_limitcounts only users the RBAC engine authorizes to create workspaces.organization-ai-gateway-accessorg role carrying the AI Bridge interception permissions (extracted from the member floors, backfilled into org default roles by migration) and enforces it at AI Gateway authentication; bridge usage stops claiming AI Governance seats under the experiment.Related but independent: #27278 hides the Workspaces page create CTAs for users without workspace-create permission.