From d72ee1723ff1a7922c619ca9807fac5fd3ecea8f Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Fri, 23 Oct 2026 12:30:50 +0200 Subject: [PATCH 1/4] chore: additional path quilification Fully qualify all paths before a search_path switch. Standardise the way of doing this across all scripts. --- .../amcheck/after-create.sql | 8 ++-- .../before-create.sql | 43 ++++++++++--------- .../dblink/after-create.sql | 12 +++--- .../pgmq/after-create.sql | 15 +++---- .../pgsodium/after-create.sql | 2 +- .../pgsodium/before-create.sql | 11 +++-- .../supabase_vault/after-create.sql | 9 ++-- 7 files changed, 52 insertions(+), 48 deletions(-) diff --git a/ansible/files/postgresql_extension_custom_scripts/amcheck/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/amcheck/after-create.sql index 1fe5be3534..7f717c8560 100644 --- a/ansible/files/postgresql_extension_custom_scripts/amcheck/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/amcheck/after-create.sql @@ -1,10 +1,10 @@ -- Grant postgres EXECUTE on amcheck functions in pg_catalog. do $$ declare - saved_search_path text := (select current_setting('search_path')); - r record; + saved_search_path pg_catalog.text := (select pg_catalog.current_setting('search_path')); + r pg_catalog.record; begin - perform set_config('search_path', '', true); + perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); for r in select p.oid::regprocedure as sig @@ -16,5 +16,5 @@ begin execute format('grant execute on routine %s to postgres', r.sig); end loop; - perform set_config('search_path', saved_search_path, true); + perform pg_catalog.set_config('search_path', saved_search_path, true); end $$; diff --git a/ansible/files/postgresql_extension_custom_scripts/before-create.sql b/ansible/files/postgresql_extension_custom_scripts/before-create.sql index 14d4c823b0..d90b5bef4c 100644 --- a/ansible/files/postgresql_extension_custom_scripts/before-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/before-create.sql @@ -8,15 +8,15 @@ -- extension` statement. do $$ declare - search_path text; - _extname text; - _extschema text; - _extversion text; - _extcascade bool; - _r record; + saved_search_path pg_catalog.text := (select pg_catalog.current_setting('search_path')); + _extname pg_catalog.text; + _extschema pg_catalog.text; + _extversion pg_catalog.text; + _extcascade pg_catalog.bool; + _r pg_catalog.record; + _sql pg_catalog.text; begin - search_path := current_setting('search_path'); - perform set_config('search_path', 'pg_catalog, pg_temp', true); + perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); _extname := @extname@; _extschema := @extschema@; @@ -24,17 +24,17 @@ begin _extcascade := @extcascade@; if not _extcascade then - perform set_config('search_path', search_path, true); + perform pg_catalog.set_config('search_path', saved_search_path, true); return; end if; if not exists (select from pg_extension where extname = 'pg_tle') then - perform set_config('search_path', search_path, true); + perform pg_catalog.set_config('search_path', saved_search_path, true); return; end if; if not exists (select from pgtle.available_extensions() where name = _extname) then - perform set_config('search_path', search_path, true); + perform pg_catalog.set_config('search_path', saved_search_path, true); return; end if; @@ -91,19 +91,22 @@ begin where name in (select name from pg_available_extensions) intersect select name - from regexp_split_to_table(current_setting('supautils.privileged_extensions', true), '\s*,\s*') as t(name) + from regexp_split_to_table(pg_catalog.current_setting('supautils.privileged_extensions', true), '\s*,\s*') as t(name) ) loop - -- Create the dependency with the caller's search_path. Otherwise, when no - -- schema is specified, the dependency would be created in pg_catalog, the - -- first schema in our locked down search_path. - perform set_config('search_path', search_path, true); + -- Build the statement while search_path is still locked down, so format() + -- cannot be resolved through the caller's search_path. if _extschema is null then - execute(format('create extension if not exists %I cascade', _r.name)); + _sql := pg_catalog.format('create extension if not exists %I cascade', _r.name); else - execute(format('create extension if not exists %I schema %I cascade', _r.name, _extschema)); + _sql := pg_catalog.format('create extension if not exists %I schema %I cascade', _r.name, _extschema); end if; - perform set_config('search_path', 'pg_catalog, pg_temp', true); + -- Create the dependency with the caller's search_path. Otherwise, when no + -- schema is specified, the dependency would be created in pg_catalog, the + -- first schema in our locked down search_path. + perform pg_catalog.set_config('search_path', saved_search_path, true); + execute _sql; + perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); end loop; - perform set_config('search_path', search_path, true); + perform pg_catalog.set_config('search_path', saved_search_path, true); end $$; diff --git a/ansible/files/postgresql_extension_custom_scripts/dblink/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/dblink/after-create.sql index d4236649df..7ee2859d1a 100644 --- a/ansible/files/postgresql_extension_custom_scripts/dblink/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/dblink/after-create.sql @@ -1,10 +1,9 @@ do $$ declare - search_path text; - r record; + saved_search_path pg_catalog.text := (select pg_catalog.current_setting('search_path')); + r pg_catalog.record; begin - search_path := current_setting('search_path'); - perform set_config('search_path', 'pg_catalog, pg_temp', true); + perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); for r in (select oid, (aclexplode(proacl)).grantee from pg_proc where proname = 'dblink_connect_u') loop continue when r.grantee = 'supabase_admin'::regrole; @@ -15,6 +14,5 @@ begin ); end loop; - perform set_config('search_path', search_path, true); -end -$$; + perform pg_catalog.set_config('search_path', saved_search_path, true); +end $$; diff --git a/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql index 454b0a9209..0cd114cee3 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql @@ -1,13 +1,12 @@ do $$ declare - extoid oid; - extversion text; - search_path text; - r record; - cls pg_class%rowtype; + saved_search_path pg_catalog.text := (select pg_catalog.current_setting('search_path')); + extoid pg_catalog.oid; + extversion pg_catalog.text; + r pg_catalog.record; + cls pg_catalog.pg_class%rowtype; begin - search_path := current_setting('search_path'); - perform set_config('search_path', 'pg_catalog, pg_temp', true); + perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); select e.oid, e.extversion into extoid, extversion from pg_extension e where extname = 'pgmq'; @@ -192,5 +191,5 @@ $shim$ LANGUAGE plpgsql; end if; end loop; - perform set_config('search_path', search_path, true); + perform pg_catalog.set_config('search_path', saved_search_path, true); end $$; diff --git a/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql index 38242ab203..8c567adc7f 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql @@ -6,7 +6,7 @@ CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name t RETURNS void LANGUAGE plpgsql SECURITY DEFINER -SET search_path TO '' +SET search_path TO pg_catalog, pg_temp AS $function$ BEGIN EXECUTE format( diff --git a/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql index fb82a46a33..d85a5b0356 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql @@ -1,9 +1,14 @@ do $$ declare - _extversion text := @extversion@; - _r record; + saved_search_path pg_catalog.text := (select pg_catalog.current_setting('search_path')); + _extversion pg_catalog.text := @extversion@; + _r pg_catalog.record; begin - if _extversion is not null and _extversion != '3.1.8' then + perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); + + if _extversion is not null and _extversion OPERATOR(pg_catalog.<>) '3.1.8' then raise exception 'only pgsodium 3.1.8 is supported'; end if; + + perform pg_catalog.set_config('search_path', saved_search_path, true); end $$; diff --git a/ansible/files/postgresql_extension_custom_scripts/supabase_vault/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/supabase_vault/after-create.sql index 89f45df218..d77978dd5a 100644 --- a/ansible/files/postgresql_extension_custom_scripts/supabase_vault/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/supabase_vault/after-create.sql @@ -1,10 +1,9 @@ do $$ declare - extversion text; - search_path text; + saved_search_path pg_catalog.text := (select pg_catalog.current_setting('search_path')); + extversion pg_catalog.text; begin - search_path := current_setting('search_path'); - perform set_config('search_path', 'pg_catalog, pg_temp', true); + perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); select e.extversion into extversion from pg_extension e where e.extname = 'supabase_vault'; @@ -19,5 +18,5 @@ begin grant execute on function vault.create_secret, vault.update_secret, vault._crypto_aead_det_decrypt to service_role; end if; - perform set_config('search_path', search_path, true); + perform pg_catalog.set_config('search_path', saved_search_path, true); end $$; From 6c156b030b303fa0a40d4867a4c15936cc13705f Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Fri, 23 Oct 2026 17:03:33 +0200 Subject: [PATCH 2/4] chore: further path fixes --- .../pgmq/after-create.sql | 4 ++-- .../pgsodium/after-create.sql | 4 ++-- .../pgsodium/before-create.sql | 1 - ...1023000000_pgmq_drop_queue_search_path.sql | 19 +++++++++++++++++++ nix/tests/expected/pgmq.out | 12 ++++++++++++ nix/tests/sql/pgmq.sql | 7 +++++++ 6 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 migrations/db/migrations/20261023000000_pgmq_drop_queue_search_path.sql diff --git a/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql index 0cd114cee3..6c4417f984 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgmq/after-create.sql @@ -142,7 +142,7 @@ BEGIN RETURN TRUE; END; -$func$ LANGUAGE plpgsql; +$func$ LANGUAGE plpgsql SET search_path = pg_catalog, pg_temp; alter extension pgmq add function pgmq.drop_queue(text); @@ -152,7 +152,7 @@ RETURNS BOOLEAN AS $shim$ BEGIN RETURN pgmq.drop_queue(queue_name); END; -$shim$ LANGUAGE plpgsql; +$shim$ LANGUAGE plpgsql SET search_path = pg_catalog, pg_temp; alter extension pgmq add function pgmq.drop_queue(text, boolean); diff --git a/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql index 8c567adc7f..28f03b7211 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql @@ -1,5 +1,5 @@ -grant execute on function pgsodium.crypto_aead_det_decrypt(bytea, bytea, uuid, bytea) to service_role; -grant execute on function pgsodium.crypto_aead_det_encrypt(bytea, bytea, uuid, bytea) to service_role; +grant execute on function pgsodium.crypto_aead_det_decrypt(pg_catalog.bytea, pg_catalog.bytea, pg_catalog.uuid, pg_catalog.bytea) to service_role; +grant execute on function pgsodium.crypto_aead_det_encrypt(pg_catalog.bytea, pg_catalog.bytea, pg_catalog.uuid, pg_catalog.bytea) to service_role; grant execute on function pgsodium.crypto_aead_det_keygen to service_role; CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) diff --git a/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql index d85a5b0356..9edf333b39 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgsodium/before-create.sql @@ -2,7 +2,6 @@ do $$ declare saved_search_path pg_catalog.text := (select pg_catalog.current_setting('search_path')); _extversion pg_catalog.text := @extversion@; - _r pg_catalog.record; begin perform pg_catalog.set_config('search_path', 'pg_catalog, pg_temp', true); diff --git a/migrations/db/migrations/20261023000000_pgmq_drop_queue_search_path.sql b/migrations/db/migrations/20261023000000_pgmq_drop_queue_search_path.sql new file mode 100644 index 0000000000..0eb42739f5 --- /dev/null +++ b/migrations/db/migrations/20261023000000_pgmq_drop_queue_search_path.sql @@ -0,0 +1,19 @@ +-- migrate:up + +-- pgmq/after-create.sql replaces pgmq.drop_queue with an override (and a +-- 2-arg compat shim) that previously resolved names through the caller's +-- search_path. New installs now pin it; pin it on existing instances too. +-- Every drop_queue body shipped by pgmq (1.4.4, 1.5.1) or by the override +-- schema-qualifies its non-catalog references, so pinning is safe whichever +-- one an instance currently has. +do $$ +begin + if to_regprocedure('pgmq.drop_queue(text)') is not null then + alter function pgmq.drop_queue(text) set search_path = pg_catalog, pg_temp; + end if; + if to_regprocedure('pgmq.drop_queue(text, boolean)') is not null then + alter function pgmq.drop_queue(text, boolean) set search_path = pg_catalog, pg_temp; + end if; +end $$; + +-- migrate:down diff --git a/nix/tests/expected/pgmq.out b/nix/tests/expected/pgmq.out index 258d11e837..6625bab3ba 100644 --- a/nix/tests/expected/pgmq.out +++ b/nix/tests/expected/pgmq.out @@ -245,3 +245,15 @@ select pgmq.create('Foo'); (1 row) +-- drop_queue and its compat shim are created by pgmq/after-create.sql and must +-- pin search_path, otherwise they resolve names through the caller's +select p.oid::regprocedure::text as func, p.proconfig +from pg_proc p +where p.oid in ('pgmq.drop_queue(text)'::regprocedure, 'pgmq.drop_queue(text,boolean)'::regprocedure) +order by 1; + func | proconfig +-------------------------------+------------------------------------- + pgmq.drop_queue(text) | {"search_path=pg_catalog, pg_temp"} + pgmq.drop_queue(text,boolean) | {"search_path=pg_catalog, pg_temp"} +(2 rows) + diff --git a/nix/tests/sql/pgmq.sql b/nix/tests/sql/pgmq.sql index a1ebe7cb23..891030c8f0 100644 --- a/nix/tests/sql/pgmq.sql +++ b/nix/tests/sql/pgmq.sql @@ -133,3 +133,10 @@ where p.pronamespace = 'pgmq'::regnamespace and p.proowner != 'postgres'::regrol -- restore the 'Foo' queue dropped by the cascade above select pgmq.create('Foo'); + +-- drop_queue and its compat shim are created by pgmq/after-create.sql and must +-- pin search_path, otherwise they resolve names through the caller's +select p.oid::regprocedure::text as func, p.proconfig +from pg_proc p +where p.oid in ('pgmq.drop_queue(text)'::regprocedure, 'pgmq.drop_queue(text,boolean)'::regprocedure) +order by 1; From ff8215cc7e36487471f7f7a89fc13ecd7925316a Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Fri, 23 Oct 2026 17:22:03 +0200 Subject: [PATCH 3/4] fix: ordering on test --- nix/tests/expected/pgmq.out | 2 +- nix/tests/sql/pgmq.sql | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nix/tests/expected/pgmq.out b/nix/tests/expected/pgmq.out index 6625bab3ba..9bec413ee5 100644 --- a/nix/tests/expected/pgmq.out +++ b/nix/tests/expected/pgmq.out @@ -250,7 +250,7 @@ select pgmq.create('Foo'); select p.oid::regprocedure::text as func, p.proconfig from pg_proc p where p.oid in ('pgmq.drop_queue(text)'::regprocedure, 'pgmq.drop_queue(text,boolean)'::regprocedure) -order by 1; +order by p.pronargs; func | proconfig -------------------------------+------------------------------------- pgmq.drop_queue(text) | {"search_path=pg_catalog, pg_temp"} diff --git a/nix/tests/sql/pgmq.sql b/nix/tests/sql/pgmq.sql index 891030c8f0..61ec7e6731 100644 --- a/nix/tests/sql/pgmq.sql +++ b/nix/tests/sql/pgmq.sql @@ -139,4 +139,4 @@ select pgmq.create('Foo'); select p.oid::regprocedure::text as func, p.proconfig from pg_proc p where p.oid in ('pgmq.drop_queue(text)'::regprocedure, 'pgmq.drop_queue(text,boolean)'::regprocedure) -order by 1; +order by p.pronargs; From b71980179cb419525f32c786ae5af39c69dce6fb Mon Sep 17 00:00:00 2001 From: Etienne Stalmans Date: Mon, 26 Oct 2026 16:59:32 +0100 Subject: [PATCH 4/4] Update ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Márton Boros --- .../pgsodium/after-create.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql b/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql index 28f03b7211..a41a8dc060 100644 --- a/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql +++ b/ansible/files/postgresql_extension_custom_scripts/pgsodium/after-create.sql @@ -2,8 +2,8 @@ grant execute on function pgsodium.crypto_aead_det_decrypt(pg_catalog.bytea, pg_ grant execute on function pgsodium.crypto_aead_det_encrypt(pg_catalog.bytea, pg_catalog.bytea, pg_catalog.uuid, pg_catalog.bytea) to service_role; grant execute on function pgsodium.crypto_aead_det_keygen to service_role; -CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role regrole, source_name text, view_name text) -RETURNS void +CREATE OR REPLACE FUNCTION pgsodium.mask_role(masked_role pg_catalog.regrole, source_name pg_catalog.text, view_name pg_catalog.text) +RETURNS pg_catalog.void LANGUAGE plpgsql SECURITY DEFINER SET search_path TO pg_catalog, pg_temp