---
title: "Supabase RLS: kodėl dviejų org. narys mato svetimus duomenis"
description: "Politika, tikrinanti narystę organizacijoje, galioja abiejose org. vartotojui, kuris priklauso dviem. Ką rado mano zondas, pataisa ir dviejų minučių patikra."
url: "https://madsalt.tech/lt/notes/supabase-rls-user-in-two-organizations/"
author: "Denys Zaichenko"
language: "lt"
published: 2026-10-18
---

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

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ą](https://madsalt.tech/lt/work/construction-document-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".

```ts
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](https://www.postgresql.org/docs/current/ddl-rowsecurity.html), 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.](https://madsalt.tech/charts/lt/supabase-rls-user-in-two-organizations.svg)

*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.

```ts
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:

```sql
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](https://madsalt.tech/lt/blog/how-to-test-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ų](https://madsalt.tech/lt/notes/supabase-raw-user-meta-data-role/), [NULL, praleidęs leidimo patikrą](https://madsalt.tech/lt/notes/plpgsql-if-not-null-fails-open/) ir [kurias schemas pateikia Data API](https://madsalt.tech/lt/notes/supabase-exposed-schemas/). Dviejų įmonių testas yra pirmoji mano [Lovable programos saugos sąrašo](https://madsalt.tech/lt/blog/lovable-app-security-checklist/) patikra. Politikoms, teisėms ir nustatymams aplink ją nemokama [Supabase saugumo patikra](https://madsalt.tech/lt/tools/supabase-security-check/) 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.
