From a45933699c0b750925011c0b3ecba384a025c3f1 Mon Sep 17 00:00:00 2001 From: "Anh Nguyen (Jerry)" <92361793+anh-nguyenn@users.noreply.github.com> Date: Sat, 7 Nov 2026 10:14:20 +0000 Subject: [PATCH] fix(0028,0029): only report functions exposed by PostgREST These lints match every SECURITY DEFINER pg_proc entry executable by the role, including procedures and trigger functions. PostgREST does not expose either: its schema cache selects prokind = 'f' and excludes functions returning trigger, so the findings claim an /rpc route that does not exist. Filter both lints the same way and add regression cases for a SECURITY DEFINER procedure and a SECURITY DEFINER trigger function. --- ...n_security_definer_function_executable.sql | 4 +++ ...d_security_definer_function_executable.sql | 4 +++ splinter.sql | 8 ++++++ ...n_security_definer_function_executable.out | 26 +++++++++++++++++++ ...d_security_definer_function_executable.out | 26 +++++++++++++++++++ ...n_security_definer_function_executable.sql | 22 ++++++++++++++++ ...d_security_definer_function_executable.sql | 22 ++++++++++++++++ 7 files changed, 112 insertions(+) diff --git a/lints/0028_anon_security_definer_function_executable.sql b/lints/0028_anon_security_definer_function_executable.sql index ac47354a..39f3385e 100644 --- a/lints/0028_anon_security_definer_function_executable.sql +++ b/lints/0028_anon_security_definer_function_executable.sql @@ -60,6 +60,10 @@ from on p.prolang = l.oid where p.prosecdef = true + -- PostgREST only exposes ordinary functions: procedures are not + -- callable via /rpc, and neither are trigger functions. + and p.prokind = 'f' + and p.prorettype <> 'pg_catalog.trigger'::regtype and pg_catalog.has_function_privilege('anon', p.oid, 'EXECUTE') and n.nspname = any(array(select trim(unnest(string_to_array(coalesce(current_setting('pgrst.db_schemas', 't'), 'public'), ','))))) and n.nspname not in ( diff --git a/lints/0029_authenticated_security_definer_function_executable.sql b/lints/0029_authenticated_security_definer_function_executable.sql index a0008957..5383f037 100644 --- a/lints/0029_authenticated_security_definer_function_executable.sql +++ b/lints/0029_authenticated_security_definer_function_executable.sql @@ -61,6 +61,10 @@ from on p.prolang = l.oid where p.prosecdef = true + -- PostgREST only exposes ordinary functions: procedures are not + -- callable via /rpc, and neither are trigger functions. + and p.prokind = 'f' + and p.prorettype <> 'pg_catalog.trigger'::regtype and pg_catalog.has_function_privilege('authenticated', p.oid, 'EXECUTE') and n.nspname = any(array(select trim(unnest(string_to_array(coalesce(current_setting('pgrst.db_schemas', 't'), 'public'), ','))))) and n.nspname not in ( diff --git a/splinter.sql b/splinter.sql index 46bb331e..622f4848 100644 --- a/splinter.sql +++ b/splinter.sql @@ -1756,6 +1756,10 @@ from on p.prolang = l.oid where p.prosecdef = true + -- PostgREST only exposes ordinary functions: procedures are not + -- callable via /rpc, and neither are trigger functions. + and p.prokind = 'f' + and p.prorettype <> 'pg_catalog.trigger'::regtype and pg_catalog.has_function_privilege('anon', p.oid, 'EXECUTE') and n.nspname = any(array(select trim(unnest(string_to_array(coalesce(current_setting('pgrst.db_schemas', 't'), 'public'), ','))))) and n.nspname not in ( @@ -1829,6 +1833,10 @@ from on p.prolang = l.oid where p.prosecdef = true + -- PostgREST only exposes ordinary functions: procedures are not + -- callable via /rpc, and neither are trigger functions. + and p.prokind = 'f' + and p.prorettype <> 'pg_catalog.trigger'::regtype and pg_catalog.has_function_privilege('authenticated', p.oid, 'EXECUTE') and n.nspname = any(array(select trim(unnest(string_to_array(coalesce(current_setting('pgrst.db_schemas', 't'), 'public'), ','))))) and n.nspname not in ( diff --git a/test/expected/0028_anon_security_definer_function_executable.out b/test/expected/0028_anon_security_definer_function_executable.out index d9cc4729..9cadb2a3 100644 --- a/test/expected/0028_anon_security_definer_function_executable.out +++ b/test/expected/0028_anon_security_definer_function_executable.out @@ -121,4 +121,30 @@ begin; (0 rows) rollback to savepoint case_system_schema; + savepoint case_not_rpc_callable; + ---------------------------------------- + -- NEGATIVE: procedures are not exposed by PostgREST, so a + -- SECURITY DEFINER procedure must not be reported as callable via + -- /rest/v1/rpc. + ---------------------------------------- + create procedure public.secdef_proc() language sql security definer + as $$ select 1 $$; + select * from lint."0028_anon_security_definer_function_executable"; + name | title | level | facing | categories | description | detail | remediation | metadata | cache_key +------+-------+-------+--------+------------+-------------+--------+-------------+----------+----------- +(0 rows) + + ---------------------------------------- + -- NEGATIVE: trigger functions return trigger and are not exposed + -- as RPC endpoints either. + ---------------------------------------- + create function public.secdef_trigger() returns trigger + language plpgsql security definer + as $$ begin return new; end $$; + select * from lint."0028_anon_security_definer_function_executable"; + name | title | level | facing | categories | description | detail | remediation | metadata | cache_key +------+-------+-------+--------+------------+-------------+--------+-------------+----------+----------- +(0 rows) + + rollback to savepoint case_not_rpc_callable; rollback; diff --git a/test/expected/0029_authenticated_security_definer_function_executable.out b/test/expected/0029_authenticated_security_definer_function_executable.out index 7f009934..33bb073a 100644 --- a/test/expected/0029_authenticated_security_definer_function_executable.out +++ b/test/expected/0029_authenticated_security_definer_function_executable.out @@ -117,4 +117,30 @@ begin; (0 rows) rollback to savepoint case_system_schema; + savepoint case_not_rpc_callable; + ---------------------------------------- + -- NEGATIVE: procedures are not exposed by PostgREST, so a + -- SECURITY DEFINER procedure must not be reported as callable via + -- /rest/v1/rpc. + ---------------------------------------- + create procedure public.secdef_proc() language sql security definer + as $$ select 1 $$; + select * from lint."0029_authenticated_security_definer_function_executable"; + name | title | level | facing | categories | description | detail | remediation | metadata | cache_key +------+-------+-------+--------+------------+-------------+--------+-------------+----------+----------- +(0 rows) + + ---------------------------------------- + -- NEGATIVE: trigger functions return trigger and are not exposed + -- as RPC endpoints either. + ---------------------------------------- + create function public.secdef_trigger() returns trigger + language plpgsql security definer + as $$ begin return new; end $$; + select * from lint."0029_authenticated_security_definer_function_executable"; + name | title | level | facing | categories | description | detail | remediation | metadata | cache_key +------+-------+-------+--------+------------+-------------+--------+-------------+----------+----------- +(0 rows) + + rollback to savepoint case_not_rpc_callable; rollback; diff --git a/test/sql/0028_anon_security_definer_function_executable.sql b/test/sql/0028_anon_security_definer_function_executable.sql index 10119188..7b510a83 100644 --- a/test/sql/0028_anon_security_definer_function_executable.sql +++ b/test/sql/0028_anon_security_definer_function_executable.sql @@ -110,4 +110,26 @@ begin; rollback to savepoint case_system_schema; + savepoint case_not_rpc_callable; + + ---------------------------------------- + -- NEGATIVE: procedures are not exposed by PostgREST, so a + -- SECURITY DEFINER procedure must not be reported as callable via + -- /rest/v1/rpc. + ---------------------------------------- + create procedure public.secdef_proc() language sql security definer + as $$ select 1 $$; + select * from lint."0028_anon_security_definer_function_executable"; + + ---------------------------------------- + -- NEGATIVE: trigger functions return trigger and are not exposed + -- as RPC endpoints either. + ---------------------------------------- + create function public.secdef_trigger() returns trigger + language plpgsql security definer + as $$ begin return new; end $$; + select * from lint."0028_anon_security_definer_function_executable"; + + rollback to savepoint case_not_rpc_callable; + rollback; diff --git a/test/sql/0029_authenticated_security_definer_function_executable.sql b/test/sql/0029_authenticated_security_definer_function_executable.sql index 1004bef1..f029a609 100644 --- a/test/sql/0029_authenticated_security_definer_function_executable.sql +++ b/test/sql/0029_authenticated_security_definer_function_executable.sql @@ -105,4 +105,26 @@ begin; rollback to savepoint case_system_schema; + savepoint case_not_rpc_callable; + + ---------------------------------------- + -- NEGATIVE: procedures are not exposed by PostgREST, so a + -- SECURITY DEFINER procedure must not be reported as callable via + -- /rest/v1/rpc. + ---------------------------------------- + create procedure public.secdef_proc() language sql security definer + as $$ select 1 $$; + select * from lint."0029_authenticated_security_definer_function_executable"; + + ---------------------------------------- + -- NEGATIVE: trigger functions return trigger and are not exposed + -- as RPC endpoints either. + ---------------------------------------- + create function public.secdef_trigger() returns trigger + language plpgsql security definer + as $$ begin return new; end $$; + select * from lint."0029_authenticated_security_definer_function_executable"; + + rollback to savepoint case_not_rpc_callable; + rollback;