Liigu sisu juurde

Supabase RLS-i testimine: 30 õigust 87-st ei teinud midagi

Vaata MarkdowninaAva rakenduses ClaudeAva rakenduses ChatGPTAva rakenduses Perplexity

Küsimus kopeeritakse ka. Kui vestlus avaneb tühjana, kleepige see sisse.

Lühidalt

Testige Supabase’i row level security’t (reataseme turvet) nii, nagu liige sellega kokku puutub: logige sisse iga rolli liikmena, tehke päris toiming ühe õigusega enne väljas ja siis sees ning kontrollige, et tulemus muutub. Tootmisrakenduses, kus oli viis muudetavat rolli, selgus nii, et 30 neist 87 õiguse märkeruudust, mida administraator sai salvestada, ei muutnud andmebaasis mitte midagi. Poliitikapõhine test seda ei näe, sest see testib teie kirjutatud poliitikat, mitte ruutu ekraanil.

Olen Denys, full-stack- ja AI-insener. Selle aasta alguses ehitasin audiitorfirmale Supabase’i peale projekti- ja ajaarvestuse rakenduse, kus iga ligipääsureegel elab andmebaasis. Allpool kirjeldan meetodit, millega neid reegleid tõestan, mida see leidis ja mida selle käitamine maksab.

Miks poliitikatestidest ei piisa, et Supabase RLS-i testida?

Poliitikatest vastab küsimusele “kas see poliitika teeb seda, mida kirjutasin?”. Teie kasutajad küsivad midagi muud: “kui ma võtan sellel rollil selle linnukese maha, kas nad saavad seda ikka teha?”. Väikeses rakenduses on vastus mõlemale sama. Rakenduses, mille seadete lehel muudavad administraatorid õiguste maatriksit, lähevad vastused lahku.

Rakenduses oli 41 tabelit, kus RLS oli sisse lülitatud, ja 402 CREATE POLICY lauset 255 migratsioonis. Administraatori ekraanil oli maatriks, kus valdkonnad (projektid, ülesanded, tööajatabelid, failid ja nii edasi) olid vastamisi toimingutega (vaatamine, loomine, muutmine, arhiveerimine), üks veerg iga rolli kohta. Iga salvestatud ruut oli lubadus administraatorile. Miski ei kontrollinud, et andmebaas seda lubadust peab.

Supabase dokumenteerib standardsed tööriistad hästi: pgTAP testid ja testiabilised võimaldavad luua kasutajaid, nendeks ümber lülituda ja kontrollida, mida nad lugeda saavad. Kasutage neid. Allolev meetod tuleb nende peale: seda juhib maatriks, mida administraator näeb, mitte poliitikate nimekiri.

Kuidas maatriksi tõestus töötab?

Igast maatriksi ruudust saab sond. Sond logib sisse rolli liikmena, teeb ühe päris toimingu ja salvestab tulemuse. Ta teeb seda kaks korda: kord ruut väljas ja kord ruut sees, kõik muu samaks jäädes. Kui tulemus muutub, on ruut jõus. Kui ei muutu, ei tee ruut midagi, ükskõik kui veenvalt see ekraanil välja näeb.

  1. Üks ruut maatriksisNäide: Standard Member · Ülesanded · Loomine
  2. Ruut väljas: tegutse rolli liikmenaLoo ülesanne sama teed pidi, mida kasutab rakendus. Salvesta tulemus.
  3. Ruut sees: korda sama toimingutSama kasutaja, samad andmed. Muutus ainult ruut.
  4. Võrdle kahte tulemustOtsus sõltub ainult sellest, mida andmebaas tegi.
  • Tulemus muutusJõus. Väljas keelatud, sees lubatud.
  • Mõlemal korral samaEi tee midagi. Ekraan lubab kontrolli, mida pole olemas.
  • Ekraan ja andmebaas ei ole ühel meelelViga. Leht näitab nuppu, mille andmebaas tagasi lükkab, või vastupidi.
Joonis 1. Iga otsus tuleb kahest päris tulemusest, mitte kunagi koodi lugemisest.

“Tulemus” tähendab siin seda, mida õiguse kasutaja teeb: poliitika tagastab ridu või lükkab kirjutamise tagasi, andmebaasi funktsioon tõstab vea, trigger blokeerib või edge function vastab 403. Üks ruut võib valvata mitut neist, nii et 87 ruutu vajasid 279 kontrolli rolli kohta. Iga kontroll lõppes ühega neist otsustest:

