Lühidalt
raw_user_meta_data hoiab kõike, mille brauser andis edasi supabase.auth.signUp()-ile. Ühes tootmisrakenduses luges handle_new_user trigger uue kasutaja rolli sealt, nii et igaüks sai registreeruda Owneri rolli id-ga ja konto enda valdusse võtta. Parandus: roll tuleb tabelist, mida kirjutab ainult server ja mis täidetakse enne kutse saatmist.
See oli audiitorfirma ajaarvestuse rakenduses, Supabase’i rakenduses, kus iga ligipääsureegel elab andmebaasis. Viga oli triggeris, mis loob iga uue kasutaja jaoks profiili.
Mida trigger tegi?
Lihtsustatult nägi see välja nii:
create function public.handle_new_user()
returns trigger
language plpgsql
security definer set search_path = ''
as $$
declare
_role_id uuid := (new.raw_user_meta_data ->> 'role_id')::uuid;
begin
insert into public.profiles (id, full_name, role_id)
values (new.id, new.raw_user_meta_data ->> 'full_name', _role_id);
return new;
end;
$$;
Kutsevoog pani õige role_id sinna, nii et tavakasutuses töötas see. Kuid raw_user_meta_data on registreerimise options.data ja selle sisu otsustab brauser:
await supabase.auth.signUp({
email: 'me@example.com',
password: '...',
options: { data: { role_id: '<owner role uuid>' } },
})
Õiguse kontroll tagastas omanike puhul tõe enne mis tahes muud vaatamist, nii et see üks väli andis täieliku kontrolli konto üle. Supabase’i enda Users guide ütleb, et kasutaja metaandmeid ei tohi kasutada RLS-poliitikates ega autoriseerimisloogikas, sest kasutaja saab neid ilma kontrollideta muuta.
Mis selle parandas?
Roll tuleb nüüd privaatsest tabelist, millele klient ligi ei pääse:
create table private.pending_invites (
email text primary key,
role_id uuid,
invited_by uuid,
created_at timestamptz default now()
);
revoke all on table private.pending_invites from anon, authenticated;
Kutsefunktsioon töötab serveris ja kontrollib kutsuja õigust. See kirjutab kõigepealt ootel kutse ja alles siis loob kutselingi. Trigger otsib rolli e-posti järgi ja kustutab rea. Nimi tuleb endiselt metaandmetest, mis on korras: see on kuvatav info.
Järjekord loeb. Kutse loob kasutaja kohe: Supabase’i Users guide märgib, et uue e-posti kutsumine loob kinnitamata kasutaja. Trigger käivitub sel hetkel, seega peab ootel rida juba olemas olema.
Kuidas oma rakendust kontrollida?
Avage oma handle_new_user funktsioon või mis tahes trigger tabelil auth.users ja otsige raw_user_meta_data. Nimi ja avatar sealt on korras. Roll, ettevõte, pakett, rentnik või miski muu, mis annab ligipääsu, peab tulema tabelist või raw_app_meta_data’st, mida kasutaja muuta ei saa.
Seejärel proovige: registreeruge API kaudu, pannes options.data sisse role_id või role: 'admin'. Uus konto ei tohiks saada midagi lisaks.
Selline kontroll on mu RLS-i testimeetodi esimene samm. Mitme rentnikuga rakenduste puhul vaadake ka, mida RLS kahe org-i kasutaja puhul kahe silma vahele jätab. Teised kontrollid, mida teen igas Supabase’i rakenduses, on mu Lovable’i rakenduse turvakontrollnimekirjas. Esimene, mida ma Dashboardis vaatan, on millised skeemid Data API avaldab. Supabase turvakontroll katab rollid, õigused, poliitikad ja autentimisseaded 45 OWASP Top 10:2025 reegli järgi, mitte seda, mida trigger registreerimise metaandmetest kopeerib, seega see jääb käsitsi tehtavaks kontrolliks.