CVE-2026-44477: CloudNativePG: Metrics exporter allows privilege escalation to PostgreSQL superuser and OS RCE
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 pgmonitor. SET ROLE changes only currentuser; sessionuser 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 searchpath 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 sessionuser = 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 searchpath 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 pgextensions metric shipped in default-monitoring.yaml used an unqualified currentdatabase() call and ran against every user database (targetdatabases: ''). Any non-superuser who owns a user database (including the default app role created by bootstrap.initdb) could shadow currentdatabase() 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 pgcatalog 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 ConfigMaps, 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 cnpgmetricsexporter role with pgident.conf peer mapping"
Introduces a dedicated cnpgmetricsexporter PostgreSQL role (granted pgmonitor, no superuser privileges) and maps it in pgident.conf via peer authentication on the local Unix socket, following the same pattern already used for cnpgpoolerpgbouncer. The metrics exporter connects as this role instead of postgres, so sessionuser 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 pgmonitor) is not sufficient: the privilege check for SET SESSION AUTHORIZATION is whether the authenticated user is a superuser, not the current sessionuser. 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 pgmonitor does not grant. As defense in depth, the monitoring transaction also prepends pgcatalog to the connection's searchpath, 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 pgmonitor's scope (catalog reads, pgstat views, settings) continue to work without modification. Queries that previously relied on superuser-level access (reading user-owned tables not granted to cnpgmetricsexporter, or superuser-only catalogs such as pgauthid or pgsubscription) will fail and need explicit GRANT statements to cnpgmetricsexporter.
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 cnpgmetricsexporter 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 pgcatalog. prefixes for all catalog functions and views (e.g. pgcatalog.currentdatabase(), pgcatalog.now()). This is a partial mitigation: it closes the searchpath-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 searchpath in a scraped database, typically by owning the database (and therefore public via pgdatabaseowner) or by holding CREATE on a schema already reachable through searchpath.
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 targetdatabases: '' queries. Avoid targetdatabases: '' unless every database in the cluster, and every role that owns one, is fully trusted. Where possible, restrict targetdatabases to specific, known-safe databases.
4. Do not expose metric query SQL to untrusted users. Multi-tenant platforms that allow customers to supply or influence custom metric query bodies should treat this as a critical trust boundary until the architectural fix is released.
References
- Fix (Patch 1): PR #10576 "schema-qualify catalog references in default monitoring queries and documentation samples" - Fix (Patch 2): "dedicated cnpgmetricsexporter role with pgident.conf peer mapping" - Reported by: Mehmet Ince
Other sources
CloudNativePG is a platform designed to manage PostgreSQL databases within Kubernetes environments. Prior to 1.29.1 and 1.28.3, 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 pgmonitor. SET ROLE changes only currentuser; sessionuser remains postgres. 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. This vulnerability is fixed in 1.29.1 and 1.28.3.
— MITRE
Affected Software
Remediation
Patch Available
Event History
Frequently Asked Questions
What is the severity of CVE-2026-44477?
CVE-2026-44477 is categorized as a high-severity vulnerability due to the residual superuser identity in PostgreSQL.
How do I fix CVE-2026-44477?
To fix CVE-2026-44477, upgrade to CloudNativePG version 1.29.1 or later, or to version 1.28.3.
What is the main impact of CVE-2026-44477?
The main impact of CVE-2026-44477 is the risk of unauthorized access due to the connection being established as a superuser before the role demotion.
Which versions are affected by CVE-2026-44477?
CVE-2026-44477 affects CloudNativePG versions prior to 1.29.1 and those before 1.28.3.
Is there a workaround for CVE-2026-44477?
Currently, the recommended action is to upgrade to the fixed versions as no specific workaround is provided for CVE-2026-44477.