Repository navigation
fix: only treat default privileges FOR ROLE the applying role as auto-granting (#696) - #704
Conversation
…-granting (#696) A default privilege fires only for objects created by its FOR ROLE owner, and apply creates objects as the role it connects with. The diff treated every default privilege as active for new tables, so with ALTER DEFAULT PRIVILEGES FOR ROLE other_role the dump omitted the explicit table grants, apply emitted a REVOKE for the grant it assumed would appear, and a re-plan saw no drift. The inspector now records the connected role in the IR metadata, and the active-default shortcut (skip covered GRANTs on new tables, REVOKE uncovered auto-grants) only considers defaults whose owner is that role. A dump has no apply role and therefore emits every explicit grant, like pg_dump. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
[High impact] The change affects privilege plans for tables created by pgschema. Fix the two paths that can grant unintended table access before merging. FindingsSummaryRecords
Reviews (1) · Last reviewed commit: "fix: only treat default privileges FOR R..." · Reviewed by Greptile |
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical privilege-handling issues can omit required grants or revokes when saved plans execute under a different role and restore explicitly revoked access when dumps are reloaded.
2 open issues
What changed in this PR
This PR addresses #696 by making pgschema’s default-privilege handling depend on the inspected role, preserving explicit grants when another role owns the defaults.
Changes:
- Records the connected role in IR metadata.
- Filters automatic-grant assumptions by role and retains explicit grants in dumps.
- Adds regression fixtures covering new tables with and without explicit grants under another role’s defaults.
| File | Description |
|---|---|
testdata/diff/default_privilege/issue_696_for_role_other_owner/plan.txt |
Expected human-readable migration plan. |
testdata/diff/default_privilege/issue_696_for_role_other_owner/plan.sql |
Expected executable plan SQL. |
testdata/diff/default_privilege/issue_696_for_role_other_owner/plan.json |
Expected serialized migration plan. |
testdata/diff/default_privilege/issue_696_for_role_other_owner/old.sql |
Establishes another role’s default privileges. |
testdata/diff/default_privilege/issue_696_for_role_other_owner/new.sql |
Adds tables with and without explicit SELECT grants. |
testdata/diff/default_privilege/issue_696_for_role_other_owner/diff.sql |
Expects the explicit GRANT without an unnecessary REVOKE. |
ir/ir.go |
Adds current-role metadata. |
ir/inspector.go |
Captures current_user during inspection. |
internal/diff/diff.go |
Restricts active defaults to the inspected role. |
Review effort: Balanced
…default privileges (#696) Review follow-up: scoping the default-privilege shortcut to the planning role made saved plans role-dependent and dropped the conservative REVOKEs from dumps, which would restore withheld access when a dump is reloaded as the default's owner. The diff is now role-independent: explicit grants on new tables are always emitted (a no-op when the default also fired), and the REVOKEs for auto-granted privileges the desired state omits are computed against every active default as before. IR.Metadata.CurrentRole is removed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Summary
A default privilege only fires for objects created by its
FOR ROLEowner, but the diff treated every default as if it would fire for the tables a migration creates. WithALTER DEFAULT PRIVILEGES FOR ROLE other_role ... GRANT SELECT ON TABLES TO reader:dumpomitted the explicitGRANT SELECT ON TABLE t TO readeron every covered table,applyas a role other thanother_rolethen produced tables without the grant (and emitted aREVOKEfor the grant it assumed would be auto-created),No changes detectedwhilereaderhad lost access.The plan cannot know which role will run it:
applymay run as a different role thanplan, a saved plan may be executed by anyone, and a dump has no apply role at all. So the diff is now role-independent in both directions:GRANTis a no-op when the default did fire; a missing one was a silent privilege loss. This is the only behavior change from main.REVOKEs for privileges a default would auto-grant on a new table but the desired state does not include (Default privileges + selective REVOKE requires two apply cycles to converge #253) are computed against every active default, as before. TheREVOKEis a no-op when the default did not fire and withholds the access when it did, so a plan or dump stays correct under whichever role restores it.The "don't REVOKE an existing grant that a desired default covers" rule from #250 is unchanged.
Fixes #696
Test plan
testdata/diff/default_privilege/issue_696_for_role_other_owner: aFOR ROLE app_ownerdefault, plus a new table with an explicit grant and one without. Before: no GRANT and a bogus-looking REVOKE. Now:GRANT SELECT ON TABLE accounts TO app_readerplus the conservativeREVOKE SELECT ON TABLE audit_log FROM app_reader; apply as the test role and re-plan are idempotent.TestDumpKeepsExplicitGrantsAndWithheldDefaults(internal/diff): a dump keeps both the explicit grant and the REVOKE for a withheld grant. Fails on main (the GRANT is missing).default_privilege/add_table_privilegeandprivilege/revoke_default_privilegenow also list the explicit grants on their new tables; all other cases in both categories are unchanged.postgresgrants it,has_table_privilegeis true, re-plan is clean.🤖 Generated with Claude Code