---
title: "Supabase RLS: kahden organisaation käyttäjä vuotaa dataa"
description: "RLS-politiikka, joka tarkistaa jäsenyyden, on tosi kummassakin organisaatiossa kahteen kuuluvalle käyttäjälle. Löydökseni, korjaus ja 2 minuutin tarkistus."
url: "https://madsalt.tech/fi/notes/supabase-rls-user-in-two-organizations/"
author: "Denys Zaichenko"
language: "fi"
published: 2026-10-18
---

# Supabase RLS: kahden organisaation käyttäjä vuotaa dataa

TL;DR

Politiikka kuten `auth_in_org(organization_id)` kysyy "onko kutsuja sen organisaation jäsen, joka omistaa tämän rivin?". Käyttäjälle, joka kuuluu organisaatioon A ja organisaatioon B, vastaus on kyllä molemmissa. Koettimessani pelkkä RLS antoi tällaiselle käyttäjälle luku- ja kirjoitusoikeuden toisen organisaation dokumenttiin. Kun lisäsin kyselyyn nykyisen pyynnön organisaation, tulokseksi tuli 0 riviä.

Löysin tämän koventaessani [rakennusdokumenttialustaa](https://madsalt.tech/fi/work/construction-document-platform/), monivuokraajaista Supabase-sovellusta. Jokaisessa vuokralaistaulussa row level security oli päällä ja pakotettu, joten kaikki näytti valmiilta.

## Mikä näytti turvalliselta?

Jokaisella politiikalla oli sama muoto: rivin `organization_id` on oltava organisaatio, jonka jäsen kutsuja on. Siksi muutama server action haki dokumentin pelkällä id:llä. Päättely oli "RLS suodattaa sen joka tapauksessa".

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

Käyttäjällä, jolla on yksi jäsenyys, tämä toimii, ja testi tällaisella käyttäjällä menee läpi.

## Miksi RLS päästää toisen organisaation läpi?

Politiikka on jäsenyystesti. Postgres arvioi sen jokaiselle riville, kuten [row security -dokumentaatio](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) kuvaa, eikä se tiedä, minkä organisaation nimissä pyyntö toimii, koska se asuu sovelluksessa: URL:ssä, evästeessä tai organisaatiovalitsimessa. Käyttäjälle, joka on molempien organisaatioiden jäsen, kummankin organisaation jokainen rivi pääsee läpi.

![Politiikassa ei lue, mihin organisaatioon pyyntö kuuluu. Yksi käyttäjä, organisaatioiden A ja B jäsen, toimii organisaatiossa A. Pelkkä RLS-politiikka: Pyyntö org A:ssa → Rivin org:n jäsen? → Kyllä, A tai B → Org B:n rivi palautuu; RLS ja pyynnön organisaatio: Pyyntö org A:ssa → Rivin org:n jäsen? → Rivi org A:ssa? → 0 riviä org B:stä.](https://madsalt.tech/charts/fi/supabase-rls-user-in-two-organizations.svg)

*Koettimeni kahden organisaation seedatulla käyttäjällä, syyskuu 2026: pelkkä RLS antoi luku- ja kirjoitusoikeuden organisaation B dokumenttiin; pyynnön organisaation lisääminen palautti 0 riviä.*

Sovelluksen `organization_id`-suodatin ei siis ole toinen kerros RLS:n päällä. Tälle käyttäjälle se on ainoa kontrolli.

Kun olet noissa apufunktioissa, tarkista, katsovatko ne `organizations.is_active`. Jäsenyysapurit eivät usein katso, ja silloin deaktivoitu organisaatio säilyttää luku- ja kirjoitusoikeuden RLS-tasolla. Kun ainoa `is_active`-tarkistus on sovelluksen tenant-resolverissa, jokainen kysely, joka ohittaa resolverin, ohittaa myös deaktivoinnin.

## Mikä korjasi sen?

Pyynnön organisaatiosta tuli pakollinen argumentti jokaiseen palvelufunktioon, joka lukee tai kirjoittaa vuokralaisdataa. Kutsukohta ei voi unohtaa sitä, koska funktio ei käänny ilman sitä.

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

Katselmoinnista tuli kaksi sääntöä lisää:

- Älä koskaan ota organisaatiota riviltä, jota olet kirjoittamassa. `update ... where id = $1 and organization_id = <that row's own organization_id>` on aina tosi, joten se ei suojaa mitään.
- Yhdistetty vierasavain `(id, organization_id)` saa tietokannan kieltäytymään linkistä eri organisaatioiden rivien välillä. Tarkista jokainen lapsitaulu: sen lisää helposti kolmeen ja unohtaa neljännen.

## Miten tarkistat oman sovelluksesi?

Luo yksi käyttäjä, jolla on aktiivinen jäsenyys kahdessa organisaatiossa. Toimi sitten tuona käyttäjänä paikallisen tai staging-kopion SQL-editorissa ja pyydä toisen organisaation rivi:

```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;
```

Toista `update`-lauseella ja vielä kerran sen jälkeen, kun olet asettanut toisen organisaation epäaktiiviseksi. Jos ensimmäinen kysely palauttaa rivin, katso jokainen paikka, joka hakee vuokralaisdataa pelkällä id:llä.

## Kuinka paha tämä on?

Arvioi mittakaava rehellisesti. Hyökkääjä tarvitsee aidon jäsenyyden molemmissa organisaatioissa, joten kyse on eheydestä ja auditointilokeista: joku, jolla on pääsy kahteen asiakkaaseen, muokkaa väärää, ja loki sanoo sen olleen sallittua. Tämä ei ole tapa, jolla ulkopuolinen lukisi kaikki vuokralaiset.

Yleinen opetus on sama kuin tekstissä [miten testaan Supabase RLS:ää](https://madsalt.tech/fi/blog/how-to-test-supabase-rls/): testaa, mitä oikea käyttäjä voi tehdä, oikeilla datan muodoilla, älä vain sitä, mitä kukin politiikka sanoo. Kolme aiheeseen liittyvää muistiinpanoa: [rekisteröitymismetadatasta otettu rooli](https://madsalt.tech/fi/notes/supabase-raw-user-meta-data-role/), [NULL, joka ohitti oikeustarkistuksen](https://madsalt.tech/fi/notes/plpgsql-if-not-null-fails-open/) ja [mitkä skeemat Data API tarjoaa](https://madsalt.tech/fi/notes/supabase-exposed-schemas/). Kahden yrityksen testi on [Lovable-sovelluksen tietoturvatarkistuslistani](https://madsalt.tech/fi/blog/lovable-app-security-checklist/) ensimmäinen kohta. Politiikkojen, oikeuksien ja asetusten osalta ilmainen [Supabase tietoturvatarkistus](https://madsalt.tech/fi/tools/supabase-security-check/) ajaa 46 OWASP Top 10:2025 -sääntöä; päästääkö politiikka kahden organisaation käyttäjän läpi, on datan muotoon liittyvä kysymys, johon vastaa vain yllä oleva kysely.
