CVE-2026-44477: Github.com/cloudnative pg/cloudnative pg Remote Code Execution Vulnerability

CRITICAL
Advisory Identifier: GHSA-423p-g724-fr39
CVSS 9.9
CWE-271 CWE-426 CWE-250
Published
Advisory Summary

CVE-2026-44477 is a CRITICAL (CVSS 9.9) remote code execution vulnerability (CWE-271: Privilege Dropping / Lowering Errors) affecting Github.com/cloudnative pg/cloudnative pg libraries in the Go ecosystem. The issue involves cloudnativepg's metrics exporter allows privilege escalation to postgresql superuser and os rce. Remediation is available by upgrading affected packages to version 1.28.3, 1.29.1.

CVE-2026-44477 at a Glance

CVE Identifier CVE-2026-44477
GHSA Identifier GHSA-423p-g724-fr39
Severity CRITICAL
CVSS v3.1 Base Score 9.9
CWE Classification CWE-271: Privilege Dropping / Lowering Errors
Affected Product Github.com/cloudnative pg/cloudnative pg
Package Ecosystem Go
Published Date
Last Updated

Security Impact

CVSS: 9.9

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required Low (PR:L)
User Interaction None (UI:N)
Scope Changed (S:C)
Confidentiality High (C:H)
Integrity High (I:H)
Availability High (A:H)

How to Fix CVE-2026-44477

To remediate this vulnerability, upgrade all affected Github.com/cloudnative pg/cloudnative pg dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

github.com/cloudnative-pg/cloudnative-pg fixed: 1.28.3
Upgrade github.com/cloudnative-pg/cloudnative-pg to 1.28.3
github.com/cloudnative-pg/cloudnative-pg fixed: 1.29.1
Upgrade github.com/cloudnative-pg/cloudnative-pg to 1.29.1
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for github.com/cloudnative-pg/cloudnative-pg

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-44477, inspect your project dependencies for Github.com/cloudnative pg/cloudnative pg:

inspect dependencies for github.com/cloudnative-pg/cloudnative-pg

Analyze the terminal output to locate any version of Github.com/cloudnative pg/cloudnative pg in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.28.3), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-271: Privilege Dropping / Lowering Errors, where the product does not drop privileges before passing control of a resource to an actor that does not have those privileges. In Github.com/cloudnative pg/cloudnative pg, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### 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 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 `cnpg_metrics_exporter` role with `pg_ident.conf` peer mapping" - Reported by: Mehmet Ince

CVE-2026-44477 Affected Github.com/cloudnative pg/cloudnative pg Versions

2 component(s)
Fixed Versions:
1.28.31.29.1
Product / Package Ecosystem Affected Versions Fixed Version
github.com/cloudnative-pg/cloudnative-pg
Go
< 1.28.3
1.28.3
github.com/cloudnative-pg/cloudnative-pg
Go
≥ 1.29.0
< 1.29.1
1.29.1

References

Frequently Asked Questions

What is CVE-2026-44477?
CVE-2026-44477 is a security vulnerability in Github.com/cloudnative pg/cloudnative pg involving CloudNativePG's metrics exporter allows privilege escalation to PostgreSQL superuser and OS RCE under CWE-271: Privilege Dropping / Lowering Errors.
Which Github.com/cloudnative pg/cloudnative pg versions are affected?
Affected versions include releases prior to 1.28.3, 1.29.1 across affected packages: github.com/cloudnative-pg/cloudnative-pg.
What version fixes CVE-2026-44477?
Upgrading to 1.28.3, 1.29.1 (or the latest patched release for your respective branch) resolves the vulnerability.
Is github.com/cloudnative-pg/cloudnative-pg affected?
Yes, github.com/cloudnative-pg/cloudnative-pg is affected in releases prior to 1.28.3, 1.29.1. Upgrade your dependency to 1.28.3, 1.29.1 or later.
What is the CVSS score of CVE-2026-44477?
CVE-2026-44477 has a CVSS v3.1 base score of 9.9 (CRITICAL) with vector string CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H.
What CWE is associated with CVE-2026-44477?
CVE-2026-44477 is classified under CWE-271: Privilege Dropping / Lowering Errors (The product does not drop privileges before passing control of a resource to an actor that does not have those privileges.).
How can I check Go dependencies for CVE-2026-44477?
Run the dependency tree command: 'inspect dependencies for github.com/cloudnative-pg/cloudnative-pg'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-423p-g724-fr39",
  "modified" : "2026-06-09T02:00:27Z",
  "published" : "2026-05-11T15:59:12Z",
  "aliases" : [ "CVE-2026-44477" ],
  "summary" : "CloudNativePG's metrics exporter allows privilege escalation to PostgreSQL superuser and OS RCE",
  "details" : "### Impact\n\nThe 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.\n\nAny 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.\n\nTwo exploitation paths follow from this root cause.\n\n#### Path 1: custom metric queries with unqualified identifiers (all supported releases)\n\nA 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.\n\nAlthough `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.\n\n#### Path 2: stock `default-monitoring.yaml` (all supported releases, no custom metrics required)\n\nThe `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.\n\n#### Combined impact\n\nThe 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.\n\n#### Who is impacted\n\n- All deployments on any supported release with default monitoring enabled are affected by Path 2.\n- All deployments on any supported release that use custom metric queries containing unqualified catalog references are affected by Path 1.\n- Multi-tenant platforms that allow customers to supply or influence custom metric query bodies are at the highest risk for Path 1.\n\n### Patches\n\nThree separate patches address the vulnerability.\n\n#### Patch 1: PR #10576 \"schema-qualify catalog references in default monitoring queries and documentation samples\"\n\nSchema-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.\n\nBackported to all currently supported releases:\n\n- **v1.29.x** (x ≥ 1)\n- **v1.28.x** (x ≥ 3)\n\n#### Patch 2: \"dedicated `cnpg_metrics_exporter` role with `pg_ident.conf` peer mapping\"\n\nIntroduces 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.\n\nDemoting 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.\n\nWith 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.\n\nThis 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`.\n\nThe 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.\n\nFor 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).\n\nThe patch will be backported to all currently supported releases:\n\n- **v1.29.x** (x ≥ 1)\n- **v1.28.x** (x ≥ 3)\n\n### Workarounds\n\nIf upgrading immediately is not possible:\n\n1. **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.\n\n2. **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`.\n\n    *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.\n\n3. **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.\n\n4. **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.\n\n### References\n\n- Fix (Patch 1): PR #10576 \"schema-qualify catalog references in default monitoring queries and documentation samples\"\n- Fix (Patch 2): \"dedicated `cnpg_metrics_exporter` role with `pg_ident.conf` peer mapping\"\n- Reported by: Mehmet Ince",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H"
  }, {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/cloudnative-pg/cloudnative-pg"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "1.28.3"
      } ]
    } ]
  }, {
    "package" : {
      "ecosystem" : "Go",
      "name" : "github.com/cloudnative-pg/cloudnative-pg"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "1.29.0"
      }, {
        "fixed" : "1.29.1"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/cloudnative-pg/cloudnative-pg/security/advisories/GHSA-423p-g724-fr39"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-44477"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/cloudnative-pg/cloudnative-pg/pull/10576"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/cloudnative-pg/cloudnative-pg"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/cloudnative-pg/cloudnative-pg/releases/tag/v1.28.3"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/cloudnative-pg/cloudnative-pg/releases/tag/v1.29.1"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-250", "CWE-271", "CWE-426" ],
    "severity" : "CRITICAL",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-11T15:59:12Z",
    "nvd_published_at" : "2026-05-28T17:16:30Z"
  }
}