🌐 US-Proxy
class="logged-out env-production page-responsive" style="word-wrap: break-word;" >
Skip to content

fix!: require org membership for user ACLs - #26852

Merged
sreya merged 3 commits into
mainfrom
org-acl-vuln
Jul 1, 2026
Merged

fix!: require org membership for user ACLs#26852
sreya merged 3 commits into
mainfrom
org-acl-vuln

Conversation

@sreya

@sreya sreya commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Per-user ACL grants on org-scoped resources were authorized without checking organization membership. A user explicitly granted access to an org-scoped template/workspace/chat retained that access after being removed from the organization, because the user-ACL branch of policy.rego did not require is_org_member (the group-ACL branches already did).

This adds is_org_member to the user-ACL rule, matching the group-ACL behavior. After the change, a per-user ACL grant only authorizes a subject who is currently a member of the resource's organization.

@sreya
sreya requested a review from Emyrk as a code owner June 30, 2026 05:02
@github-actions github-actions Bot added the release/breaking This label is applied to PRs to detect breaking changes as part of the release process label Jun 30, 2026
@sreya

sreya commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

/coder-agents-review

@coder-agents-review

coder-agents-review Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-06-30 19:32 UTC by @sreya
Spend: $41.80 / $100.00

Review history
  • R1 (2026-06-30): 19 reviewers, 4 Nit, 2 P2, 2 P3, COMMENT. Review
  • R2 (2026-06-30), 4 Nit, 2 P2, 2 P3, COMMENT. Review

deep-review v0.9.0 | Round 2 | 612b6d4..c5a5a62

Last posted: Round 2, 8 findings (2 P2, 2 P3, 4 Nit), COMMENT. Review

Finding inventory

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed (c5a5a62) organizationmembers.sql:54 CTE-based ACL cleanup misses soft-delete path; trigger pattern would cover all DELETE paths R1 Mafuuu P2, Zoro P2, Hisoka P3, Knuckle P3, Knov P3, Kite P3, Robin P3 Yes
CRF-2 P2 Open PR description Breaking change ships with empty description R1 Mafu-san P2, Leorio P2 Yes
CRF-3 P3 Open acl/updatevalidator.go:28 No org membership validation on ACL write path; non-member entries silently activate on join R1 Hisoka P3, Kurapika P3 Yes
CRF-4 P3 Author fixed (c5a5a62) commit 5b90932 Fix and scrub commits for authorization change have no bodies R1 Leorio P3, Mafu-san Nit Yes
CRF-5 Nit Author fixed (c5a5a62) authz_internal_test.go:800 Redundant ANT-2026-22460 reference duplicates function doc at line 735 R1 Gon Yes
CRF-6 Nit Open policy.rego:333 User ACL is_org_member guard has no explanatory comment; group ACL rules have one R1 Leorio Yes
CRF-7 Nit Author fixed (c5a5a62) organizationmembers.sql:53 DeleteOrganizationMember now has hidden UPDATE side effects with no doc comment R1 Leorio Yes
CRF-8 Nit Author fixed (c5a5a62) querier_test.go:5620 TestDeleteOrganizationMemberRemovesUserACLs has no doc comment; sibling test does R1 Leorio Yes

Round log

Round 1

Panel (19 reviewers). Netero: no findings. 2 P2, 2 P3, 4 Nit. Reviewed against 612b6d4..61b727b.

Round 2

BLOCKED. CRF-2 (P2), CRF-3 (P3), CRF-6 (Nit) silent. 5 findings addressed by removing CTE scrub and rewriting tests. No review.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@coder-tasks

coder-tasks Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • docs/admin/templates/template-permissions.md - Add that users must be members of the template's organization for per-user ACL grants to take effect.
  • docs/user-guides/shared-workspaces.md - Add a prerequisite or note that workspace sharing via user ACLs requires the target user to be a member of the workspace's organization.
  • docs/admin/users/organizations.md - Document that removing a member from an organization now also removes their per-user ACL entries from templates, workspaces, and chats within that organization. (database ACL-scrubbing changes were removed from this PR)

Automated review via Coder Agents

@coder-agents-review coder-agents-review Bot 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.

The core fix is sound: adding is_org_member to the user ACL rule in policy.rego closes the gap that the TODO comment identified, making user ACLs consistent with the group ACL rules that already required org membership. Test coverage is thorough, with real database tests for the scrub and a well-structured invariant test that locks in both the deny (non-member) and allow (member) paths without breaking site-wide roles.

Severity summary: 2 P2, 2 P3, 4 Nit.

The main concern is the mechanism used for ACL cleanup: the codebase uses BEFORE DELETE triggers on organization_members for sibling cleanup (group membership, AI budget overrides), but this PR uses a CTE embedded in the SQLC query. Seven reviewers flagged this independently. The CTE misses the soft-delete path, where delete_deleted_user_resources() runs a raw DELETE FROM organization_members that fires existing triggers but not the CTE. No security impact (the Rego policy blocks access regardless), but it creates a data hygiene asymmetry.

