Advisorii de securitate din Supabase, înainte de lansare
O bază de date care merge pe laptop poate fi accesibilă oricui după lansare. Linter-ul Supabase arată tabelele, politicile și cheile care permit asta.

Orice proiect Supabase găzduit este o bază de date Postgres cu un API REST în față. Pe laptop ajungi la baza de date printr-o conexiune pe care o ai doar tu. În producție, aceleași tabele răspund la cereri HTTP venite de la oricine are URL-ul proiectului și cheia publishable care stă în bundle-ul JavaScript. Row-level security este singurul lucru care desparte cele două situații, iar linter-ul bazei de date este cel mai ieftin mod de a găsi tabelele în care lipsește.
Ce se schimbă când proiectul nu mai e local
supabase start pornește tot stack-ul pe mașina ta: Postgres, Auth, Storage și celelalte funcții Supabase, cu Studio la http://localhost:54323. Există ca să experimentezi „fără să afectezi mediul de producție" (ghidul de dezvoltare locală). Nimic din el nu dovedește însă că regulile de acces sunt corecte, pentru că local ești singurul care face cereri.
Într-un proiect găzduit, Data API este un endpoint public. Ghidul de row-level security spune direct ce înseamnă asta pentru un tabel fără reguli active: „A table in an exposed schema without RLS is readable and writable by any role with a grant on it." Nici grant-urile nu te salvează: la proiectele existente, „a new table in public starts with every privilege already granted to all three roles", iar „adding policies doesn't take those grants back". Grant-urile decid ce este accesibil. RLS decide cine vede un rând.
Lipsa apare într-un singur loc. Potrivit paginii Securing your API, tabelele create din dashboard au RLS activat implicit, iar cele create în SQL Editor sau prin alt instrument trebuie să îl activezi explicit. O migration este SQL simplu, deci un proiect a cărui schemă e construită din migrations poate ajunge în producție cu toate tabelele deschise, fără ca vreun test local să observe.
Ce verifică linter-ul bazei de date
Supabase numește aceste verificări Advisors: „deterministic checks that ship with the platform" (database linter). Sunt împărțite în Health, Security și Performance, iar fiecare verificare are un număr, un nume, un titlu și un nivel. Cele de securitate pe care le vei întâlni cel mai probabil:
0013_rls_disabled_in_public, „Table publicly accessible", ERROR: niciun RLS pe un tabel din schema public, deci oricine are URL-ul proiectului poate vedea, modifica și șterge fiecare rând.0023_sensitive_columns_exposed, „Sensitive data publicly accessible", ERROR: API-ul ajunge la un tabel cu secrete probabile sau identificatori personali, iar nimic nu restricționează accesul.0015_rls_references_user_metadata, „Security policy relies on user-editable data", ERROR: politica se potrivește peuser_metadata, pe care utilizatorii îl pot edita, deci pot ocoli regula.0010_security_definer_view, „View bypasses row-level security", ERROR: o vizualizare publică rulează cu drepturile proprietarului, sare peste RLS și poate întoarce rânduri pe care apelantul nu ar trebui să le vadă.0002_auth_users_exposed, „User data exposed through a view", ERROR: o vizualizare pe tabelul de utilizatori din auth dă date personale oricui are acces la API.0024_permissive_rls_policy, „Security policy allows unrestricted access", WARN: o condiție mereu adevărată anulează protecția pe care RLS ar fi trebuit să o ofere.0007_policy_exists_rls_disabled, „Security policy not enforced", INFO: o politică este definită, dar inactivă, pentru că RLS e oprit pe tabel.0008_rls_enabled_no_policy, „No access rules defined", INFO: RLS e pornit și nu există nicio politică, deci API-ul nu poate citi sau scrie deloc tabelul.
Lista e mai lungă: există verificări pentru politici lente, pentru funcții SECURITY DEFINER apelabile fără autentificare și pentru obiecte din afara row-level security, precum vizualizările materializate. Le-am listat pe cele care decid dacă primul deploy public scurge date.
Un exemplu concret: tabelul reports
O migration creează public.reports cu o coloană user_id. Local inserezi câteva rânduri și totul merge. După deploy apare 0013, iar constatarea e corectă: tabelul poate fi citit și scris de oricine are URL-ul și cheia publishable.
Remediul din ghidul RLS este o singură instrucțiune:
alter table public.reports enable row level security;
Doar atât schimbă felul în care se strică lucrurile, nu le repară. După ce RLS e activ și nu există nicio politică, nimic nu mai e accesibil prin API cu o cheie publishable, exact ce raportează 0008. Aplicația nu mai întoarce datele tuturor, dar nu mai întoarce nici datele tale.
A doua jumătate este o politică. Iată exemplul din ghid, scris pentru un tabel todos; aceeași formă se aplică și la reports, după ce schimbi numele:
create policy "Individuals can view their own todos." on todos for select to authenticated using ( (select auth.uid()) = user_id );
Două detalii contează. Politica acoperă doar select, deci un client care inserează are nevoie de o politică separată pentru insert. Iar forma (select auth.uid()) evită reevaluarea pe rând pe care o raportează 0003: ca apel simplu de funcție, verificarea se poate relua pentru fiecare rând, ceea ce costă la tabele mari.
Error, warning și info: ce spune nivelul
Pagina Advisors etichetează fiecare verificare cu ERROR, WARN sau INFO, dar nu definește cele trei cuvinte. CLI-ul le dă un sens mecanic: --level stabilește nivelul minim afișat, iar --fail-on stabilește nivelul la care comanda se încheie cu cod non-zero. Citește-le ca pe o ordonare după cât de tare strigă constatarea, nu ca pe o afirmație despre ce se strică.
În practică le tratăm așa. Un ERROR este o cale către date care nu ar trebui să existe: rânduri citibile de un apelant anonim, o vizualizare sau o coadă în afara RLS, o politică ce se bazează pe un câmp controlat de utilizator. Se repară înainte de lansare. Un WARN este o configurație care probabil nu e ce ai vrut: politici nerestrictive sau suprapuse, ceva ce nu va scala. INFO este curățenie sau un bug funcțional, iar cele două constatări INFO de mai sus sunt cele mai clare exemple: 0007 înseamnă că o politică scrisă de tine nu face nimic, iar 0008 înseamnă că API-ul nu ajunge la nimic.
0024 merită o propoziție separată. Este doar WARN și este constatarea care anulează în liniște tot exercițiul: o politică a cărei condiție e mereu adevărată, de exemplu using (true), dă fiecărui apelant același acces ca absența oricărei politici, în timp ce tabelul arată protejat în dashboard.
Ce cheie stă în browser
Supabase emite acum două tipuri de chei, iar ele sunt „short strings, not JWTs" (API keys). Cheia publishable, cu prefixul sb_publishable_, este documentată ca „Safe to expose online: web page, mobile or desktop app, GitHub actions, CLIs, source code". Cheia secretă, cu prefixul sb_secret_, este pentru „backend components of your app, such as servers, APIs with their own authorization checks". Aceeași pagină spune „Never put one in a browser, a shipped application, or source control" și „A secret key bypasses every Row Level Security policy you have".
Ultima propoziție este tot designul. Fiecare tabel la care ajunge cheia publishable este protejat doar de politici, pentru că cheia este publică prin definiție: „Anyone can read it, so it only reaches what Row Level Security allows." Politicile sunt granița de securitate.
Numele vechi încă funcționează: anon este cheia publishable veche, iar service_role cheia secretă veche, și Supabase le depreciază pe ambele până la sfârșitul lui 2026. Politicile se scriu pentru rolurile anon și authenticated, în timp ce service_role are BYPASSRLS și le sare peste.
Două reguli practice din aceeași pagină. Ține cheile în afara controlului de versiune, inclusiv din scripturile de CI, și folosește stocarea de secrete a platformei. Și nu da unei chei de server un prefix pe care bundler-ul îl tratează ca public, „or your bundler will ship the key".
O nuanță e ușor de ratat: o cheie secretă ocolește RLS doar când cererea nu poartă niciun token de acces al utilizatorului, deci codul de server care acționează în numele unui utilizator autentificat trece tot prin politicile acelui utilizator.
Cum rulezi verificările
Există trei căi: paginile Security și Performance din Studio, unealta MCP get_advisors sau Management API și CLI-ul. Pagina linter-ului documentează comanda ca supabase db advisors. Ieșirea de ajutor din CLI 2.100.1 o descrie ca „Checks database for security and performance issues" și oferă --local, --linked sau --db-url pentru țintă, --type cu all, security sau performance, --level cu info, warn sau error și --fail-on cu none, info, warn sau error.
O rulare pe proiectul legat, înainte să publici ceva:
supabase db advisors --linked --type security --level info
Iar în CI, aceeași comandă cu --fail-on error oprește un deploy la o constatare care expune date; mută pragul la warn când echipa citește într-adevăr warning-urile.
Rulează și local, cu --local, pe un stack pornit de supabase start. Tabelele locale vin din aceleași migrations ca producția, deci o rulare locală curată este un proxy decent, cu rezerva de mai sus.
Cunoaște limitele. Linter-ul citește configurația, nu intenția. Poate spune că o politică e nerestrictivă; nu poate spune că using (auth.uid() = user_id) e greșită pentru că user_id acceptă NULL sau că politica de insert îi permite unui utilizator să scrie id-ul altcuiva. Tratează un raport curat ca pe un minim, apoi citește fiecare politică ca pe o propoziție despre cine poate face ce.
Checklist înainte de lansare
- RLS este activ pe fiecare tabel și vizualizare expusă prin Data API, inclusiv pe tot ce creează migrations, nu doar pe ce creează dashboard-ul.
- Fiecare politică scrisă de tine stă pe un tabel unde RLS este într-adevăr activ (
0007). - Nicio condiție de politică nu e mereu adevărată (
0024); unde este intenționată, notează de ce. - Politicile se potrivesc pe roluri și pe coloane din rând, niciodată pe
user_metadata(0015). - Vizualizările și funcțiile
SECURITY DEFINERsunt revizuite, iarexecutese acordă doar rolurilor care au nevoie, pentru că RLS nu se aplică în funcții. - Cheia secretă există doar în variabile de mediu pe server și în stocarea de secrete a CI; browserul vede cheia publishable și nimic altceva.
supabase db advisors --linked --type securitye curat laerror, iar fiecare warning a fost citit de o persoană.- Se citesc și constatările info:
0008înseamnă că un tabel e inaccesibil, nu că e sigur. - Backup-urile sunt rezolvate: pe planul Free nu pot fi descărcate, iar PITR este opțiunea când contează punctul de recuperare (checklist-ul de producție).
Unde se leagă produsul
Supabase Local Manager rulează mai multe stack-uri Supabase locale dintr-un singur serviciu. CLI-ul Supabase open source se ocupă de administrarea de bază a bazei de date, iar produsul adaugă celelalte servicii, API-urile și interfața, folosind Docker. Dashboard-ul lui include o zonă de advisors de securitate și performanță care afișează constatările cu severitate, îndrumare și opțiunea de a le respinge. Când ții mai multe proiecte pe aceeași mașină, deschiderea fiecărui Studio este pasul care se sare, așa că advisorii lângă tabele, SQL și migrations fac diferența dintre a verifica și a nu verifica. Este un proiect independent, neafiliat, neaprobat și nesponsorizat de Supabase, Inc., aflat în early access: codul sursă este privat, iar accesul se cere prin GitHub. Vezi supabasedatabasemanager.danubelabs.net.
Ce facem noi
Tratăm advisorii ca pe o poartă înainte de deploy: CLI-ul în CI cu --fail-on error și un om care citește warning-urile. Dacă ești pe punctul să publici un proiect și vrei ca regulile de acces să fie verificate de cineva care nu le-a scris, vezi proiectele noastre de software sau programează un apel introductiv gratuit. Iar dacă site-ul tău nu are nevoie deloc de date de utilizator, un setup fără bază de date, ca cel din articolul despre CMS-ul bazat pe Git, elimină întrebarea cu totul.
Întrebări frecvente
- Ce este linter-ul de bază de date din Supabase?
- Supabase numește aceste verificări Advisors: verificări deterministe care vin cu platforma. Sunt împărțite în Health, Security și Performance, iar fiecare constatare are un număr, un nume și un nivel. Le poți citi în Studio, prin unealta MCP get_advisors sau prin Management API, ori din CLI cu supabase db advisors.
- Care e diferența dintre cheia publishable și cheia secretă?
- Cheia publishable (sb_publishable_) este făcută să fie publică: Supabase o documentează ca sigură de expus în pagini web, aplicații, CLI-uri și cod sursă, iar ea ajunge doar la ce permite row-level security. Cheia secretă (sb_secret_) stă doar pe server, ocolește toate politicile RLS și nu trebuie să apară niciodată într-un browser, într-o aplicație livrată sau în controlul de versiune. Cheile vechi anon și service_role sunt versiunile anterioare ale acestor două.
- Ce înseamnă rls_disabled_in_public?
- Este verificarea 0013, de nivel ERROR: un tabel din schema public nu are RLS, deci oricine are URL-ul proiectului poate vedea, modifica și șterge fiecare rând. Activează-l cu alter table public.tabelul_tău enable row level security, apoi adaugă politicile după care API-ul trebuie să funcționeze. RLS fără nicio politică face tabelul inaccesibil, iar linter-ul raportează asta separat, ca 0008.
- Pot ignora o constatare de nivel warning?
- Nu automat. Un warning înseamnă că probabil configurația nu e ce ai vrut, iar unele contează mult: 0024 semnalează o condiție de politică mereu adevărată, care dă fiecărui apelant același acces ca absența oricărei politici. Nivelurile sunt o ordonare după cât de tare strigă constatarea, nu o promisiune despre impact, așa că citește fiecare warning și decide. Constatările de nivel error sunt cele care se repară înainte de lansare.
- Cum rulez advisorii Supabase din CLI?
- Rulează supabase db advisors cu o țintă: --local pentru stack-ul pornit de supabase start, --linked pentru proiectul legat sau --db-url pentru un connection string. Adaugă --type security ca să limitezi verificările la securitate, --level pentru nivelul minim afișat și --fail-on error ca comanda să se încheie cu cod non-zero când găsește o eroare, ceea ce o face utilizabilă ca poartă în CI.
- De ce are tabelul meu RLS local, dar nu în producție?
- Pentru că cele două moduri de a crea un tabel se comportă diferit. Supabase activează RLS implicit pe tabelele create din dashboard, iar cele create în SQL Editor sau prin alt instrument trebuie activate explicit. O migration este SQL simplu, deci o schemă construită integral din migrations poate ajunge în producție cu toate tabelele deschise, în timp ce testele locale trec.


