Skip to content

feat: add definer_function_uses_current_user lint (0034) - #201

Open
mattmuller wants to merge 1 commit into
supabase:mainfrom
mattmuller:lint/0034-definer-function-uses-current-user
Open

mattmuller wants to merge 1 commit into
supabase:mainfrom
mattmuller:lint/0034-definer-function-uses-current-user

Conversation

@mattmuller

Copy link
Copy Markdown

What kind of change does this PR introduce?

New WARN-level lint 0034_definer_function_uses_current_user.

What is the current behavior?

Nothing flags current_user used for an authorization decision inside a SECURITY DEFINER function. In such a function current_user is the function owner for the whole call, never the caller. A check like current_user = 'admin_role' gives the same answer for every caller, so it either admits everyone or blocks everyone. session_user is not a fix either: through the Data API it is the connection role, not the end user.

What is the new behavior?

The lint inspects SQL and PL/pgSQL SECURITY DEFINER functions in exposed schemas (pgrst.db_schemas, default public; extension-owned functions skipped) and reports those that use current_user, current_role or user in an authorization-like way. It reads prosrc as text, so the heuristic is intentionally narrow:

  1. Block and line comments, quoted identifiers and string literals are blanked first, so 'current_user', "user" and -- current_user never match.
  2. Qualified names (t.user) and longer identifiers (user_id, my_current_user) never match. user is reserved in Postgres, so a bare unquoted user is the function.
  3. Only comparison operands (=, <>, !=, in, any, like, is distinct from) and arguments to pg_has_role / has_*_privilege count. Plain reads (values (current_user)) and set col = current_user assignments, typically audit columns, are ignored.
  4. Functions that run SET ROLE or SET SESSION AUTHORIZATION are skipped.

Minimal repro:

create function public.is_admin() returns boolean
language sql security definer set search_path = ''
as $$ select current_user = 'admin_role' $$;

select * from lint."0034_definer_function_uses_current_user";

Additional context

I searched open and closed issues and PRs for current_user and security definer and found nothing proposing this. Related but different: 0028 / 0029 report SECURITY DEFINER functions that are callable at all. #172 asks 0029 to skip functions with an authorization gate in the body, which is the opposite direction. #197 claims number 0031, so numbers in this series may need adjusting at merge.

How it was tested

test/sql/0034_definer_function_uses_current_user.sql:

  • Not flagged: SECURITY INVOKER function; definer using auth.uid(); identifiers inside a comment, a string literal and a declared variable current_user_label, plus a parameter user_id; insert ... values (current_user); update ... set owner_name = current_user; a function that runs set local role first.
  • Flagged (3 rows): current_user = 'admin_role'; owner_name = current_user in a filter; pg_has_role(current_user, ...) in PL/pgSQL.
  • Resolution: rewriting them with auth.uid() / auth.role() returns 0 rows.

Full suite passes on supabase/postgres 15.14.1.169 and 17.6.1.169, including the splinter.sql validity check. bin/check_lints.py passes.

False-positive risk. Low to moderate. Text matching cannot see dynamic SQL, so some real uses are missed. It can flag a function that deliberately checks its owner. Audit-style uses are excluded by the heuristic above.

@mattmuller
mattmuller requested a review from a team as a code owner October 12, 2026 09:56

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant