pgvector 0.8.7 fixes CVE-2026-103484: a database user who can create an IVFFlat index can write out of bounds in the backend, which can lead to arbitrary code execution. Every version through 0.8.6 is affected. Upgrade.

That part is simple. Two other parts are not: who can actually reach the bug, and what “upgraded” means for a backend that has been running since last Tuesday.

What broke

An IVFFlat build samples the table, runs k-means over the sample to pick the list centers, then assigns every row to its nearest center. The index’s dimension count comes from the column’s type modifier (the 1536 in vector(1536)), and the build sizes its working arrays to match. Before the fix, the k-means step that sums sample vectors into those arrays took its loop bound from each vector’s own header instead. If a vector disagreed with the type modifier, the loop kept writing after the array ended.

The fix passes the index’s dimension count down, checks every vector against it, and adds the same check everywhere IVFFlat and HNSW accept a vector: build, insert, and scan. (The advisory doesn’t say how an attacker gets a mismatched vector past the type modifier, and I’m not going to speculate in public four days after the fix.)

If you write extensions, the lesson generalizes. A type modifier is a declaration about a column. It is not a property of the bytes your access method is handed, and C code that sizes a buffer from one and loops over the other is one bad value away from an advisory of its own.

Third time this year

This is the third memory-safety CVE in pgvector’s index build code in 2026:

  • CVE-2026-3172, fixed in 0.8.2 in February. Integer wraparound in parallel HNSW builds, reachable by anyone who could create or reindex an HNSW index with parallel workers. Data leak or crash.
  • CVE-2026-18022, fixed in 0.8.6 in July. Integer wraparound in IVFFlat builds on 32-bit systems. Out-of-bounds write.
  • CVE-2026-103484, fixed in 0.8.7. The one above, with no 32-bit qualifier.

Three different mechanisms, one neighborhood. Build code runs once per index rather than once per query, so it gets a small fraction of the exercise the scan path gets, and it does a great deal of arithmetic on sizes derived from metadata. If I were pointing a fuzzer at a vector extension, that is where I would point it. Credit for this one goes to four researchers at Compass Security.

The privilege nobody thinks of as a privilege

“A database user with the ability to create an IVFFlat index” sounds like a short list. It isn’t. CREATE INDEX requires owning the table, and in a depressing number of applications the role that owns the tables is the role the application connects as, because that’s the role the migration framework runs as. So the list is “your application, and anyone who can get it to run SQL.”

REINDEX belongs on the list too. It rebuilds through the same build code, and since PostgreSQL 17 it can be granted to roles that own nothing, through the MAINTAIN privilege or the pg_maintain predefined role. The February advisory named reindexing explicitly. Go find out who has those.

The durable fix is dull: a migration role that owns the schema, and an application role with SELECT, INSERT, UPDATE, and DELETE and nothing else. It’s tedious to retrofit. It is also the difference between this CVE applying to your application role and not.

Upgrading, properly

Installing the package is not the end of it. The fix lives in the vector shared library, and a backend that loaded the old library keeps running the old code until it exits. Pooled connections can live for days.

So after the new package is in place, recycle every backend that started before it landed:

1SELECT pid, usename, application_name, backend_start
2 FROM pg_stat_activity
3 WHERE backend_type = 'client backend'
4 AND backend_start < '2026-10-05 09:00:00-07'; -- when the package landed

pg_terminate_backend() is there if you’re feeling decisive. If you run PgBouncer, RECONNECT on the admin console closes each server connection as it’s released; otherwise server_lifetime (3600 seconds by default) will get there on its own schedule. If you put vector in shared_preload_libraries (it doesn’t need to be there), restart the server. And don’t treat pg_extension.extversion as proof of anything: it reports which SQL script ran, not which library a given backend has mapped.

If you build from source or use PGDG packages

Confirm you actually have 0.8.7. The changelog at the v0.8.7 tag dates the release 1 October; the changelog on master still lists 0.8.7 as unreleased and doesn’t mention the overflow. pgvector publishes git tags, not GitHub releases, so if your alerting watches the Releases page, it saw nothing. Read the tag.

If you’re on a managed service

You are running whatever your provider ships, whenever they ship it. Ask them which version you’re on and when 0.8.7 lands, and until it does, take a hard look at which roles can create indexes.