---
title: "NULL PL/pgSQL IF NOT praleido patikrą 3683 užduotims"
description: "SECURITY DEFINER funkcijoje false OR NULL yra NULL, o IF NOT praleidžia RAISE. Kodėl ta pati patikra saugi RLS, coalesce pataisa ir kaip ieškoti."
url: "https://madsalt.tech/lt/notes/plpgsql-if-not-null-fails-open/"
author: "Denys Zaichenko"
language: "lt"
published: 2026-10-18
---

# NULL PL/pgSQL IF NOT praleido patikrą 3683 užduotims

Trumpai

Leidimo patikra `SECURITY DEFINER` funkcijoje buvo `if not (has_perm or created_by = me or assignee_id = me) then raise`. 2026 m. birželį visos 3683 programos užduotys neturėjo nei kūrėjo, nei vykdytojo. `false OR NULL OR NULL` yra `NULL`, `NOT NULL` vis dar `NULL`, o `IF` praleidžia savo kūną, kai sąlyga yra `NULL`. Todėl klaida niekada nebuvo iškelta, ir bet kuris prisijungęs vartotojas galėjo klonuoti užduotis. Pataisa: `coalesce(..., false)` aplink visą sąlygą.

Tai buvo [audito įmonės laiko apskaitos](https://madsalt.tech/lt/work/audit-firm-time-tracking/) programoje, funkcijoje, esančioje už „klonuoti užduotį".

## Kaip atrodė patikra?

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

Ji skamba kaip „atmesk, nebent galite kurti užduotis, ją sukūrėte arba ji jūsų". 3683 užduotys buvo užpildytos automatiškai, todėl `created_by` ir `assignee_id` visose buvo `NULL`. Rolei su išjungtais visais leidimais sąlyga buvo `NOT (false OR NULL OR NULL)`, tai yra `NULL`, ir `raise` niekada nebuvo įvykdytas.

## Kodėl ta pati sąlyga saugi RLS?

Row level security viską, kas nėra `true`, traktuoja kaip atmetimą, todėl `NULL` reiškia ne. PL/pgSQL `IF` viską, kas nėra `true`, traktuoja kaip „praleisti kūną", o čia kūnas buvo būtent atmetimas. PostgreSQL dokumentacija nurodo abi puses, [row security](https://www.postgresql.org/docs/current/ddl-rowsecurity.html) ir [IF](https://www.postgresql.org/docs/current/plpgsql-control-structures.html); kartu jos tą pačią išraišką vienoje vietoje paverčia draudimu, o kitoje leidimu.

![Tas pats NULL, du priešingi atsakymai. has_perm yra false, created_by ir assignee_id yra NULL. RLS politikoje: false OR NULL → NULL → Eilutė atmesta; IF NOT (...) THEN RAISE atveju: false OR NULL → NOT NULL yra NULL → RAISE praleistas → Klonuoti leista.](https://madsalt.tech/charts/lt/plpgsql-if-not-null-fails-open.svg)

*Trijų reikšmių logika, kaip ją aprašo PostgreSQL 18 dokumentacija, skyriai 5.9 (row security) ir 41.6.4 (IF).*

`SECURITY DEFINER` funkcija taip pat veikia apeidama RLS, todėl galioja tik jos pačios patikra. Užtenka vienos `NULL` šakos: visos kitos gali būti `false`.

## Kas tai pataisė?

Apgaubkite visą sąlygą taip, kad `NULL` taptų atmetimu, ir klonavimą traktuokite kaip užduoties kūrimą, o ne šaltinio redagavimą:

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

## Ar tai pasikartojo?

Taip, maždaug po trijų mėnesių, dar trijose funkcijose: užduoties archyvavimas, jos tipo keitimas ir patikra, ar galima joje registruoti laiką. SQL skaitymas jų nepagavo. Pagavo [RLS įrodymas pagal roles](https://madsalt.tech/lt/blog/how-to-test-supabase-rls/), nes jis išjungia kiekvieną leidimą ir tikrina, kad veiksmas atmetamas.

## Kaip ieškoti?

`grep 'if not ('` randa sintaksę, o ne klaidą. Kas pasiteisino:

1. Viena užklausa su `pg_get_functiondef` ištraukite visų funkcijų kūnus.
2. Kiekviename `if not (...) then` ir `if a <> b then` pašalinkite `coalesce(...)`, `exists(...)` ir `(select ...)`. Nuoga `or` grandinė ar palyginimas su stulpeliu, galinčiu būti NULL, yra kandidatas.
3. Taip pat ieškokite `if not <variable> then` ir sekite, kur kintamasis nustatomas. `_ok := has_perm(...) or created_by = me;` paslepia tą pačią klaidą dviejose eilutėse.
4. Kiekvieną pagalbinę funkciją, naudojamą kaip `if not helper(...)`, iškvieskite trimis būdais atšauktoje transakcijoje: nežinomas id, `NULL` argumentas ir tikras vartotojas, kurio laukiamos eilutės nėra. Pagalbinei funkcijai, grąžinančiai `NULL` bet kuriuo iš jų, reikia `coalesce` kvietimo vietoje arba viduje.

Toje programoje patikra aprėpė 14 pagalbinių funkcijų, visos jau buvo saugios. Klaida apsiribojo klonavimo funkcija ir trimis vėliau rastomis. Susijusi daugelio nuomininkų spraga: [ką RLS praleidžia vartotojui dviejose org.](https://madsalt.tech/lt/notes/supabase-rls-user-in-two-organizations/). Programa buvo audito įmonės darbo laiko apskaita; kitos jos tylios klaidos aprašytos straipsnyje [darbo laiko apskaita audito įmonei](https://madsalt.tech/lt/blog/time-tracking-for-audit-firms/). [Supabase saugumo patikra](https://madsalt.tech/lt/tools/supabase-security-check/) tarp savo 46 taisyklių išvardija SECURITY DEFINER funkcijas, bet negali pasakyti, ar jų viduje esantis IF klaidos atveju praleidžia, todėl ši patikra lieka rankinė.