"A user ACL grant must not authorize a non-member, even one holding a privileged site-wide role." Kite called the test coverage "thorough" and Mafu-san verified genuine test-first development by running the test commits at base SHA and confirming expected failures.


coderd/rbac/acl/updatevalidator.go:30

P3 [CRF-3] Validate checks that user IDs exist (ValidateUserIDs) but never checks whether the user is a member of the resource's organization. An admin can add a user ACL entry for a non-org-member. The Rego policy now blocks the grant from taking effect, but the entry persists. If the user later joins the org for an unrelated reason, the old ACL entry silently activates with no notification.

This gap predates this PR. The PR's is_org_member enforcement makes it newly visible: before, non-member ACL entries were always active (the bug being fixed). After, they lie dormant until a future org join. The write-time check would need the resource's org ID threaded into the validator.

(Hisoka P3, Kurapika P3)

🤖

🤖 This review was automatically generated with Coder Agents.

Comment thread coderd/database/queries/organizationmembers.sql Outdated
Comment thread coderd/database/queries/organizationmembers.sql
Comment thread coderd/rbac/policy.rego
Comment thread coderd/rbac/authz_internal_test.go Outdated
Comment thread coderd/rbac/policy.rego
Comment thread coderd/database/queries/organizationmembers.sql
Comment thread coderd/database/querier_test.go Outdated
Comment on lines +54 to +81
WITH deleted_member AS (
DELETE FROM organization_members AS organization_member
WHERE
organization_id = @organization_id AND
user_id = @user_id
;
organization_member.organization_id = @organization_id
AND organization_member.user_id = @user_id
RETURNING organization_member.organization_id, organization_member.user_id
), scrub_template_user_acls AS (
UPDATE templates
SET user_acl = user_acl - deleted_member.user_id::text
FROM deleted_member
WHERE
templates.organization_id = deleted_member.organization_id
AND templates.user_acl ? deleted_member.user_id::text
), scrub_workspace_user_acls AS (
UPDATE workspaces
SET user_acl = user_acl - deleted_member.user_id::text
FROM deleted_member
WHERE
workspaces.organization_id = deleted_member.organization_id
AND workspaces.user_acl ? deleted_member.user_id::text
), scrub_chat_user_acls AS (
UPDATE chats
SET user_acl = user_acl - deleted_member.user_id::text
FROM deleted_member
WHERE
chats.organization_id = deleted_member.organization_id
AND chats.user_acl ? deleted_member.user_id::text
)

@Emyrk Emyrk Jun 30, 2026

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.

This should probably be a trigger if it is somthing we want to enforce?

@sreya

sreya commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

/coder-agents-review

@coder-agents-review coder-agents-review Bot 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.

Five of eight R1 findings were addressed by removing the CTE scrub and rewriting the test comments. The PR is now cleanly scoped to the Rego policy fix + tests.

Three findings have no author response. Further review is blocked until these are addressed or acknowledged:

CRF-2 (P2) The PR description is still empty. This is a breaking change (fix!:). Operators, changelog tooling, and release notes need: what the old behavior was, what the new behavior is, and what downstream impact to expect.

CRF-3 (P3) acl.Validate does not check org membership on the write path. A non-member can be granted an ACL entry that lies dormant and silently activates if they later join the org. This predates the PR; a brief acknowledgment or ticket is sufficient.

CRF-6 (Nit) The is_org_member guard on the user ACL rule in policy.rego:333 has no explanatory comment. The sibling group ACL rules have one. A one-line comment would match the pattern.

To unblock: reply to these threads (fix, acknowledge, or explain why no action is needed) or push changes. The full review panel will run on the next round.

🤖 This review was automatically generated with Coder Agents.

@sreya
sreya requested a review from Emyrk June 30, 2026 19:55
sreya added 2 commits June 30, 2026 19:56
Adds TestAuthorizeUserACLOrgMembership confirming that requiring org
membership for per-user ACL grants does not change how site-wide roles
authorize org-scoped resources:

  - A site role that grants the action (template-admin) still authorizes
    via its role, with or without an ACL entry, even when the subject is
    not a member of the resource's organization.
  - A per-user ACL grant does not authorize a non-member, even one with a
    privileged site-wide role (user-admin); it only takes effect once the
    subject is also an org member.
@sreya
sreya merged commit 6b3341a into main Jul 1, 2026
28 of 29 checks passed
@sreya
sreya deleted the org-acl-vuln branch July 1, 2026 07:15
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 1, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

cherry-pick release/breaking This label is applied to PRs to detect breaking changes as part of the release process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants