9. The security review
On this page
An auditor asks a harder question than “what grants are in our YAML?”: can this role actually perform the operation? Acme’s role graph is tidy, but PostgreSQL has access paths outside ordinary named ACLs.
The guided steps expose unexpected access paths. As an optional challenge, remove one unintended path and verify that the affected role can no longer perform the forbidden operation.
Chapter 9 · Security review
PostgreSQL 18.3 · PGlite ↗The auditor asks: “Who can really do this?”
Effective access hides outside ordinary direct ACLs. Investigate four surprises: PUBLIC, SECURITY DEFINER, delegated grant options, and broad predefined roles.
Chapter 9 · Security review
PG 18.3Step 1 of 5
Every Run starts from this step’s prepared database. Changes from previous runs are discarded.
Step 1
Surprise 1: PUBLIC can execute
PostgreSQL grants EXECUTE on new functions to PUBLIC by default. Every role is part of PUBLIC, even when no named function grant exists.
Call the function as contractor, who has no declared access.
Editable SQL · disposable browser database · changes are discarded after the run
PostgreSQL output
PostgreSQL’s rows, command tags, or exact error will appear here.
How the browser lab models roles
The selector sets session authorization inside an isolated PGlite database. Statements run one at a time and autocommit, like psql: execution stops at the first error, and earlier statements keep their effect. PostgreSQL performs ordinary role, ownership, schema, and object checks; the diagram comes from catalog privilege queries after your SQL. This is not a password, CONNECT, pg_hba.conf, or concurrent-session test.
Close the PUBLIC path explicitly
PostgreSQL gives PUBLIC—every role—EXECUTE on new functions by default. State both desired absences so existing and future functions converge:
grants:
- role: PUBLIC
ensure: absent
privileges: [EXECUTE]
object: { type: function, schema: billing_api, name: "*" }
default_privileges:
- owner: app_owner
scope: { type: global }
grant:
- role: PUBLIC
ensure: absent
privileges: [EXECUTE]
on_type: function
The global default matters because PostgreSQL’s built-in function default is global. A schema-scoped revoke cannot subtract a global grant.
Review predefined roles by capability
Predefined roles provide built-in capabilities. Audit their memberships alongside object grants: neither view alone describes all effective access. Categorize the capability each one supplies:
pg_read_all_dataandpg_write_all_dataare broad data-access roles for matching objects, but they do not bypass row-level security.pg_monitorgroupspg_read_all_settings,pg_read_all_stats, andpg_stat_scan_tablesfor configuration, statistics, and observation; it is not a business-data ACL.pg_signal_backendcan signal ordinary backends, but cannot signal superuser backends.pg_database_ownerhas exactly one implicit member, the current database owner, and membership in it cannot be granted.pg_read_server_files,pg_write_server_files, andpg_execute_server_programare high-risk server-file or program-execution capabilities.
An object-grant review alone misses built-in capabilities. Auditing effective access therefore includes one more question: who is a member of a pg_* role?
pgroles can declare memberships in a predefined role, including an explicit complete-member assertion. See predefined and external granted roles for the managed declaration and its adoption behavior.
Optional repair and verification
The lab presents paths that are easy to miss: an ordinary login reaches a function through PUBLIC, a SECURITY DEFINER function runs with its owner's authority, or a role can delegate through WITH GRANT OPTION. For the optional repair challenge, put the repair and verification in the same script: every Run starts a fresh database. A negative test passes by proving the forbidden outcome is absent. The query might raise a permission error, return no forbidden rows, or update zero rows.
A SECURITY DEFINER function needs review of its owner, body, fixed search_path, callable surface, and PUBLIC exposure. WITH GRANT OPTION needs separate review of who can delegate. See memberships and grants for the managed declarations and their limits.
One more finding costs nothing to write down: Acme’s application still connects with the founder-era admin credentials, and a superuser bypasses every check in this chapter. No grant, policy, or RLS rule constrains that connection—pgroles cannot manage it away. Moving the application onto a scoped login, the way reporting_app was built in chapter 2, is the remediation an auditor will ask for first.
Desired ACLs are necessary; effective-access tests tell you whether every other path agrees with them.