SEC.PRIV.GRANT_OPTION — The account can hand its access on
- Category: security
- Severity: medium
- Level: 0
- Confidence: deterministic
- Downtime class: none — the finding is about a grant, not about a statement
- Stability: stable
- Suites: audit
- Applies to: PostgreSQL 18 · MySQL 8.4
What it actually costs
Every other grant rule asks what an account can do. This one asks what it can give away — and that is a different kind of fact.
An account that may re-grant what it holds can create a second account holding the same access. The second one carries no note saying where it came from, so a privilege audit run tomorrow sees a plain account rather than the path that made it. Any limit you place elsewhere on the server becomes voluntary at that point.
Bad
-- The account can pass this on to anybody it likes.
GRANT SELECT, INSERT ON shop.* TO 'app'@'10.0.0.%' WITH GRANT OPTION;
Good
-- The same access, without the ability to widen the server.
GRANT SELECT, INSERT ON shop.* TO 'app'@'10.0.0.%';
-- On an account that already has it, this takes back only the handing-on:
REVOKE GRANT OPTION FOR SELECT, INSERT ON shop.* FROM 'app'@'10.0.0.%';
The second form is the one people miss. REVOKE GRANT OPTION FOR leaves the access itself intact —
so this finding can be acted on without changing what the application can do, which makes it cheap
even where the grant itself is correct.
Both engines, one decision
This is one of the few security facts neither engine has to itself. MySQL spells it
WITH GRANT OPTION and records it in the grant tables; PostgreSQL records it in the ACL, where
aclexplode().is_grantable reports it. The rule carries no engine check — a driver test here
would be a test about wording.
Why medium
What is handed on is access to data, so the blast radius is bounded by what this grant already
covers. Where the re-grantable privilege reshapes the schema instead, that is a different
sentence at a different weight:
SEC.PRIV.GRANT_OPTION_STRUCTURAL.
What is not reported
A grant the engine ships. Anything that is not a project decision — initdb's own grants, an
extension's — is excluded, or the rule would fire on a database created a minute ago.
A grant that could not be read in full is undetermined. "Not grantable" and "we could not see
whether it is grantable" read the same to somebody who cannot tell them apart, and only one of them
is good news.
Related
SEC.PRIV.GRANT_OPTION_STRUCTURAL— the same clause on a privilege that changes the schema.SEC.PRIV.GRANT_PUBLIC— a grant that reaches every role instead of one.