---
title: "NULL PL/pgSQL-i IF NOT-is jättis 3683 ülesande kontrollita"
description: "SECURITY DEFINER funktsioonis on false OR NULL väärtus NULL ja IF NOT jätab RAISE vahele. Miks see on RLS-is turvaline, coalesce-parandus ja kuidas otsida."
url: "https://madsalt.tech/et/notes/plpgsql-if-not-null-fails-open/"
author: "Denys Zaichenko"
language: "et"
published: 2026-10-18
---

# NULL PL/pgSQL-i IF NOT-is jättis 3683 ülesande kontrollita

Lühidalt

`SECURITY DEFINER` funktsiooni sees oli õiguse kontroll kujul `if not (has_perm or created_by = me or assignee_id = me) then raise`. 2026. aasta juunis ei olnud rakenduse kõigil 3683 ülesandel loojat ega vastutajat. `false OR NULL OR NULL` on `NULL`, `NOT NULL` on endiselt `NULL` ja `IF` jätab oma keha vahele, kui tingimus on `NULL`. Seega ei tõstetud viga kunagi ja iga sisseloginud kasutaja sai ülesandeid kloonida. Parandus on `coalesce(..., false)` kogu tingimuse ümber.

See juhtus [audiitorfirma ajaarvestuse](https://madsalt.tech/et/work/audit-firm-time-tracking/) rakenduses, funktsioonis, mis seisab "kloonimise" taga.

## Milline see kontroll oli?

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

See loeb nagu "keela, kui sa ei tohi ülesandeid luua, ei teinud seda ise ega oma seda". 3683 ülesannet oli automaatselt külvatud, seega olid `created_by` ja `assignee_id` igaühel `NULL`. Rolli puhul, millel kõik õigused olid välja lülitatud, oli tingimus `NOT (false OR NULL OR NULL)`, mis on `NULL`, ja `raise` ei käivitunud kunagi.

## Miks on sama tingimus RLS-is turvaline?

Row level security käsitleb kõike, mis ei ole `true`, keeldumisena, seega `NULL` tähendab ei. PL/pgSQL `IF` käsitleb kõike, mis ei ole `true`, kui "jäta keha vahele", ja siin oli keha just keeldumine. PostgreSQL-i dokumentatsioon ütleb mõlemad pooled välja, [row security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) ja [IF](https://www.postgresql.org/docs/current/plpgsql-control-structures.html) kohta; koos muudavad need sama avaldise ühes kohas keeluks ja teises loaks.

![Sama NULL, kaks vastupidist vastust. has_perm on false, created_by ja assignee_id on NULL. RLS-poliitikas: false OR NULL → NULL → Rida keelati; IF NOT (...) THEN RAISE korral: false OR NULL → NOT NULL on NULL → RAISE jäi vahele → Kloon lubatud.](https://madsalt.tech/charts/et/plpgsql-if-not-null-fails-open.svg)

*Kolmeväärtuseline loogika, nagu seda kirjeldab PostgreSQL 18 dokumentatsioon, jaotised 5.9 (row security) ja 41.6.4 (IF).*

`SECURITY DEFINER` funktsioon töötab ka RLS-ist mööda, seega on selle enda kontroll ainus, mis kehtib. Üks `NULL` haru piisab: ülejäänud harud võivad kõik olla `false`.

## Mis selle parandas?

Mähkige kogu tingimus nii, et `NULL` muutub keeluks, ja käsitlege kloonimist ülesande loomisena, mitte lähteülesande muutmisena:

```sql
if not coalesce((select has_perm('tasks', 'create')), false) then
  raise exception 'forbidden' using errcode = '42501';
end if;
```

## Kas see juhtus uuesti?

Jah, umbes kolm kuud hiljem, veel kolmes funktsioonis: ülesande arhiveerimine, selle tüübi muutmine ja kontroll, kas sellele tohib aega logida. SQL-i lugemine neid ei tabanud. Neid tabas [rollipõhine RLS-i tõestus](https://madsalt.tech/et/blog/how-to-test-supabase-rls/), sest see lülitab iga õiguse välja ja kontrollib, et toiming lükatakse tagasi.

## Kuidas otsida?

`grep 'if not ('` leiab süntaksi, mitte vea. Mis töötas:

1. Tõmmake kõigi funktsioonide kehad välja `pg_get_functiondef`-iga ühe päringuga.
2. Igas `if not (...) then` ja `if a <> b then` eemaldage `coalesce(...)`, `exists(...)` ja `(select ...)`. Paljas `or`-ahel või võrdlus nullitava veeru üle on kandidaat.
3. Sobitage ka `if not <variable> then` ja jälgige, kus muutuja väärtustatakse. `_ok := has_perm(...) or created_by = me;` peidab sama vea kahel real.
4. Kutsuge iga abifunktsiooni, mida kasutatakse kujul `if not helper(...)`, kolmel viisil tagasi keeratud tehingus: tundmatu id, `NULL` argument ja päris kasutaja, kelle oodatav rida puudub. Abifunktsioon, mis tagastab neist mis tahes juhul `NULL`, vajab `coalesce`-i kutsekohas või sees.

Selles rakenduses kontrollis läbivaatus 14 abifunktsiooni, kõik juba turvalised. Viga piirdus kloonimisfunktsiooni ja hiljem leitud kolmega. Seotud mitme rentnikuga lünk: [mida RLS kahe org-i kasutaja puhul kahe silma vahele jätab](https://madsalt.tech/et/notes/supabase-rls-user-in-two-organizations/). Rakendus oli audiitorettevõtte tööaja jälgija; selle muud vaiksed vead on artiklis [tööaja arvestus audiitorettevõttele](https://madsalt.tech/et/blog/time-tracking-for-audit-firms/). [Supabase turvakontroll](https://madsalt.tech/et/tools/supabase-security-check/) loetleb SECURITY DEFINER funktsioonid oma 46 reegli hulgas, kuid ei oska öelda, kas nende sees olev IF jääb vea korral lahti, seega see läbivaatus jääb käsitööks.
