Files
zitadel/cmd/setup/73.sql
T
dc899002ec fix: Correctly remove adjacent roles on user grants
# Which Problems Are Solved

Addresses a bug that leads roles on project grants to not be correctly removed. Final outcome: a role that the user thought to be removed, might still be in place.

# How the Problems Are Solved

- fix iteration of roles to be removed
- added migration to fix wrongly computed roles

# Additional Context

Affects management API only

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Livio Spring <9405495+livio-a@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

(cherry picked from commit 9d60e83d6f)
2026-07-10 07:37:22 +02:00

40 lines
2.0 KiB
SQL

-- Finds user grants whose stored roles include roles that are no longer valid
-- (the residue left by the buggy cascade removal, GHSA-v859-c572-qh5p), and
-- returns the corrected role set for each. $1 = instance_id, run once per instance.
--
-- Scope: only grant-based user grants (ug.grant_id set) can be affected by this
-- bug. It was introduced by ChangeProjectGrant (internal/command/project_grant.go),
-- which cascades a *multi*-role removal to the user grants tied to that project
-- grant. Direct user grants never go through a multi-role removal call, so any
-- role mismatch there is unrelated to this CVE (e.g. pre-existing role_key drift)
-- and must not be "fixed" here to avoid stripping unrelated, legitimate roles.
WITH computed AS (
SELECT
ug.id,
ug.resource_owner,
ug.roles AS current_roles,
-- Recompute the roles this grant is actually allowed to keep by filtering
-- its current roles down to the ones still granted by the project grant.
COALESCE(
ARRAY(
SELECT r
FROM unnest(ug.roles) AS r -- expand the stored roles array
WHERE r = ANY(pg.granted_role_keys)
),
ARRAY[]::TEXT[] -- keep NULL out; use empty array
) AS valid_roles
FROM projections.user_grants5 ug
JOIN projections.project_grants4 pg
ON pg.instance_id = ug.instance_id
AND pg.grant_id = ug.grant_id
WHERE ug.instance_id = $1
AND ug.grant_id IS NOT NULL AND ug.grant_id <> '' -- direct grants are out of scope, see above
AND ug.roles IS NOT NULL
AND cardinality(ug.roles) > 0 -- nothing to correct on empty grants
)
-- valid_roles is always a subset of current_roles, so a size mismatch means at
-- least one stale role was dropped -> this grant needs a corrective event.
SELECT id, resource_owner, valid_roles
FROM computed
WHERE cardinality(valid_roles) <> cardinality(current_roles);