GitHub Security Advisory: GHSA-423p-g724-fr39
Package: github.com/cloudnative-pg/cloudnative-pg (GO)
Severity: CRITICAL (CVSS 0.0)
CWE: CWE-250: Execution with Unnecessary Privileges, CWE-271: Privilege Dropping / Lowering Errors, CWE-426: Untrusted Search Path
Vulnerable versions: >= 1.29.0, < 1.29.1
Patched in: 1.29.1

Summary: CloudNativePG's metrics exporter allows privilege escalation to PostgreSQL superuser and OS RCE

Details:
### Impact

The CloudNativePG metrics exporter opens its PostgreSQL connection as the `postgres` superuser via the pod-local Unix socket, then demotes the session with `SET ROLE pg_monitor`. `SET ROLE` changes only `current_user`; `session_user` remains `postgres`. That residual superuser identity is the foothold for the rest of the chain.

Any SQL expression evaluated inside the scrape session can invoke `RESET ROLE` to recover real superuser privileges, then use `COPY ... TO PROGRAM` to spawn an OS-level subprocess as the `postgres` user inside the primary pod. The `READ ONLY` transaction flag does not block this; it gates writes to database state, not external processes.

Two exploitation paths follow from this root cause.

#### Path 1: custom metric queries with unqualified identifiers (all supported releases)

A database user who owns a schema on the `search_path` of any scraped database can plant a shadow object whose name matches an unqualified identifier in a custom metric query. When the exporter next evaluates that query, the shadow expression executes inside the `session_user = postgres` scrape session, giving the attacker PostgreSQL superuser privileges and OS command execution inside the primary pod within one scrape interval (≤30 s). Exploitability requires a custom metric query that contains an unqualified relation or function reference.

Although `search_path` shadowing of unqualified identifiers is the most direct case, the underlying bug is that any expression evaluated inside the scrape session is a superuser code path. Other exploitable shapes include user-defined functions, operators or casts resolved during the scrape, joins or subqueries against user-owned tables and views, and index expressions or RLS policies on read-touched objects.

#### Path 2: stock `default-monitoring.yaml` (all supported releases, no custom metrics required)

The `pg_extensions` metric shipped in `default-monitoring.yaml` used an unqualified `current_database()` call and ran against every user database (`target_databases: '*'`). Any non-superuser who owns a user database (including the default `app` role created by `bootstrap.initdb`) could shadow `current_database()` and trigger the full escalation chain against a stock CNPG deployment on the first scrape after the shadow was planted.

#### Combined impact

The chain yields privilege escalation from a low-privileged database role (e.g. the default `app` role) to PostgreSQL superuser, plus arbitrary OS command execution as the `postgres` user inside the primary pod, all within one scrape interval. A web application SQL injection vulnerability in an app backed by a CNPG cluster is therefore sufficient to pivot to database-pod RCE.

#### Who is impacted

- All deployments on any supported release with default monitoring enabled are affected by Path 2.
- All deployments on any supported release that use custom metric queries containing unqualified catalog references are affected by Path 1.
- Multi-tena