SEC.PRIV.ROLE_SUPERUSER — The account is a superuser, or one SET ROLE away
- Category: security
- Severity: high
- Level: 0
- Confidence: deterministic
- Downtime class: none — the finding is about an account, not about a statement
- Stability: stable
- Suites: audit
- Applies to: PostgreSQL 18
There is nothing left to check
Row-level security does not apply to a superuser. GRANT and REVOKE do not constrain it. It reads
and writes every file the database process can, and it can run COPY … FROM PROGRAM. Every other
finding SQLens produces is a statement about a boundary; this one says there is no boundary for the
others to be about.
The common shape is the one you cannot see
Almost nobody sets SUPERUSER on an application role. What happens instead is that the application
role is granted a role that has it — often the bootstrap role, often during one migration that needed
it once and was never revoked.
PostgreSQL does not inherit role attributes through membership. So \du shows your application
role as unremarkable, pg_roles.rolsuper is false for it, and one SET ROLE makes it a superuser
anyway. That is why this finding names the path it found:
app -> deploy -> postgres
Revoke the membership that leads there. Removing the attribute at the end of the chain is usually not what you want — that role may exist precisely to have it.
Bad
-- The obvious form.
ALTER ROLE app SUPERUSER;
-- And the one that hides: `app` looks ordinary in every listing.
GRANT postgres TO app;
Good
REVOKE postgres FROM app;
-- Give the application what it needs, by name.
GRANT USAGE ON SCHEMA public TO app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app;
What about NOINHERIT?
It changes nothing here, and the reason is worth knowing: NOINHERIT governs privileges — an
inheriting member automatically holds what its roles were granted, a non-inheriting one has to
SET ROLE first. Role attributes (SUPERUSER, CREATEROLE, BYPASSRLS) are not inherited by
either, on any PostgreSQL version.
So both accounts are exactly one statement away from the attribute, and SQLens rates them the same. A tool that rated them differently would be describing a distinction the engine does not make.
When it is deliberate
A migration connection may legitimately need more than the runtime one — that is the two-connection setup SQLens recommends. Audit the runtime role, and record a decision for the migration one.