#PostgreSQL#Kubernetes#CloudNativePG#security#monitoring

CloudNativePG CVE-2026-44477: Why a Metrics Exporter Running as Superuser Is a Privilege Escalation Path

webhani·

The CloudNativePG project announced versions 1.29.1 and 1.28.3 to fix CVE-2026-44477, the first CVE assigned against the operator, rated critical (CVSS v4 9.4 per the project's announcement; other databases list different scores). The affected range is every version before 1.28.3, and 1.29.0. If you run PostgreSQL on Kubernetes through this operator, upgrading is the action item. This post covers the more durable lesson: why the design pattern behind the bug is common, and how to check for it in your own monitoring.

We are working from the project's announcement and advisory databases. Some exploitation details circulating in third-party write-ups are secondhand, and we flag them as such below.

What went wrong

Per the advisories, the operator's metrics exporter opens its PostgreSQL connection as the postgres superuser over the pod-local Unix socket, then immediately demotes the session with SET ROLE pg_monitor so the monitoring queries run with limited privileges.

That looks reasonable, and it's a common shortcut: the exporter needs a connection that always works, including during bootstrap, so it uses the most privileged identity available and drops down. The weakness is that SET ROLE is a reversible demotion. The session's authenticated user is still the superuser; the active role is merely changed. Anything that can execute SQL inside that session can, in principle, switch back.

The attack surface here is that monitoring queries are not only the built-in ones. Operators can define custom queries, and database objects such as functions or views in the schemas those queries touch are controlled by whoever created them. Third-party analyses describe two paths: a low-privileged database role causing SQL to run during a scrape that resets the role, and schema shadowing, where an unqualified identifier in a monitoring query resolves to an attacker-controlled object. From superuser, COPY ... TO PROGRAM reaches operating system commands in the Postgres container. Treat these mechanics as plausible rather than confirmed; the fix itself is consistent with them, since the release reportedly schema-qualifies built-in monitoring queries with pg_catalog.

The structural lesson is independent of the details: a connection that authenticates as a superuser and relies on SET ROLE for containment is only as safe as every query that will ever run on it.

Checking and upgrading

First find out what you run. For an operator installed through Helm:

kubectl get deployment -n cnpg-system cnpg-controller-manager \
  -o jsonpath='{.spec.template.spec.containers[0].image}'

Then check each cluster's instance manager version, since the exporter runs inside the instance manager in the pod, not in the operator Deployment:

kubectl get clusters.postgresql.cnpg.io -A \
  -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,IMG:.status.image
kubectl cnpg status <cluster-name> -n <namespace>

Upgrade the operator to 1.28.3 or 1.29.1 or later. Instance managers are updated as pods roll, which means the fix reaches running clusters through an in-place or rolling update depending on your configuration. Plan for that restart window; for primaries, do it through the supported switchover path rather than deleting pods by hand. Confirm afterwards that every pod reports the patched instance manager.

If you cannot upgrade immediately, reduce exposure while you wait:

  • Remove or review custom monitoring queries (the monitoring config maps and secrets) so nothing user-influenced runs on the exporter connection.
  • Restrict who can create objects, especially functions and operators, in schemas the monitoring queries read. Revoking CREATE on public from application roles is a sound default regardless:
REVOKE CREATE ON SCHEMA public FROM PUBLIC;
  • Tighten NetworkPolicy so only your Prometheus scrape path can reach the metrics port.

Auditing your own monitoring setup

The CVE is specific to one operator, but the pattern shows up everywhere. A short checklist for any exporter or agent you run against a database:

  1. What identity does it authenticate as? Prefer a dedicated login role with pg_monitor membership from the start, not a superuser that demotes itself.
  2. Is containment reversible? SET ROLE and SET SESSION AUTHORIZATION can generally be undone by a session that began with higher privilege. A real boundary is a separate login.
  3. Are all queries schema-qualified? Unqualified names resolve through search_path, which an attacker can influence in some configurations. Qualify with pg_catalog. or pin search_path for the role.
  4. Who can add queries? Custom metric definitions are code that runs with the exporter's authority. Review them like code, not like configuration.
  5. Can the database host reach out? COPY ... TO PROGRAM is the usual stepping stone from SQL to a shell. If no application needs it, only superusers can use it by default, so the real question is keeping superuser sessions unreachable from untrusted SQL.

Several of these map to older principles: least privilege, separation of identities, and not letting data cross into the control path. They are easy to forget in glue components like exporters because nobody thinks of them as part of the security perimeter.

What we recommend

Upgrade CloudNativePG now and verify instance manager versions per cluster, not just the operator. Then spend an hour on the checklist above for the other monitoring components in your estate; Postgres exporters, backup agents, and migration tools often share the same shortcut. A critical CVE in a well-maintained project is a useful prompt to look at the components nobody has reviewed. If you'd like a second pair of eyes on a Kubernetes-hosted PostgreSQL setup, webhani can help with a focused review.