Pereiti prie turinio

Supabase RLS: kodėl dviejų org. narys mato svetimus duomenis

Supabase

Žiūrėti kaip MarkdownAtidaryti per ClaudeAtidaryti per ChatGPTAtidaryti per Perplexity

Klausimas taip pat nukopijuojamas. Jei pokalbis atsidaro tuščias, įklijuokite jį.

Trumpai

Politika kaip auth_in_org(organization_id) klausia: „ar kviečiantysis yra organizacijos (org.), kuriai priklauso ši eilutė, narys?“. Vartotojui, priklausančiam org. A ir org. B, atsakymas abiem atvejais yra „taip”. Mano zonde vien RLS tokiam vartotojui suteikė skaitymo ir rašymo prieigą prie kitos org. dokumento. Pridėjus prie užklausos jos org., grįžo 0 eilučių.

Tai radau stiprindamas statybos dokumentų platformą, daugelio nuomininkų Supabase programą. Visose nuomininkų lentelėse row level security buvo įjungtas ir priverstas, todėl atrodė, kad viskas baigta.

Kas atrodė saugu?

Visos politikos turėjo tą pačią formą: eilutės organization_id turi būti org., kurios narys yra kviečiantysis. Dėl to kai kurie serverio veiksmai ieškojo dokumento vien pagal id. Argumentas buvo „RLS vis tiek atfiltruos”.

const { data } = await supabase
  .from('documents')
  .select('*')
  .eq('document_id', id)
  .single()

Vartotojui su viena narystė tai veikia, o testas su tokiu vartotoju praeina.

Kodėl RLS praleidžia kitą org.?

Politika yra narystės testas. Postgres vertina ją kiekvienai eilutei, kaip aprašo row security dokumentacija, ir nežino, kurios organizacijos vardu veikia užklausa, nes tai gyvena programoje: URL, slapuke ar org. perjungiklyje. Vartotojui, kuris yra abiejų org. narys, praeina visos abiejų org. eilutės.

Politikoje nėra, kuriai org. priklauso užklausa. Vienas vartotojas, org. A ir org. B narys, dirba org. A. Vien RLS politika: Užklausa org. A → Eilutės org. narys? → Taip, A arba B → Grąžinta org. B eilutė; RLS plius šios užklausos org.: Užklausa org. A → Eilutės org. narys? → Eilutė org. A? → 0 eilučių iš org. B.
Mano zondas su užpildytu vartotoju dviejose org., 2026 m. rugsėjis: vien RLS suteikė skaitymą ir rašymą org. B dokumentui; pridėjus užklausos org., grįžo 0 eilučių.

Taigi programos organization_id filtras nėra antras sluoksnis virš RLS. Šiam vartotojui tai vienintelė kontrolė.

Kol esate prie tų pagalbinių funkcijų, patikrinkite, ar jos žiūri į organizations.is_active. Narystės pagalbinės funkcijos dažnai nežiūri, ir tada išjungta org. RLS lygyje išlaiko skaitymo ir rašymo prieigą. Kai vienintelė is_active patikra yra programos nuomininko nustatymo kode, bet kokia užklausa, apeinanti tą kodą, apeina ir išjungimą.

Kas tai pataisė?

Užklausos org. tapo privalomu argumentu kiekvienoje paslaugos funkcijoje, kuri skaito ar rašo nuomininko duomenis. Kvietimo vietoje to pamiršti neįmanoma, nes be jo funkcija nesikompiliuoja.

export async function getDocument(orgId: string, id: string) {
  return supabase
    .from('documents')
    .select('*')
    .eq('organization_id', orgId)
    .eq('document_id', id)
    .single()
}

Peržiūros metu atsirado dar dvi taisyklės:

  • Niekada neimkite org. iš eilutės, kurią ruošiatės rašyti. update ... where id = $1 and organization_id = <that row's own organization_id> visada teisinga, todėl nieko nesaugo.
  • Sudėtinis išorinis raktas ant (id, organization_id) verčia duomenų bazę atmesti ryšį tarp skirtingų org. eilučių. Patikrinkite kiekvieną antrinę lentelę: lengva pridėti trims ir praleisti ketvirtą.

Kaip patikrinti savo programą?

Sukurkite vieną vartotoją su aktyvia narystė dviejose org. Tada vietinės ar staging kopijos SQL redaktoriuje veikite kaip tas vartotojas ir paprašykite kitos org. eilutės:

begin;
set local role authenticated;
set local request.jwt.claims = '{"sub": "<user id>", "role": "authenticated"}';

-- RLS alone: returns the org B document
select document_id from documents where document_id = '<org B document id>';

-- with the org of the request: 0 rows
select document_id from documents
where document_id = '<org B document id>'
  and organization_id = '<org A id>';
rollback;

Pakartokite su update ir dar kartą, kai antra org. išjungta. Jei pirma užklausa grąžina eilutę, peržiūrėkite kiekvieną vietą, kur nuomininko duomenys užklausiami vien pagal id.

Kiek tai rimta?

Įvertinkite sąžiningai. Užpuolikui reikia tikros narystės abiejose org., todėl tai apie vientisumą ir audito pėdsakus: kas nors, turintis prieigą prie dviejų klientų, pakeičia ne to kliento duomenis, o žurnalas sako, kad tai buvo leista. Tai ne būdas svetimam žmogui perskaityti visus nuomininkus.

Bendra pamoka ta pati, kurią aprašiau straipsnyje kaip testuoju Supabase RLS: testuokite tai, ką gali tikras vartotojas, su tikromis duomenų formomis, o ne tik tai, ką sako kiekviena politika. Trys susijusios pastabos: rolė, paimta iš registracijos metaduomenų, NULL, praleidęs leidimo patikrą ir kurias schemas pateikia Data API. Dviejų įmonių testas yra pirmoji mano Lovable programos saugos sąrašo patikra. Politikoms, teisėms ir nustatymams aplink ją nemokama Supabase saugumo patikra paleidžia 46 OWASP Top 10:2025 taisykles; ar politika praleidžia dviejų org. vartotoją, yra duomenų formos klausimas, į kurį atsako tik aukščiau pateikta užklausa.

© 2026 Denys Zaichenko. Originalus straipsnis: https://madsalt.tech/lt/notes/supabase-rls-user-in-two-organizations/