PostgreSQL shipped security updates for every supported branch on November 12, 2026: 18.7, 17.12, 16.16, 15.20 and 14.25, plus the second release candidate of PostgreSQL 19. The announcement counts 52 security vulnerabilities and more than 140 bug fixes, far above the project’s usual quarterly volume. Thirty-two of the 52 CVEs carry a CVSS base score of 8.8, and most share one profile: reachable over the network, a low-privileged database login, no user interaction, and full impact on confidentiality, integrity and availability.
The centerpiece is CVE-2026-6460, a type confusion in composite-type handling. The underlying primitive entered the codebase on May 5, 2004, and it survived there for 22 years.
I think the CVE count matters less than the shape of the fix. The composite-type patch changes how the server validates values at runtime, and it alters the binary interface between PostgreSQL and compiled C-language extensions. Server and extensions now have to move together. The same release closes code execution flaws in client tools, fixes a pre-authentication denial of service in PostgreSQL 18, requires reindexing, tells you to distrust existing incremental backups, and ends support for PostgreSQL 14. A minor release is no longer a quiet package update, and this one needs a coordinated maintenance window.
Key takeaways for database administrators
- Update the server to 18.7, 17.12, 16.16, 15.20 or 14.25, and rebuild or update every C-language extension in the same step.
- Restart PostgreSQL explicitly if it runs under Patroni or any unit other than the package’s own service.
- Update pg_dump, pg_restore, psql, reindexdb and vacuumdb on every machine that runs them, including bastions and CI runners.
- Reindex the affected GIN, pg_trgm GiST, TOAST and bpchar B-tree indexes.
- Take a new full backup if you use incremental backups, and anchor future incrementals to it.
- Leave
composite_type_mismatchat its secure default. - PostgreSQL 14 is end-of-life. Version 14.25 is its last release.
How CVE-2026-6460 works: a value that outlived its type
To see how a flaw this severe hid for two decades, start with how PostgreSQL represents values internally. Every value that moves through the executor, the index access methods and the type input/output functions travels as a Datum, a generic pointer-sized machine word (eight bytes on 64-bit builds). For pass-by-value types such as int4, the Datum holds the value itself. For pass-by-reference types such as text, bytea, uuid or composite rows, it holds a pointer to a memory buffer.
A raw Datum carries no inline tag saying what it is, as security researcher Mehmet Ince lays out step by step in his technical writeup. Its meaning lives in external catalog metadata: an OID pointing into pg_type and, for row-shaped values, a tuple descriptor naming each field and its type. An output function never inspects the bytes to discover their type. It trusts the surrounding metadata completely. The project’s own advisory describes the result as “inadequate tracking of typed values, and values were not self-identifying,” which let stale values linger in data dictionary entries, PL/pgSQL variables and many other corners of the server.
The gap opens when the metadata changes while old values are still alive. On May 5, 2004, commit 077db40fa1f3 let PostgreSQL change a column’s type in place with ALTER TABLE ... ALTER COLUMN ... TYPE, and that capability extends to the attributes of user-defined composite types. When you run PREPARE, PostgreSQL parses, analyzes and plans the statement, and constants (composite literals included) are materialized into the cached plan according to the type’s definition at that moment. A later ALTER TYPE ... ALTER ATTRIBUTE ... TYPE updates the catalog but does not rewrite the Datums already built against the old layout. At EXECUTE time, the composite output function record_out() consults the current descriptor, sees the new attribute type, and hands the old bytes to the new type’s output function.
Ince’s crash proof of concept is only a handful of statements, demonstrated on PostgreSQL 18.6. A uuid attribute is changed to bytea between PREPARE and EXECUTE. The old sixteen bytes are then interpreted by byteaout(), and bytea is a variable-length (varlena) type whose leading header bytes describe the storage format. Crafted header bytes mark the value as an external expanded object, so the following eight bytes are treated as a native pointer to an ExpandedObjectHeader. In the crash payload that pointer is zero. The call path runs from EXECUTE through record_out(), byteaout() and the detoasting layer into DatumGetEOHP(), which returns NULL, and then into EOH_get_flat_size(NULL), which dereferences it. The backend dies with signal 11. PostgreSQL treats any backend segfault as possible shared-memory corruption, so the postmaster resets every other session on the instance.
Sixteen attacker-chosen bytes turn out to be enough to forge a varlena header and a complete 64-bit pointer.
Who can trigger it: the privilege question
The public sources do not frame the required privileges identically, so precision matters here. The project’s advisory describes the attacker as an object creator. It says ALTER ... SET DATA TYPE is the primary route but other DDL paths exist, and it gives ALTER TABLE ... ADD COLUMN; ROLLBACK; ALTER TABLE ... ADD COLUMN as an example. Red Hat rates the flaw Important and describes an authenticated user with permission to create or alter database objects. Ince states that he could trigger the crash as a lowest-privileged login role, using a composite type created in his session’s pg_temp schema. Temporary object creation is available to login roles by default.
What none of the public sources confirms is whether an account holding only default privileges can go from the crash all the way to code execution. The privilege floor of the full chain is not documented. The project has not reported exploitation in the wild, and the CISA assessment in the CVE record lists exploitation as none. That is a reason to patch calmly, not to patch slowly.
📬 Stay Ahead of Cyber Threats
Get the latest cybersecurity news, critical vulnerabilities, threat intelligence, tutorials, and exclusive giveaways delivered straight to your inbox. No spam. Unsubscribe anytime.
From segfault to code execution
A deterministic crash is only the start of an exploit. Ince calls this one of the most reliable memory-corruption primitives he has encountered, because the corrupted pointer lands directly on a trusted indirect-call interface. PostgreSQL’s expanded-object subsystem, which exists to make in-memory work on large arrays and records efficient, invokes operations through a method table embedded in every ExpandedObjectHeader. Those callbacks were never meant to be attacker-reachable. Output functions assume that the header, the pointer and the method table were all created by PostgreSQL itself, and the type confusion breaks that assumption.
By his account, Ince used forged objects in heap memory to turn a routine value-formatting call into a call to system() with an attacker-chosen string. The command runs inside the backend with the privileges of the server’s operating system user, conventionally postgres. Address-space layout randomization was the remaining obstacle, and he defeated it with the same primitive instead of a second bug. PostgreSQL arrays embed their element type in the binary array representation, and unnest() trusts that embedded type. If an array claims text elements while the surrounding expression expects a bigint, the pointer bits of the text value come back to the client as an integer. One root cause supplies both the address leak and the hijack.
Ince dates his exploitation technique to a second commit, 1dc5ebc9077a, merged May 14, 2015, which introduced expanded-object pointers and their native method callbacks. The 2004 change supplied the confusion and the 2015 machinery supplied the control-flow target. He published the crash SQL and the address leak, withheld the final step that invokes a command, and posted video of a full working exploit on PostgreSQL 18.6.
The research did not stop at one variant. He reported 21 cases in total, finding the same underlying mistake around RowExpr evaluation, JSON conversion caches and other places where old values outlive their descriptors. Six of the release’s 52 CVEs trace directly to this line of research:
- CVE-2026-6460, the umbrella issue
- CVE-2026-116306, a composite-type change during field assignment in
ExecEvalFieldStore[De]Form() - CVE-2026-116329, statistics import publishing values under an obsolete type
- CVE-2026-120148, extended statistics on transient pseudo-types such as anonymous records
- CVE-2026-120142, dropped-column mismatches when comparing row types
- CVE-2026-120139, integer wraparound in tuple-size calculations
Managed PostgreSQL services are affected too
Ince ported his working exploit to essentially every managed PostgreSQL platform he knew and shared reproducible proofs of concept privately with their security teams through August 2026. AWS (Aurora and RDS) and Microsoft Azure Database for PostgreSQL received reports and working PoCs in mid-August. Supabase got its report around the same time, and Ubicloud and the ClickHouse team followed by the end of the month. Databricks validated the exploits immediately, and Google escalated the AlloyDB report to its highest priority, P0.
His summary is blunt: he saw no unaffected vendor, Postgres-compatible offerings included. Outsourcing the infrastructure does not outsource the risk. His writeup does not state each service’s current patch status, so check the deployed minor version on your platform instead of assuming the coordination timeline means remediation.
An AI-heavy credit list: who found the bugs
CVE-2026-6460 is explicitly an umbrella issue, and its credit list reads like a census of how security research works in 2026. The release notes thank Andrew Gierth, Alexander Lakhin, Peter Geoghegan, Nikhil Sontakke and Ince, among others, alongside the OpenAI Security Research Team, the Anthropic OSS program, Claude and Ada Logics. The neighboring ExecEvalFieldStore fix credits OpenAI Codex Security. A long tail of use-after-free and buffer-overflow fixes across PL/Perl, PL/Tcl, dblink, tsvector_delete() and ts_rank_cd() credits the Anthropic OSS program and Claude repeatedly. The core patch series was authored by Heikki Linnakangas, Andres Freund, Nathan Bossart and Nikhil Sontakke.
My read is that continuous AI-assisted auditing of a thirty-year-old C codebase is now finding real, exploitable memory-safety bugs at industrial scale.
A counterpoint landed one day before the release. PostgreSQL consultant Christophe Pettus published “Stop Using AI to Report PostgreSQL Bugs,” describing bursts of ten to fifteen machine-generated reports of highly variable quality arriving on the project’s bug mailing list from single addresses. Both things are true at once. The tooling behind several of this release’s most serious findings is also eating volunteer triage time with noise. For operators the takeaway is practical: the discovery rate of latent bugs in PostgreSQL’s C core and extension ecosystem is unlikely to slow down, so plan patch cadence around more batches like this one.
Why the fix forces you to rebuild C extensions
The 18.7 release notes are unusually candid about the history. Operations such as ALTER TABLE ... ALTER COLUMN TYPE are described as “hazardous because tuples of the older row layout may still exist in the system.” PostgreSQL “long had some checks to defend against that,” the notes say, but they were never complete and they entirely failed to detect cases where composite types were changed concurrently with queries using them.
The remedy is a large set of patches that add versioning logic for composite datums, so code that deforms a tuple can detect when the datum no longer matches the tuple descriptor it is about to be read with. The same series adds locking to block ALTER TYPE on a composite type used by a CREATE TABLE that has not committed yet. It also tightens the dependency rules for ALTER COLUMN TYPE, which is no longer allowed when a function’s default expression or a new-style SQL function body depends on the type.
A new server parameter, composite_type_mismatch, can disable the checks for legacy workloads that break under strict enforcement. The notes warn in the same breath that doing so carries security risks. Switching it off to avoid rebuilding extensions would defeat the whole patch.
The engineering fact with the biggest consequences is that the fix crosses the extension ABI boundary. PGDG RPM maintainer Devrim Gündüz wrote the clearest public packaging analysis. He notes that the fix (commit 245eb832f) changes how composite values are stored and modifies the server’s interface to extensions in two specific ways. DatumGetHeapTupleHeader(), previously a C preprocessor macro, is now a real function exported by the server, and heap_getattr() now calls a new heap_datum_check() routine.
Server headers are compiled into extension shared objects, so the incompatibility runs in both directions:
- An old extension binary still loads and works on a patched server, because every symbol it expects is present. Its compiled code holds the old inlined macro expansion instead of the new validation path, so in Gündüz’s words it “may not be protected from the vulnerability.”
- A rebuilt extension references
heap_datum_check()and the functionalDatumGetHeapTupleHeader(), and fails to load on an unpatched server with an undefined-symbol error from the dynamic linker.
The release notes state the requirement flatly: “For full protection, this change requires rebuilding C-language extensions, and there are some extensions that will require source-code changes.”
Packagers have made the coupling enforceable. Gündüz’s team rebuilt every extension in the PGDG RPM repositories against the new minors, across RHEL, Rocky, AlmaLinux, Fedora, Amazon Linux, SLES and openSUSE Leap. Extension packages used to require only the PostgreSQL major version. A rebuilt extension for PostgreSQL 18 now requires postgresql18-libs >= 18.7, so dnf cannot install a new extension beside an old server. Debian-side packagers applied the same protection. Community engineer Jeremy Schneider verified that with postgresql-18 held at 18.6, installing postgresql-18-cron fails because the extension depends on postgresql-18 (>> 18.7), and it succeeds only when the server upgrades in the same transaction.
Schneider’s broader point deserves some thought. Extensions are packaged and distributed separately, so anyone who depends on them cannot fully receive this release’s CVE fixes until every extension they run has been rebuilt. Server and ecosystem now share a single upgrade clock.
Gündüz also flags a trap for high-availability deployments. If PostgreSQL runs outside its package’s own service unit, for example under Patroni or a custom systemd unit, updating packages on disk does not restart the daemon. Restart it explicitly so the new server binary and the rebuilt extension libraries load together.
Client-tool flaws in pg_restore, pg_dump, reindexdb and vacuumdb
The composite-type family dominates the release, but several other fixes cross trust boundaries that administrators routinely forget.
CVE-2026-116314 is improper input validation in pg_restore’s handling of non-plain archives. A crafted custom or directory archive can execute arbitrary code as the operating system user running pg_restore, and the patch addresses out-of-bounds accesses and file path traversal hazards. The advisory draws a useful line: restoring a dump legitimately executes arbitrary SQL at the destination database, but it must never give code execution inside the pg_restore process on the operator’s own machine.
CVE-2026-116330 attacks the other end of the pipeline. When pg_dump produces a tar-format archive, it includes an undocumented restore.sql script guarded by \restrict and \unrestrict meta-commands, the protection introduced with the fix for CVE-2025-8714. The restriction key in that script was predictable. A compromised origin server could craft object definitions that escape the restriction block and inject psql meta-commands, running code as the client OS account when an administrator executes the script. Noah Misch’s fix makes the restrict commands actually functional in tar output.
Both flaws are rated 8.8 and need no privileges. They only require that a human run the tool against hostile input, which is exactly what incident responders and migration engineers do with untrusted databases.
The client-side cleanup continues with SQL-injection fixes in pg_dump’s NOT NULL constraint quoting (CVE-2026-116318) and reindexdb’s index-name quoting (CVE-2026-116325). In vacuumdb, fully qualified operator names now stop a database owner from executing code as the vacuumdb user (CVE-2026-116313).
CVE-2026-116322: the pre-authentication denial of service in PostgreSQL 18
CVE-2026-116322 is the only flaw in the batch that needs neither authentication nor user interaction. It affects PostgreSQL 18 only, in minors before 18.7. Earlier major versions are unaffected.
An unauthenticated client can send a cancel request packet before login and, through timing, arrange for authentication_timeout to fire while the process holds a spinlock during cancel handling. The process exits holding the lock, and the database crashes and restarts. The fix delays processing of cancel packets until it is safe to handle them. The project rates it 5.3 because the impact is availability only, with no code execution. For an exposed PostgreSQL 18 instance it is still a remotely triggerable, pre-authentication denial of service. The Anthropic OSS program reported it.
Read together, the release has a clear shape: one unauthenticated DoS, a large cluster of authenticated memory-corruption paths to OS-level code execution, and a set of client-tool flaws that hit anyone who processes untrusted archives or dumps from compromised servers. Updating the server alone leaves jump hosts, CI runners and backup jobs exposed.
Reindexing and backup chains after the update
Two post-update requirements in the release notes carry their own data-integrity risk, and the CVE headlines bury them.
Which indexes to rebuild
Several fixed bugs could have silently corrupted indexes:
- A B-tree deduplication problem for columns declared as bpchar without a length. Trailing-space-insensitive equality violates the bitwise-equality assumption deduplication relies on, so index-only scans can return subtly wrong results. Columns declared char(n) are not affected, because the length constraint forces equal strings to be bitwise equal.
- GIN corruption from VACUUM missing entries across multiple index scans, and from page-deletion bugs after incomplete splits, leaving entries that point at unrelated table rows.
- Possible corruption in pg_trgm GiST indexes using
gist_trgm_ops. - Missed updates to TOAST-table indexes after
REINDEX CONCURRENTLY, which surface later as “missing chunk number” errors.
The project’s guidance is to read the changelog entries that mention REINDEX and rebuild anything potentially affected: REINDEX INDEX for suspect GIN indexes, a rebuild of every pg_trgm GiST index, REINDEX TABLE where TOAST corruption is suspected, and reindexing of B-trees on unconstrained bpchar columns. The deduplication patch stops new corruption and leaves existing entries alone, so the rebuild is the repair.
Why existing incremental backups are no longer trustworthy
The release notes say existing incremental backup files “should not be trusted,” because several of the fixed bugs could produce corrupt incremental backups. Users of the feature, introduced in PostgreSQL 17, should take a new full backup after updating and anchor all later incrementals to it. If your recovery strategy rests on incremental chains, this is a chain reset, and it belongs inside the maintenance window.
A related fix matters if you dump from replicas. Snapshot export and import mishandled snapshots taken during recovery, and the bug was observed with parallel pg_dump against a standby, where it could corrupt both the dump output and the standby’s own data through incorrectly set hint bits. If that matches your backup topology, treat recent standby-sourced dumps with suspicion and regenerate them on the patched build.
PostgreSQL patch checklist: what to do, in order
Applying the update is still the usual minor-release routine. The releases are cumulative, and no dump/restore or pg_upgrade is required. The sequence around it is what changed.
- Update the server to 18.7, 17.12, 16.16, 15.20 or 14.25, matching your major branch.
- Update every compiled C-language extension in the same package transaction. Tightened repository dependencies enforce this on PGDG RPM and Debian-based systems. Source-built and vendor-bundled extensions are your responsibility, and some need source changes before they compile against the new headers.
- If PostgreSQL runs under Patroni or any unit other than the package’s own service, restart it right after the update.
- Update pg_dump, pg_restore, psql, reindexdb and vacuumdb on every machine that runs them, bastions and CI runners included.
- Perform the reindexing described above.
- Take a fresh full backup if you use incremental backups.
- Leave
composite_type_mismatchat its secure default.
Mitigations if you cannot patch yet
The advisory and release notes offer no server-side workaround, which leaves exposure reduction. Red Hat’s guidance is to restrict network reachability to trusted administrative hosts and to revoke object-creation privileges from untrusted accounts, specifically REVOKE CREATE ON SCHEMA public FROM PUBLIC.
That shrinks the DDL surface, but it has limits. Ince’s crash ran through a temporary type in pg_temp, and temporary-object creation is available to login roles by default. A complete stopgap would also revoke TEMPORARY on the database from untrusted roles, which breaks applications that legitimately use temporary tables. Treat these as holding measures. None of them replaces the patch, and none protects against the client-tool flaws.
PostgreSQL 14 end of life
Version 14.25 is the last release PostgreSQL 14 will ever get. The branch is end-of-life, and every vulnerability discovered after November 12 stays open on it permanently. Branches older than 14 were already unsupported and received no fix for these flaws either.
PostgreSQL 15 reaches end-of-life in November 2027, and general availability of PostgreSQL 19 is planned for November 19, 2026, a week after this batch. If you are still on 14, schedule the migration as a project with a supported target that gives you the longest runway.
My assessment: two events under one version number
I read this release as two events wearing one version number. The first is a conventional, if unusually large, security update: client-side code execution paths closed, a pre-authentication restart vector eliminated, and a long tail of memory-safety and privilege bugs fixed across the server and its procedural languages.
The second is a structural correction to an invariant PostgreSQL has carried since 2004: the assumption that a composite value’s bytes and its type descriptor can never disagree. The fix enforces it through datum versioning at the exact boundary where the old assumption failed. That the invariant survived 22 years says more about how subtle it was than about the project’s security process. It spanned the parser, planner, executor, catalog caches and out-of-tree C code. It took a researcher asking a naive question about prepared statements, plus a fleet of AI auditors approaching the same code from other angles, to surface the whole family at once.
The price of the correction is that PostgreSQL’s extension ecosystem, the “slow core, fast edge” architecture much of the database’s adoption rests on, now has a hard synchronization point. Extensions and server must patch together, some extension authors must change source code, and packagers had to encode that coupling into dependency metadata to protect users from their own upgrade habits. When a minor release touches value representation or the extension ABI, the binary swap is the smallest part of the work. The rebuild, the restart, the reindex and the backup reset are the security fix.
Frequently asked questions
What is CVE-2026-6460 in PostgreSQL?
CVE-2026-6460 is an umbrella type confusion vulnerability in PostgreSQL’s composite-type handling. When a composite type’s attribute is altered while old values built against the previous layout are still alive, the server can pass those stale bytes to the wrong output function. The underlying primitive dates to May 5, 2004. It is fixed in 18.7, 17.12, 16.16, 15.20 and 14.25.
Which PostgreSQL versions fix the 52 vulnerabilities?
PostgreSQL 18.7, 17.12, 16.16, 15.20 and 14.25, released on November 12, 2026. PostgreSQL 14.25 is the final release of that branch, and branches older than 14 were already unsupported.
Do I need to rebuild PostgreSQL extensions after this update?
Yes, for full protection. The fix changes the server’s interface to C-language extensions. Old extension binaries still load on a patched server but may not be protected. Rebuilt extensions fail to load on an unpatched server. PGDG RPM and Debian packages enforce matching versions through dependency metadata.
Is CVE-2026-6460 being exploited in the wild?
The project has not reported in-the-wild exploitation, and the CISA assessment in the CVE record lists exploitation as none. A researcher has demonstrated a working exploit on PostgreSQL 18.6 and published the crash and address-leak steps, so patch promptly.
Do I have to run pg_upgrade or a dump and restore?
No. The releases are cumulative, and a minor update needs no dump/restore or pg_upgrade. You do need to rebuild extensions, restart explicitly if you run under Patroni or a custom unit, reindex the affected indexes, and take a new full backup if you use incremental backups.
What happens to PostgreSQL 14?
Version 14.25 is the last release. PostgreSQL 14 is end-of-life and will receive no further security or bug fixes.