Otsus Kontrolle rolli kohta Mida see tähendab
Mõju 227 Tulemus pöördus koos ruuduga
Surnud 30 Tulemus jäi samaks, olgu ruut väljas või sees
Kättesaamatu 19 Roll ei pääse toiminguni üldse, tõestatud eraldi
Edge function 1 Jõustab edge function, kontrollitud üle HTTP
Ainult ekraanil 1 Muudab ainult seda, mida leht näitab, kaetud front-end testiga
Ekraan ja andmebaas on ühel meelel 1 See, mida leht pakub, vastab sellele, mida andmebaas lubab
Kukkus läbi 0 Kontroll, mille tulemus ei vastanud ootusele

Mida tõestus leidis?

Käivitus 15. septembril 2026 kattis kõik viis rolli, mida administraator saab muuta (Admin, Senior, Standard, Restricted ja kloonitud kohandatud roll). Tulemused olid kõigi rollide puhul samad.

87 õiguse ruutu, mida administraator sai salvestada. Selle järgi, mis iga ruutu tegelikult jõustas. Sama kõigi viie rolli puhul. Jõustab andmebaas: 55; Jõustatakse mujal: 1 edge function, 1 ekraan: 2; Ei muutnud midagi: 30.
Joonis 2. 87 õiguse ruutu, mida administraator sai salvestada, selle järgi, mis neid tegelikult jõustas. Sama tulemus kõigi viie rolli puhul.

Ükski neist 30-st ei lasknud kedagi sisse. 29 puhul ei lugenud ükski poliitika, funktsioon ega trigger seda ruutu kunagi; viimane kaotas tähtsuse pärast seda, kui üks poliitika ümber kirjutati. See on ikkagi päris probleem: administraator, kes võtab rollil “Kustutamine” linnukese maha, usub, et roll ei saa kustutada. Peitsime kõik 30 ära, selle asemel et neid tööle ühendada, sest iga ühendamine oleks vajanud tooteotsust, ja ruut, mis ei tee midagi, ei peaks otsuse tegemise ajal ekraanil olema.

Sama töö tõi välja veel neli viga:

Mis oli valesti Kuidas see välja tuli Parandus
30 ruutu ei teinud midagi Surnud otsus, sama tulemus väljas ja sees Maatriksist peidetud
Kolm andmebaasi funktsiooni lasid toimingu läbi, kui ülesandel polnud vastutajat Sond, mis oleks pidanud olema keelatud, lubati läbi Funktsioonid keelavad nüüd, kui väärtus puudub
Tööajatabeli mustandiks lähtestamine sõltus valest ruudust Ekraan ja andmebaas ei olnud ühe rolli puhul ühel meelel Seotud taasavamise õigusega, kuhu see kuulub
Liige nägi uusi õigusi alles pärast lehe uuesti laadimist Andmebaas oli muutunud, leht mitte Õigused värskenevad iga 60 sekundi järel ja akna fookusel
Veeru vaikeväärtus rikkus oma check-piirangut, nii et sisestused ilma selle veeruta ebaõnnestusid Leidis paranduse turvaülevaatus sama töö käigus Vaikeväärtus muudetud väärtuseks, mida piirang lubab

Puuduva vastutaja viga oli see, mis loeb. Kaitsed olid PL/pgSQL-is kirjutatud nii:

if not (has_permission or assignee_id = auth.uid()) then
  raise exception 'forbidden';
end if;

Kui assignee_id on null, on assignee_id = auth.uid() null, false or null on null, not null on endiselt null ja if käsitleb nulli kui väära. Erind ei käivitu kunagi. Tootmises ei olnud ühelgi ülesandel vastutajat määratud, nii et see ei olnud äärejuhtum: see puudutas iga ülesannet, mis jäi ulatusse. Parandus mähib tingimuse coalesce(…, false) sisse, nii et puuduv väärtus keelab, ja see läks välja esimesena, omaette, enne ülejäänud partiid.

Kuidas seda tootmise koopia vastu käitada?

Tõestus skeemi peal, mis tootmisest erineb, ei tõesta tootmise kohta midagi. Testikeskkond töötab kohalikus Supabase’i pinus, eraldi andmebaasis, mis on tootmise skeemi koopia koos ülevaatusel olevate migratsioonidega. Enne iga käivitust võrdleb see mõlema kohaliku andmebaasi sõrmejälge: 799 rida, mis kirjeldavad tabeleid, poliitikaid, funktsioone ja õigusi. Iga erinevus peatab käivituse.

  1. Tootmise skeemKopeeritud ühte usaldusväärsesse masinasse. See ei lähe kunagi hostitud CI-käitajasse.
  2. Kohalik pinu: rakenduse andmebaasSeda teenindavad kohalik API ja auth, seda kasutavad end-to-end kontrollid.
  3. Kohalik pinu: isoleeritud tõestuse andmebaasSama skeem, kasutab ainult maatriksi tõestus.
  4. Sõrmejälje kontroll799 rida andmebaasi kohta. Iga erinevus peatab käivituse enne ühtki sondi.
  5. Sondid käivad iga rolli nimelPäris sisseloginud liikmetena, samu teid pidi, mida kasutab rakendus.
  6. Jälgi ei jääRidade arv lõpus peab võrduma ridade arvuga alguses.
