Īsumā
Atļaujas pārbaude SECURITY DEFINER funkcijā bija if not (has_perm or created_by = me or assignee_id = me) then raise. 2026. gada jūnijā visiem 3683 lietotnes uzdevumiem nebija ne izveidotāja, ne atbildīgā. false OR NULL OR NULL ir NULL, NOT NULL joprojām ir NULL, un IF izlaiž savu pamatdaļu, ja nosacījums ir NULL. Tāpēc kļūda netika izmesta nekad, un jebkurš pieteicies lietotājs varēja klonēt uzdevumus. Labojums ir coalesce(..., false) ap visu nosacījumu.
Tas notika revīzijas uzņēmuma darba laika uzskaites lietotnē, funkcijā, kas stāv aiz “klonēt uzdevumu”.
Kā izskatījās pārbaude?
if not ( has_perm('tasks', 'create')
or v_task.created_by = auth.uid()
or v_task.assignee_id = auth.uid() ) then
raise exception 'forbidden';
end if;
Tā skan kā “atsaki, ja vien tu nedrīksti veidot uzdevumus, neizveidoji šo vai nepieder tev”. 3683 uzdevumi bija iesēti automātiski, tāpēc created_by un assignee_id visiem bija NULL. Lomai ar izslēgtām visām atļaujām nosacījums bija NOT (false OR NULL OR NULL), kas ir NULL, un raise nekad neizpildījās.
Kāpēc tas pats nosacījums RLS ir drošs?
Row level security visu, kas nav true, uztver kā atteikumu, tāpēc NULL nozīmē nē. PL/pgSQL IF visu, kas nav true, uztver kā “izlaid pamatdaļu”, un šeit pamatdaļa bija tieši atteikums. PostgreSQL dokumentācija nosaka abas puses, par row security un par IF; kopā tās to pašu izteiksmi vienā vietā pārvērš aizliegumā, bet otrā atļaujā.
SECURITY DEFINER funkcija darbojas arī apejot RLS, tāpēc spēkā ir tikai tās pašas pārbaude. Pietiek ar vienu NULL zaru: visi pārējie var būt false.
Kas to izlaboja?
Ietiniet visu nosacījumu tā, lai NULL kļūtu par atteikumu, un uztveriet klonēšanu kā uzdevuma izveidi, nevis avota rediģēšanu:
if not coalesce((select has_perm('tasks', 'create')), false) then
raise exception 'forbidden' using errcode = '42501';
end if;
Vai tas atkārtojās?
Jā, apmēram pēc trim mēnešiem, vēl trijās funkcijās: uzdevuma arhivēšana, tā tipa maiņa un pārbaude, vai uz tā drīkst reģistrēt laiku. SQL lasīšana tās nenotvēra. Notvēra RLS pierādījums pa lomām, jo tas izslēdz katru atļauju un pārbauda, vai darbība tiek noraidīta.
Kā meklēt?
grep 'if not (' atrod sintaksi, nevis kļūdu. Kas strādāja:
- Ar vienu vaicājumu izvelciet visu funkciju pamatdaļas ar
pg_get_functiondef. - Katrā
if not (...) thenunif a <> b thennoņemietcoalesce(...),exists(...)un(select ...). Kailāorķēde vai salīdzinājums ar kolonnu, kas var būt NULL, ir kandidāts. - Meklējiet arī
if not <variable> thenun izsekojiet, kur mainīgajam piešķir vērtību._ok := has_perm(...) or created_by = me;paslēpj to pašu kļūdu divās rindās. - Katru palīgfunkciju, ko lieto kā
if not helper(...), izsauciet trīs veidos atceltā transakcijā: nezināms id,NULLarguments un īsts lietotājs, kuram gaidītās rindas nav. Palīgfunkcijai, kas jebkurā no tiem atgriežNULL, vajadzīgscoalesceizsaukuma vietā vai iekšpusē.
Tajā lietotnē pārbaude aptvēra 14 palīgfunkcijas, visas jau bija drošas. Kļūda aprobežojās ar klonēšanas funkciju un trim vēlāk atrastajām. Saistīta vairāku nomnieku nepilnība: ko RLS izlaiž lietotājam divās org.. Lietotne bija revīzijas uzņēmuma darba laika uzskaites rīks; tā citas klusās kļūdas ir rakstā darba laika uzskaite revīzijas uzņēmumam. Supabase drošības pārbaude savos 46 noteikumos uzskaita SECURITY DEFINER funkcijas, bet nevar pateikt, vai IF tajās izlaiž pārbaudi (fails open), tāpēc šī pārlūkošana paliek manuāla.