Skip to content

feat(defaults): drop the schema-wide public grants, add verify/revert/tests - #113

Merged
pyramation merged 1 commit into
mainfrom
feat/public-hardening-no-blanket-grants
Aug 4, 2026
Merged

feat(defaults): drop the schema-wide public grants, add verify/revert/tests#113
pyramation merged 1 commit into
mainfrom
feat/public-hardening-no-blanket-grants

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

defaults/public revoked PUBLIC's privileges and then immediately handed schema public back to three roles wholesale:

 ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;
 REVOKE CREATE ON SCHEMA public FROM PUBLIC;
-GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO authenticated, anonymous, administrator;
-ALTER DEFAULT PRIVILEGES IN SCHEMA public
-GRANT EXECUTE ON FUNCTIONS TO authenticated, anonymous, administrator;

Measured on a real deploy, schema public holds 1,499 extension functions + 19 pgpm-verify deploy helpers and zero application functions — and all 1,518 already carry =X from before the revoke, because extensions are created earlier. So the first grant only added redundant ACL entries, while the default grant kept every future function in public reachable by anonymous — a blanket anonymous surface, which is exactly what the revoke above exists to prevent. Both are gone; a function in public now needs an explicit grant, which fails closed.

verify/ and revert/ were still -- XXX stubs. Verify now proves the four properties the deploy claims, the load-bearing one being that the database-wide default exists and excludes PUBLIC — only the schema-less, role-scoped form of ALTER DEFAULT PRIVILEGES reaches schemas other than public, so the check is on defaclnamespace = 0:

SELECT 1 FROM pg_default_acl
 WHERE defaclnamespace = 0 AND defaclobjtype = 'f'
   AND current_user::regrole::oid = defaclrole

Revert restores PostgreSQL's out-of-the-box PUBLIC privileges (CREATE on schema public, the function default, CONNECT/TEMPORARY on the database).

Ordering (why the new extension test matters)

ALTER DEFAULT PRIVILEGES is prospective, so an extension created after this module never gets PUBLIC=X and its functions become uncallable — deploying pgpm-defaults before extensions breaks 47 citext + 37 pgcrypto functions, and 'Abc'::citext = 'abc'::citext fails with permission denied for function citext_eq, which looks nothing like a permissions bug at the call site. The new extension ordering test pins that failure mode so the requirement (deploy this module last) can't be silently dropped.

17/17 tests pass; the snapshot's default_func_acl_count drops 2 → 1 with the schema-scoped default gone.

Context

Split out of constructive-io/constructive-db#2734, which wires pgpm-defaults into the deployment graph — the module is vendored there, and constructive-db's CI doesn't run pgpm-modules/** jest suites, so the change and its tests belong here.

Link to Devin session: https://app.devin.ai/sessions/76e3c8aa95f24cd1af720e76280ddf90
Requested by: @pyramation

…/tests

The module revoked PUBLIC's database and function privileges and then handed schema public straight back to authenticated, anonymous and administrator, reopening as a blanket role grant what the revoke had just closed. Schema public holds only extension functions (which keep PUBLIC=X from before the revoke) and the pgpm-verify deploy helpers, so the grants added redundant ACL entries and one real hole. A function in public now needs an explicit grant.

verify/ and revert/ were still stubs: verify now proves the four properties the deploy claims (no PUBLIC database CREATE, no schema public CREATE, a database-wide function default for the owner that excludes PUBLIC, no schema-wide default in public), and revert restores PostgreSQL's out-of-the-box PUBLIC privileges.

Tests cover the fail-closed grant behavior and the ordering requirement: an extension created after this module has no PUBLIC execution, so the module must be deployed last.
@pyramation pyramation self-assigned this Aug 4, 2026
@devin-ai-integration

Copy link
Copy Markdown

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@pyramation
pyramation merged commit af334a3 into main Aug 4, 2026
25 checks passed
@pyramation
pyramation deleted the feat/public-hardening-no-blanket-grants branch August 4, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant