← Toate cazurile

Cazul 006 · Aplicație făcută cu un AI builder · Acces la date

Reparația AI pentru „permission denied” a deschis toate facturile.

O politică de securitate scrisă de AI a scăpat de eroare lăsând orice utilizator să citească orice factură.

Un monitor întunecat cu un editor de cod: „-- fix permission denied”, „create policy on invoices” și „using (true);” pe un rând roșu.
Înainte
Orice utilizator poate citi toate facturile.
După
Politică pe proprietar; test cu doi utilizatori în CI.

Bug-ul

O aplicație de facturare făcută cu un AI builder își ține datele în Supabase. Utilizatorii care își deschid facturile primesc „permission denied”. Fondatorul lipește eroarea în AI builder, care scrie o nouă politică de row-level security, iar eroarea dispare. Condiția politicii este using (true): orice rând, pentru oricine. În funcție de rolul pe care se aplică, asta înseamnă orice utilizator logat sau oricine are cheia publică a aplicației.

Cum se vede

Arată ca o reparație. Facturile se încarcă, eroarea a dispărut, fondatorul merge mai departe. În aplicație, fiecare utilizator vede tot doar facturile lui, pentru că doar pe acelea le cer ecranele. Cine interoghează baza de date direct, cu cheia pe care browserul o are deja, le primește pe ale tuturor. În istoricul proiectului, schimbarea apare ca bug fix.

Cum îl găsește un test

Creezi doi utilizatori de test, fiecare cu o factură. Te loghezi ca A și ceri facturile prin API-ul public, cum face browserul, dar fără filtrul aplicației. Numeri rândurile care îi aparțin lui B. Orice număr peste zero înseamnă test picat. O a doua verificare citește lista de politici din baza de date și semnalează orice politică pe un tabel cu date de utilizator a cărei condiție e pur și simplu true.

Reparația

Politica se înlocuiește cu una limitată la proprietar: auth.uid() = user_id. Apoi se caută cauza reală a erorii inițiale. De obicei e un rând inserat fără ID de utilizator, pe care politica pe proprietar nu avea cum să-l potrivească.

Testul care îl prinde

Un test cu doi utilizatori: A cere facturile lui B prin API-ul public, cu cheia anon și token-ul lui A, și așteaptă zero rânduri. O interogare în CI pe pg_policies oprește build-ul dacă vreo politică de pe un tabel cu date de utilizator are condiția doar true.

invoices-rls.spec.tsIlustrativ
import { test, expect } from "@playwright/test";
import { anonClientSignedInAs, seed, sql } from "./db";

test("user A reads none of user B's invoices", async () => {
  const supabase = await anonClientSignedInAs(seed.userA);
  const { data } = await supabase
    .from("invoices")
    .select("id")
    .eq("user_id", seed.userB.id);
  expect(data ?? []).toHaveLength(0);
});

test("no policy on a user-data table is just `true`", async () => {
  const open = await sql`
    select tablename, policyname from pg_policies
    where schemaname = 'public' and qual = 'true'`;
  expect(open).toEqual([]);
});

Ce pune în pericol

Facturile tuturor clienților, cu nume, adrese și sume, la îndemâna oricărui utilizator al aplicației. E o încălcare GDPR, iar în aplicație nu se vede nimic.

Spune-ne ce lansezi.

O aplicație nouă sau rescrisă, un agent AI, o automatizare sau o versiune care trebuie testată. Un apel gratuit de 30 de minute, apoi un preț în scris. Dacă încă nu ai nevoie de noi, îți spunem.

Programează un apel gratuit