From 3a0e7e5f2938fa37d8800c1fa5c524e4a4980ad8 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Tue, 10 Nov 2026 16:40:18 -0600 Subject: [PATCH 1/5] fix: strict qualification --- Makefile | 2 +- flake.lock | 8 ++-- nix/plpgsql-check.nix | 8 ++-- sql/supabase_vault--0.3.2--0.3.3.sql | 56 ++++++++++++++++++++++++++++ test/expected/test.out | 38 +++++++++++++++---- test/sql/test.sql | 3 +- 6 files changed, 99 insertions(+), 16 deletions(-) create mode 100644 sql/supabase_vault--0.3.2--0.3.3.sql diff --git a/Makefile b/Makefile index 8d9c9e8..d609676 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ else endif EXTENSION = supabase_vault -EXTVERSION = 0.3.2 +EXTVERSION = 0.3.3 DATA = $(wildcard sql/*--*.sql) diff --git a/flake.lock b/flake.lock index 704715e..0f9a553 100644 --- a/flake.lock +++ b/flake.lock @@ -43,16 +43,16 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1791394750, - "narHash": "sha256-SMv4EKUaGmfUyXyeGHW3AiX1vbef7El56RKTIxio8jQ=", + "lastModified": 1793674321, + "narHash": "sha256-wLcCioA8+xi7ck/gdZmBeZo/NJfXxvYYCVOpAGvCgK8=", "owner": "supabase", "repo": "xpg", - "rev": "8f2a2005e8e020e0db5a7cbb94a87a9dd3227411", + "rev": "27aedcb0a23bce2879e7b114369e9c937665082d", "type": "github" }, "original": { "owner": "supabase", - "ref": "v2.6.0", + "ref": "v2.7.0", "repo": "xpg", "type": "github" } diff --git a/nix/plpgsql-check.nix b/nix/plpgsql-check.nix index 3031ce4..f48d234 100644 --- a/nix/plpgsql-check.nix +++ b/nix/plpgsql-check.nix @@ -7,13 +7,15 @@ stdenv.mkDerivation rec { pname = "plpgsql-check"; - version = "2.10.15"; + version = "6afcb44a5f9ec06de7b74d8f8ebbeb638ac7b945"; src = fetchFromGitHub { owner = "okbob"; repo = "plpgsql_check"; - rev = "v${version}"; - hash = "sha256-war+GVHMDegfDXjxdIsUkrPPDntKeyb/bGxibHVbpDo="; + # rev = "v${version}"; + rev = "6afcb44a5f9ec06de7b74d8f8ebbeb638ac7b945"; + + hash = "sha256-AQGJglOiuUC5djHNiJHpzDErfpmqglvs7djxfX8mHEc="; }; buildInputs = [ postgresql ]; diff --git a/sql/supabase_vault--0.3.2--0.3.3.sql b/sql/supabase_vault--0.3.2--0.3.3.sql new file mode 100644 index 0000000..08f95e1 --- /dev/null +++ b/sql/supabase_vault--0.3.2--0.3.3.sql @@ -0,0 +1,56 @@ +CREATE OR REPLACE FUNCTION vault.create_secret(new_secret text + , new_name text = CAST(NULL AS text) + , new_description text = CAST('' AS text) + , new_key_id uuid = CAST(NULL AS uuid)) +RETURNS uuid +LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'pg_catalog', 'pg_temp' +AS $$ +DECLARE + rec record; +BEGIN + INSERT INTO vault.secrets (secret, name, description) + VALUES ( + new_secret, + new_name, + new_description + ) + RETURNING * INTO rec; + UPDATE vault.secrets s + SET secret = pg_catalog.encode(vault._crypto_aead_det_encrypt( + message := pg_catalog.convert_to(rec.secret, 'utf8'), + additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'), + key_id := 0, + context := 'pgsodium'::pg_catalog.bytea, + nonce := rec.nonce + ), 'base64') + WHERE id OPERATOR(pg_catalog.=) rec.id; + RETURN rec.id; +END +$$; + + +CREATE OR REPLACE FUNCTION vault.update_secret(secret_id uuid + , new_secret text = CAST(NULL AS text) + , new_name text = CAST(NULL AS text) + , new_description text = CAST(NULL AS text) + , new_key_id uuid = CAST(NULL AS uuid)) +RETURNS void +LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'pg_catalog', 'pg_temp' +AS $$ +BEGIN + UPDATE vault.secrets s + SET + secret = CASE WHEN new_secret IS NULL THEN s.secret + ELSE pg_catalog.encode(vault._crypto_aead_det_encrypt( + message := pg_catalog.convert_to(new_secret, 'utf8'), + additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'), + key_id := 0, + context := 'pgsodium'::pg_catalog.bytea, + nonce := s.nonce + ), 'base64') END, + name = coalesce(new_name, s.name), + description = coalesce(new_description, s.description), + updated_at = now() + WHERE s.id OPERATOR(pg_catalog.=) secret_id; +END +$$; diff --git a/test/expected/test.out b/test/expected/test.out index f176917..7740963 100644 --- a/test/expected/test.out +++ b/test/expected/test.out @@ -85,7 +85,7 @@ where name = 'empty_secret'; SELECT extversion FROM pg_extension where extname = 'supabase_vault'; extversion ------------ - 0.3.2 + 0.3.3 (1 row) -- Validate that each security definer function explicitly specifies pg_temp and @@ -139,7 +139,8 @@ FROM SELECT plpgsql_check_function_tb(pg_proc.oid, COALESCE(pg_trigger.tgrelid, 0), oldtable=>pg_trigger.tgoldtable, - newtable=>pg_trigger.tgnewtable) AS pcf + newtable=>pg_trigger.tgnewtable, + extra_security_warnings_level=>4) AS pcf FROM pg_proc LEFT JOIN pg_trigger ON (pg_trigger.tgfoid = pg_proc.oid) @@ -152,9 +153,32 @@ FROM OFFSET 0 ) ss ORDER BY (pcf).functionid::regprocedure::text, (pcf).lineno; - functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------------------------------------------+--------+-----------+----------+-------------------------------+--------+------+---------------+----------+-------+--------- - vault.create_secret(text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | - vault.update_secret(uuid,text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | -(2 rows) + functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context +-----------------------------------------------+--------+---------------+----------+--------------------------------------+--------+------+----------------+----------+-----------------------------------------------------------------------------------------+--------- + vault.create_secret(text,text,text,uuid) | 12 | SQL statement | 00000 | column identifier is not qualified | | | security extra | 327 | UPDATE vault.secrets s +| + | | | | | | | | | SET secret = pg_catalog.encode(vault._crypto_aead_det_encrypt( +| + | | | | | | | | | message := pg_catalog.convert_to(rec.secret, 'utf8'), +| + | | | | | | | | | additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'), +| + | | | | | | | | | key_id := 0, +| + | | | | | | | | | context := 'pgsodium'::pg_catalog.bytea, +| + | | | | | | | | | nonce := rec.nonce +| + | | | | | | | | | ), 'base64') +| + | | | | | | | | | WHERE id OPERATOR(pg_catalog.=) rec.id | + vault.create_secret(text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | + vault.update_secret(uuid,text,text,text,uuid) | 3 | SQL statement | 00000 | function identifier is not qualified | | | security extra | 604 | UPDATE vault.secrets s +| + | | | | | | | | | SET +| + | | | | | | | | | secret = CASE WHEN new_secret IS NULL THEN s.secret +| + | | | | | | | | | ELSE pg_catalog.encode(vault._crypto_aead_det_encrypt( +| + | | | | | | | | | message := pg_catalog.convert_to(new_secret, 'utf8'), +| + | | | | | | | | | additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'),+| + | | | | | | | | | key_id := 0, +| + | | | | | | | | | context := 'pgsodium'::pg_catalog.bytea, +| + | | | | | | | | | nonce := s.nonce +| + | | | | | | | | | ), 'base64') END, +| + | | | | | | | | | name = coalesce(new_name, s.name), +| + | | | | | | | | | description = coalesce(new_description, s.description), +| + | | | | | | | | | updated_at = now() +| + | | | | | | | | | WHERE s.id OPERATOR(pg_catalog.=) secret_id | + vault.update_secret(uuid,text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | +(4 rows) diff --git a/test/sql/test.sql b/test/sql/test.sql index 09b2086..e0a4ccc 100644 --- a/test/sql/test.sql +++ b/test/sql/test.sql @@ -114,7 +114,8 @@ FROM SELECT plpgsql_check_function_tb(pg_proc.oid, COALESCE(pg_trigger.tgrelid, 0), oldtable=>pg_trigger.tgoldtable, - newtable=>pg_trigger.tgnewtable) AS pcf + newtable=>pg_trigger.tgnewtable, + extra_security_warnings_level=>4) AS pcf FROM pg_proc LEFT JOIN pg_trigger ON (pg_trigger.tgfoid = pg_proc.oid) From 00fccdb4567528dc03c65c502f8f415541e15192 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Tue, 10 Nov 2026 16:41:13 -0600 Subject: [PATCH 2/5] chore: run schema dump script --- supabase_vault.sql | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/supabase_vault.sql b/supabase_vault.sql index de0b3be..11a3ec0 100644 --- a/supabase_vault.sql +++ b/supabase_vault.sql @@ -49,14 +49,14 @@ BEGIN ) RETURNING * INTO rec; UPDATE vault.secrets s - SET secret = encode(vault._crypto_aead_det_encrypt( - message := convert_to(rec.secret, 'utf8'), - additional := convert_to(s.id::text, 'utf8'), + SET secret = pg_catalog.encode(vault._crypto_aead_det_encrypt( + message := pg_catalog.convert_to(rec.secret, 'utf8'), + additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'), key_id := 0, - context := 'pgsodium'::bytea, + context := 'pgsodium'::pg_catalog.bytea, nonce := rec.nonce ), 'base64') - WHERE id = rec.id; + WHERE id OPERATOR(pg_catalog.=) rec.id; RETURN rec.id; END $$; @@ -75,17 +75,17 @@ BEGIN UPDATE vault.secrets s SET secret = CASE WHEN new_secret IS NULL THEN s.secret - ELSE encode(vault._crypto_aead_det_encrypt( - message := convert_to(new_secret, 'utf8'), - additional := convert_to(s.id::text, 'utf8'), + ELSE pg_catalog.encode(vault._crypto_aead_det_encrypt( + message := pg_catalog.convert_to(new_secret, 'utf8'), + additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'), key_id := 0, - context := 'pgsodium'::bytea, + context := 'pgsodium'::pg_catalog.bytea, nonce := s.nonce ), 'base64') END, name = coalesce(new_name, s.name), description = coalesce(new_description, s.description), updated_at = now() - WHERE s.id = secret_id; + WHERE s.id OPERATOR(pg_catalog.=) secret_id; END $$; From 1f4d370fce1f827ed840269c3c6d4eba0a232b98 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Tue, 10 Nov 2026 19:37:10 -0600 Subject: [PATCH 3/5] fix: fully qualify function call --- sql/supabase_vault--0.3.2--0.3.3.sql | 2 +- test/expected/test.out | 42 ++++++++++------------------ 2 files changed, 15 insertions(+), 29 deletions(-) diff --git a/sql/supabase_vault--0.3.2--0.3.3.sql b/sql/supabase_vault--0.3.2--0.3.3.sql index 08f95e1..3aaf766 100644 --- a/sql/supabase_vault--0.3.2--0.3.3.sql +++ b/sql/supabase_vault--0.3.2--0.3.3.sql @@ -50,7 +50,7 @@ BEGIN ), 'base64') END, name = coalesce(new_name, s.name), description = coalesce(new_description, s.description), - updated_at = now() + updated_at = pg_catalog.now() WHERE s.id OPERATOR(pg_catalog.=) secret_id; END $$; diff --git a/test/expected/test.out b/test/expected/test.out index 7740963..722c324 100644 --- a/test/expected/test.out +++ b/test/expected/test.out @@ -153,32 +153,18 @@ FROM OFFSET 0 ) ss ORDER BY (pcf).functionid::regprocedure::text, (pcf).lineno; - functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------------------------------------------+--------+---------------+----------+--------------------------------------+--------+------+----------------+----------+-----------------------------------------------------------------------------------------+--------- - vault.create_secret(text,text,text,uuid) | 12 | SQL statement | 00000 | column identifier is not qualified | | | security extra | 327 | UPDATE vault.secrets s +| - | | | | | | | | | SET secret = pg_catalog.encode(vault._crypto_aead_det_encrypt( +| - | | | | | | | | | message := pg_catalog.convert_to(rec.secret, 'utf8'), +| - | | | | | | | | | additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'), +| - | | | | | | | | | key_id := 0, +| - | | | | | | | | | context := 'pgsodium'::pg_catalog.bytea, +| - | | | | | | | | | nonce := rec.nonce +| - | | | | | | | | | ), 'base64') +| - | | | | | | | | | WHERE id OPERATOR(pg_catalog.=) rec.id | - vault.create_secret(text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | - vault.update_secret(uuid,text,text,text,uuid) | 3 | SQL statement | 00000 | function identifier is not qualified | | | security extra | 604 | UPDATE vault.secrets s +| - | | | | | | | | | SET +| - | | | | | | | | | secret = CASE WHEN new_secret IS NULL THEN s.secret +| - | | | | | | | | | ELSE pg_catalog.encode(vault._crypto_aead_det_encrypt( +| - | | | | | | | | | message := pg_catalog.convert_to(new_secret, 'utf8'), +| - | | | | | | | | | additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'),+| - | | | | | | | | | key_id := 0, +| - | | | | | | | | | context := 'pgsodium'::pg_catalog.bytea, +| - | | | | | | | | | nonce := s.nonce +| - | | | | | | | | | ), 'base64') END, +| - | | | | | | | | | name = coalesce(new_name, s.name), +| - | | | | | | | | | description = coalesce(new_description, s.description), +| - | | | | | | | | | updated_at = now() +| - | | | | | | | | | WHERE s.id OPERATOR(pg_catalog.=) secret_id | - vault.update_secret(uuid,text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | -(4 rows) + functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context +-----------------------------------------------+--------+---------------+----------+------------------------------------+--------+------+----------------+----------+-------------------------------------------------------------------------+--------- + vault.create_secret(text,text,text,uuid) | 12 | SQL statement | 00000 | column identifier is not qualified | | | security extra | 327 | UPDATE vault.secrets s +| + | | | | | | | | | SET secret = pg_catalog.encode(vault._crypto_aead_det_encrypt( +| + | | | | | | | | | message := pg_catalog.convert_to(rec.secret, 'utf8'), +| + | | | | | | | | | additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'),+| + | | | | | | | | | key_id := 0, +| + | | | | | | | | | context := 'pgsodium'::pg_catalog.bytea, +| + | | | | | | | | | nonce := rec.nonce +| + | | | | | | | | | ), 'base64') +| + | | | | | | | | | WHERE id OPERATOR(pg_catalog.=) rec.id | + vault.create_secret(text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | + vault.update_secret(uuid,text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | +(3 rows) From fe2efe238ff6ab605687929ef23e258135e5791f Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Tue, 10 Nov 2026 19:43:08 -0600 Subject: [PATCH 4/5] refactor: qualify column name --- sql/supabase_vault--0.3.2--0.3.3.sql | 2 +- test/expected/test.out | 19 +++++-------------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/sql/supabase_vault--0.3.2--0.3.3.sql b/sql/supabase_vault--0.3.2--0.3.3.sql index 3aaf766..a9756a5 100644 --- a/sql/supabase_vault--0.3.2--0.3.3.sql +++ b/sql/supabase_vault--0.3.2--0.3.3.sql @@ -23,7 +23,7 @@ BEGIN context := 'pgsodium'::pg_catalog.bytea, nonce := rec.nonce ), 'base64') - WHERE id OPERATOR(pg_catalog.=) rec.id; + WHERE s.id OPERATOR(pg_catalog.=) rec.id; RETURN rec.id; END $$; diff --git a/test/expected/test.out b/test/expected/test.out index 722c324..8ab78ee 100644 --- a/test/expected/test.out +++ b/test/expected/test.out @@ -153,18 +153,9 @@ FROM OFFSET 0 ) ss ORDER BY (pcf).functionid::regprocedure::text, (pcf).lineno; - functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context ------------------------------------------------+--------+---------------+----------+------------------------------------+--------+------+----------------+----------+-------------------------------------------------------------------------+--------- - vault.create_secret(text,text,text,uuid) | 12 | SQL statement | 00000 | column identifier is not qualified | | | security extra | 327 | UPDATE vault.secrets s +| - | | | | | | | | | SET secret = pg_catalog.encode(vault._crypto_aead_det_encrypt( +| - | | | | | | | | | message := pg_catalog.convert_to(rec.secret, 'utf8'), +| - | | | | | | | | | additional := pg_catalog.convert_to(s.id::pg_catalog.text, 'utf8'),+| - | | | | | | | | | key_id := 0, +| - | | | | | | | | | context := 'pgsodium'::pg_catalog.bytea, +| - | | | | | | | | | nonce := rec.nonce +| - | | | | | | | | | ), 'base64') +| - | | | | | | | | | WHERE id OPERATOR(pg_catalog.=) rec.id | - vault.create_secret(text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | - vault.update_secret(uuid,text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | -(3 rows) + functionid | lineno | statement | sqlstate | message | detail | hint | level | position | query | context +-----------------------------------------------+--------+-----------+----------+-------------------------------+--------+------+---------------+----------+-------+--------- + vault.create_secret(text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | + vault.update_secret(uuid,text,text,text,uuid) | | | 00000 | unused parameter "new_key_id" | | | warning extra | | | +(2 rows) From f562fbeab3fb84e1e2a2aa1ef32f8702a30e2948 Mon Sep 17 00:00:00 2001 From: Andrew Jackson Date: Tue, 10 Nov 2026 21:20:37 -0600 Subject: [PATCH 5/5] chore: sql snapshot --- supabase_vault.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/supabase_vault.sql b/supabase_vault.sql index 11a3ec0..49f39f4 100644 --- a/supabase_vault.sql +++ b/supabase_vault.sql @@ -56,7 +56,7 @@ BEGIN context := 'pgsodium'::pg_catalog.bytea, nonce := rec.nonce ), 'base64') - WHERE id OPERATOR(pg_catalog.=) rec.id; + WHERE s.id OPERATOR(pg_catalog.=) rec.id; RETURN rec.id; END $$; @@ -84,7 +84,7 @@ BEGIN ), 'base64') END, name = coalesce(new_name, s.name), description = coalesce(new_description, s.description), - updated_at = now() + updated_at = pg_catalog.now() WHERE s.id OPERATOR(pg_catalog.=) secret_id; END $$;