Joonis 3. Tõestus loeb ainult siis, kui andmebaas, millel see käis, vastab tootmisele.

Kaks detaili tegid testikeskkonnast usaldusväärse:

  • Kõigepealt lõhkuge see meelega. Enne kui rohelist käivitust usaldasin, asendasin ühe insert-poliitika sellisega, mis lubab kõike, ja käivitasin tõestuse uuesti. See pidi minema punaseks nende ruutude peal, mida see poliitika valvab. Testikeskkond, mis jääb katkise poliitikaga roheliseks, on dekoratsioon.
  • Kontrollige ka salvestatud olekut. Esimene läbimine alustab iga rolli puhtalt lähtepunktilt. Teine läbimine alustab iga rolli praegusest salvestatud maatriksist, sest ruut võib käituda teisiti nende ruutude kõrval, mille administraator on päriselt märkinud.

Kuidas hoida see pärast väljalaset rohelisena?

Kord käivitatud tõestus vananeb järgmise migratsiooniga. Nüüd käib tõestus enne iga pushi, mis puudutab migratsioone, auth-koodi või maatriksit ennast.

  1. git pushMuudatus puudutab migratsioone, authi või maatriksit.
  2. Pre-push hook: maatriksi tõestusKäib lokaalselt tõestuse andmebaasi vastu. Punane blokeerib pushi.
  3. Push jõuab mainiDeploy käib tavapäraselt.
  4. Isehostitud CI käivitusSama tõestus uuesti masinas, kus on tootmise skeemi koopia.
Joonis 4. Blokeeriv värav on lokaalne, sest tootmise skeemi koopia jääb ühte masinasse.

Tõestus on 135 testi 11 failis, samas projektis on nende kõrval umbes 3 100 tavalist ühiktesti.

Mis ei töötanud?

  • Hostitud CI ei suutnud seda käitada. Tõestus vajab tootmise skeemi koopiat ja ma ei tahtnud seda hostitud käitajasse. Seega on blokeeriv värav pre-push hook ja CI isehostitud masinas on teine kontroll, mida deploy ei oota.
  • Fikseeritud ridade arv läks katki. Esimene “jälgi ei jää” kontroll võrdles fikseeritud arvudega. Järgmine täiesti õigustatud migratsioon lisas ühe õiguse rea ja muutis selle punaseks. Nüüd võrdleb see ridade arvu lõpus ridade arvuga alguses.
  • Vahele jäetud ei ole läbitud. End-to-end kontrollid vajavad kohalikku edge-käituskeskkonda. Kui see maas on, jäetakse need vahele, ja vahelejätmist on lihtne lugeda roheliseks.
  • Koormatud sülearvutis on see aeglane. Rollipõhised läbimised vajasid koormatud masinas pikemaid ajalõppe.
  • See tõestab seda, mida sondid kirjeldavad. Ruut, millele keegi sondi ei kirjutanud, raporteeritakse kattevahena, mitte turvalisena.

Kuidas seda oma Supabase’i rakenduses korrata

  1. Pange kirja iga õigus, mida ekraan lubab kellelgi salvestada: roll × valdkond × toiming. See nimekiri on spetsifikatsioon, mitte teie poliitikad.
  2. Märkige iga ruudu juurde päris toiming, mida see peaks kontrollima, ja kuidas keeldumine välja paistab (ridu pole, viga, 403).
  3. Ehitage andmebaas, mis vastab tootmisele, ja võtke sellest sõrmejälg, et skeemi triiv käivituse peataks.
  4. Käivitage iga toiming sisseloginud liikmena, ruut enne väljas ja siis sees, ning võrrelge.
  5. Lõhkuge üks poliitika meelega ja veenduge, et tõestus läheb punaseks.
  6. Peitke või ühendage tööle iga ruut, mis midagi ei tee, ja tehke tõestusest värav enne pushi.

Kui teie rakenduses on RLS juba olemas, kuid te pole kindel, et see teeb seda, mida seadete ekraan ütleb, töötab sama meetod olemasoleva koodibaasi auditina, mis on projekti päästmise esimene samm. Projektist, mille taga need numbrid on, kirjutasin audiitorfirma ajaarvestuse juhtumis.

Pole kindel, kas teie RLS vastab seadete ekraanile?

Võrdlen hea meelega märkmeid selle kohta, kuidas te seda testite. Ilma müügijututa.

Võtke ühendust