PostgreSQL's 52-CVE update is an upgrade project, not a patch. And PostgreSQL 14 is done

PostgreSQL's 52-CVE update is an upgrade project, not a patch. And PostgreSQL 14 is done

On November 12 the PostgreSQL project put out minor releases 18.7, 17.12, 16.16, 15.20 and 14.25, plus a second release candidate for PostgreSQL 19. Between them they close 52 CVEs and fix more than 140 bugs. For a point release, that's a big number, and this one wants more from you than the usual "bump the package, restart, go home."

Two more things make this round different. PostgreSQL 14.25 is the last release of the 14 branch, so 14 is now end-of-life and gets no further security or bug fixes. And the fix for the headline bug, CVE-2026-6460, changes how the server talks to extensions. So a routine patch turns into a coordinated server-plus-extensions upgrade, followed by some reindexing and a backup reset.

What CVE-2026-6460 is

It's a type confusion that can be triggered after ALTER ... SET DATA TYPE. A user who is allowed to create objects can use it to run arbitrary code as the operating system account the database runs under. The official advisory scores it 8.8 under CVSS 3.1: reachable over the network, low complexity, low privileges, no user interaction. Every version before 18.7, 17.12, 16.16, 15.20 and 14.25 is affected.

"Needs object-creation privileges" sounds like a comfortable precondition. I don't think it is. In our pentesting engagements, the most common setup we find is an application role that owns its own schema. Migrations run as that role, the app connects as that role, and so whatever account a SQL injection lands in can usually create tables. That's the attacker profile this CVE needs.

The blast radius is the part that bothers me. On a self-hosted Postgres, the OS user running the server can read the whole data directory, which means every database on the cluster, not just the one the compromised app uses. In Docker setups it often also sees whatever secrets got mounted into the container.

The extension catch

The fix touches internals that extensions compile against. DatumGetHeapTupleHeader() went from a macro to a real server function, and heap_getattr() now calls heap_datum_check(). Because of that, the PGDG packagers rebuilt every extension against the new minor releases, and their advice is to update the server and its extensions together.

From our PHP and Docker work, here's where this goes wrong. Someone pins the Postgres base image to a new tag but the extension packages are installed in a separate layer with their own pinned versions, or the other way round. Upgrade both in the same change, rebuild the image, and test it before it reaches production. If you build extensions from source yourself, rebuild them against the new headers. A minor release normally doesn't ask for that.

The work after the upgrade

The release announcement lists several bug fixes that only help once you rebuild things:

  • GIN indexes you suspect are corrupted need a reindex.
  • pg_trgm GiST indexes built with gist_trgm_ops need a reindex.
  • B-tree indexes on bpchar columns declared without a length need a reindex.
  • Any table throwing "missing chunk number" errors needs REINDEX TABLE.
  • If you use incremental backups, take a new full backup and base all later incrementals on it. Several of the fixed bugs could produce corrupt incrementals.

To find the trigram indexes quickly:

SELECT i.indexrelid::regclass AS index_name
FROM pg_index i
JOIN pg_opclass o ON o.oid = ANY (i.indclass::oid[])
WHERE o.opcname = 'gist_trgm_ops';

If you only do one item on that list, make it the backup. A corrupt incremental gives no sign of trouble until the day you restore from it, and that's the worst possible time to find out. Reindexing large tables takes time and I/O, so schedule it for a quiet window. Don't just tack it onto the end of the patch run.

Update the client tools too

The server isn't the only thing that needs patching. CVE-2026-116318 is a SQL injection in pg_dump through not-null constraint names, and CVE-2026-116330 covers the tar-format restore script failing to block psql command injection. The release notice also lists CVE-2026-116314, arbitrary code execution in pg_restore (CVSS 8.8), and CVE-2026-116313, where vacuumdb --missing-stats-only lets a database owner run code as whoever is running vacuumdb (CVSS 7.2). If anyone else can create and name objects in your databases, update the client tools along with the server.

Think about where those tools actually run: backup cron containers, CI runners, the ops laptop someone uses for ad-hoc restores. When we build cloud-native apps for enterprise clients, the backup sidecar is nearly always the most neglected image in the stack, pinned to whatever client version was current when someone first wrote the cron job. It runs with broad credentials, too.

On a managed service, check what your provider has rolled out. Even if the server is handled for you, the client tools, reindexing and backup chain may still be your job.

PostgreSQL 14 is finished

14.25 is the last patch the 14 branch will get. If you're still on it, apply 14.25 now and start planning the major upgrade this quarter. Don't leave it for next year. For our Swiss enterprise clients with compliance requirements, an unsupported database version is an audit finding on its own, whether or not anyone has exploited anything.

We see 14 hang around most often behind mobile apps. In our native iOS and Android projects for healthcare and IoT clients, the apps ship every few weeks while the API database was set up once, years ago, and nobody has touched it since because it "just works." If that describes your backend, this is the push to fix it. Aim for 18, the newest stable branch. 19 is still a release candidate and belongs in a staging environment, not behind your users. We've done these migrations as part of our development work, and the database upgrade itself is rarely the hard part. Finding every service that connects to it is.

What we'd do this week

  1. Inventory every Postgres instance and its version, including the forgotten ones in staging and in CI.
  2. Upgrade server and extensions together to 18.7, 17.12, 16.16, 15.20 or 14.25.
  3. Update pg_dump, pg_restore and vacuumdb wherever they run.
  4. Take a fresh full backup if you use incrementals.
  5. Reindex the affected GIN, trigram GiST and bpchar B-tree indexes.
  6. Check which roles can create objects, and take that right away from any app role that doesn't need it.
  7. Put a date on the calendar for leaving PostgreSQL 14.

If a PostgreSQL 14 cluster that nobody wants to own sounds familiar, let's talk.

database-securityexpert-analysispostgresqltech-newsupgrade-planning