GitHub Advisoryadvisory · Go
GHSA-423p-g724-fr39 CloudNativePG's metrics exporter allows privilege escalation to PostgreSQL superuser and OS RCE
CRITICAL
Official page ↗
- Aliases
- CVE-2026-44477
- Ecosystem
- Go
- Packages
- github.com/cloudnative-pg/cloudnative-pg
- Severity
- Critical
- CVSS vector
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H
- Weaknesses
- CWE-250, CWE-271, CWE-426
- Reviewed
- 2026-05-11
Affected packages
• Go github.com/cloudnative-pg/cloudnative-pg: < 1.28.3 → fixed in 1.28.3
• Go github.com/cloudnative-pg/cloudnative-pg: >= 1.29.0, < 1.29.1 → fixed in 1.29.1
Description
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-tenant platforms that allow customers to supply or influence custom metric query bodies are at the highest risk for Path 1.
Patches
Three separate patches address the vulnerability.
Patch 1: PR #10576 "schema-qualify catalog references in default monitoring queries and documentation samples"
Schema-qualifies all unqualified `pg_catalog` function and view references in the shipped `default-monitoring.yaml` and in documentation examples. This closes Path 2 in operator-shipped configuration and removes the unqualified-identifier attack surface from all operator-shipped metric queries. Operators who clone or copy `default-monitoring.yaml` into custom monitoring `ConfigMap`s, or have copy-pasted unqualified queries elsewhere, must re-qualify those queries themselves.
Backported to all currently supported releases:
• v1.29.x (x ≥ 1)
• v1.28.x (x ≥ 3)
Patch 2: "dedicated `cnpg_metrics_exporter` role with `pg_ident.conf` peer mapping"
Introduces a dedicated `cnpg_metrics_exporter` PostgreSQL role (granted `pg_monitor`, no superuser privileges) and maps it in `pg_ident.conf` via peer authentication on the local Unix socket, following the same pattern already used for `cnpg_pooler_pgbouncer`. The metrics exporter connects as this role instead of `postgres`, so `session_user` is never a superuser and `RESET ROLE` has no escalation effect. This eliminates the root cause entirely.
Demoting the session at the SQL level (via `SET SESSION AUTHORIZATION pg_monitor`) is not sufficient: the privilege check for `SET SESSION AUTHORIZATION` is whether the *authenticated* user is a superuser, not the current `session_user`. With the connection still authenticated as `postgres`, any SQL in the session can run `RESET SESSION AUTHORIZATION` and recover the original superuser identity. This is the same recovery primitive as `RESET ROLE`, one layer up. Only changing the authenticated user closes the loop.
With this change in place, the original chain breaks at every step: `RESET ROLE` and `RESET SESSION AUTHORIZATION` cannot recover superuser, and `COPY ... TO PROGRAM` requires a privilege `pg_monitor` does not grant. As defense in depth, the monitoring transaction also prepends `pg_catalog` to the connection's `search_path`, so unqualified catalog identifiers cannot resolve to user-planted shadow objects.
This patch changes the connection identity but not how queries are evaluated. Custom metric queries within `pg_monitor`'s scope (catalog reads, `pg_stat_*` views, settings) continue to work without modification. Queries that previously relied on superuser-level access (reading user-owned tables not granted to `cnpg_metrics_exporter`, or superuser-only catalogs such as `pg_authid` or `pg_subscription`) will fail and need explicit `GRANT` statements to `cnpg_metrics_exporter`.
The role is created and maintained with `PASSWORD NULL`; any password set out-of-band is cleared on the next reconcile, so the role cannot be authenticated by password regardless of operator pre-creation.
For replica clusters, upgrade the source primary cluster before any replica clusters that consume from it. The `cnpg_metrics_exporter` role is created on the source primary and replicates downstream; a replica cluster upgraded first will scrape against a missing role until the source primary upgrades or the role is created manually (see the monitoring documentation).
The patch will be backported to all currently supported releases:
• v1.29.x (x ≥ 1)
• v1.28.x (x ≥ 3)
Workarounds
If upgrading immediately is not possible:
1. Schema-qualify all identifiers in custom metric queries. Use explicit `pg_catalog.` prefixes for all catalog functions and views (e.g. `pg_catalog.current_database()`, `pg_catalog.now()`). This is a partial mitigation: it closes the `search_path`-shadowing shape in operator- and user-supplied metric bodies, but other expression shapes (user-defined functions, operators or casts; joins or subqueries on user-owned tables and views; RLS policies on read-touched objects) remain superuser code paths until Patch 2 lands.
2. Restrict database ownership. Ensure only fully trusted roles own user databases in scraped clusters. The exploit requires the ability to plant an object on the metrics exporter's `search_path` in a scraped database, typically by owning the database (and therefore `public` via `pg_database_owner`) or by holding `CREATE` on a schema already reachable through `search_path`.
*PG <15 caveat:* `public` grants `CREATE` to `PUBLIC` by default before PostgreSQL 15, so any authenticated role in a scraped database can plant a shadow object regardless of ownership.
3. **Limit the scope of `target_databases: '*'` queries.** Avoid `target_databases: '*'` unless every database in the cluster, and every role that owns one, is fully trusted. Where possible, restrict `target_databases` to specific, known-safe databases.
4. Do not expose metric query SQL to untrusted users. Multi-tenant platforms that allow customers to s…
Related
Alias · CVE (1)
- CVE-2026-44477 CloudNativePG is a platform designed to manage PostgreSQL databases within Kubernetes environments. vulnerability
References
- https://github.com/cloudnative-pg/cloudnative-pg/security/advisories/GHSA-423p-g724-fr39 web
- https://nvd.nist.gov/vuln/detail/CVE-2026-44477 advisory
- https://github.com/cloudnative-pg/cloudnative-pg/pull/10576 web
- https://github.com/cloudnative-pg/cloudnative-pg package
- https://github.com/cloudnative-pg/cloudnative-pg/releases/tag/v1.28.3 web
- https://github.com/cloudnative-pg/cloudnative-pg/releases/tag/v1.29.1 web