-- search_path für alle eigenen Funktionen festnageln. -- -- Der Supabase-Linter meldet 34 Funktionen mit „Function Search Path Mutable". -- Nachgezählt sind das alles SECURITY-INVOKER-Funktionen; die vier -- SECURITY-DEFINER-Funktionen (is_hr_user, current_hr_user_id, -- apply_due_pending_changes, fn_track_employee_assignment) setzen den Pfad -- längst. Deshalb steht im Advisor auch 0 errors. -- -- Warum das trotzdem behoben wird: -- -- Der Angriff braucht SECURITY DEFINER. Wer in einem Schema, das im -- search_path früher liegt, eine eigene Tabelle `employees` anlegt, bringt -- eine unqualifiziert schreibende Funktion dazu, auf die untergeschobene -- zuzugreifen — mit den Rechten der Eigentümerin der Funktion. Bei INVOKER -- läuft alles mit den Rechten der aufrufenden Person, es gibt also nichts zu -- gewinnen, und die RLS-Policies greifen unverändert. -- -- Zur Lücke wird die Warnung erst, wenn eine dieser Funktionen später auf -- SECURITY DEFINER umgestellt wird, etwa weil eine Mutation an RLS vorbei -- schreiben muss. In dem Moment denkt niemand mehr an den search_path. -- Einmal festnageln räumt die Falle weg und ändert kein Verhalten. -- -- `pg_temp` steht ausdrücklich am Ende: ohne die Angabe durchsucht Postgres -- das temporäre Schema *zuerst*, und dort darf jede Sitzung anlegen, was sie -- will. do $$ declare v_func record; v_count int := 0; begin for v_func in select p.oid::regprocedure as signature from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' -- Nur Funktionen, keine Prozeduren oder Aggregate. and p.prokind = 'f' -- Erweiterungen gehören uns nicht: pg_trgm legt show_trgm und show_limit -- in public ab. Daran zu drehen bricht bei der nächsten Aktualisierung -- der Erweiterung oder wird stillschweigend zurückgesetzt. and not exists ( select 1 from pg_depend d where d.objid = p.oid and d.deptype = 'e' ) -- Bereits gesetzte nicht anfassen: die vier DEFINER-Funktionen stehen -- auf `search_path = public` und sollen so bleiben. and not exists ( select 1 from unnest(coalesce(p.proconfig, '{}')) c where c like 'search_path=%' ) loop execute format('alter function %s set search_path = public, pg_temp', v_func.signature); v_count := v_count + 1; end loop; raise notice 'search_path festgenagelt für % Funktion(en)', v_count; end; $$; -- Gegenprobe: danach darf in public keine eigene Funktion ohne search_path -- mehr stehen. Schlägt das an, hat die Schleife oben etwas übersehen — besser -- hier, als es im Advisor stehen zu lassen. do $$ declare v_offen int; begin select count(*) into v_offen from pg_proc p join pg_namespace n on n.oid = p.pronamespace where n.nspname = 'public' and p.prokind = 'f' and not exists (select 1 from pg_depend d where d.objid = p.oid and d.deptype = 'e') and not exists (select 1 from unnest(coalesce(p.proconfig, '{}')) c where c like 'search_path=%'); if v_offen > 0 then raise exception 'Es stehen noch % Funktion(en) ohne search_path in public.', v_offen; end if; end; $$;