Vai al contenuto
Capitolo 02 · 11 min

Row-Level Security: il buco sotto ogni tabella

Il buco più comune, e più dannoso, nelle app costruite con l'IA è un database che si fida del browser. È la causa profonda della CVE di Lovable e di innumerevoli fughe più silenziose. Il rimedio è una funzionalità chiamata Row-Level Security, e vale la pena capirla con precisione, perché è facile attivarla e sbagliare lo stesso.

Uno schedario in un atrio pubblico con un cartello 'solo personale autorizzato', e nessuna serratura.

checked byseesA userRLS policyOnly their rows
Row-level security lets the database enforce who sees which rows.

Il client non è dalla tua parte

Gli strumenti IA moderni adorano un pattern in cui il browser parla direttamente a un database ospitato come Supabase o Firebase. È veloce da costruire e rende benissimo in demo. Il problema è il modello di sicurezza: il browser viene consegnato con una chiave del database, e il browser è completamente sotto il controllo dell'utente. Chiunque può aprire gli strumenti sviluppatore, copiare quella chiave e interrogare il database direttamente, scavalcando la tua app, le tue schermate e qualsiasi controllo che viveva nel codice front-end.

  browser (qualsiasi utente)   la tua UI dice          database
  ┌───────────────┐           'puoi vedere            ┌─────────────┐
  │ chiave anon ──┼──────────  la tua riga' ────X ───▶│ ogni riga    │
  │ devtools aperti│           (solo front-end)       │ per tutti    │
  └───────────────┘   ◀─────── restituisce TUTTE le righe ─└─────────────┘
Senza regole per riga, il cartello 'solo personale autorizzato' sta nella UI; il database risponde a chiunque abbia la chiave pubblica.

Quindi una regola applicata nel tuo componente React, "mostra solo gli ordini dell'utente corrente", non è un controllo di sicurezza. È una preferenza di visualizzazione. Il database consegnerà volentieri gli stessi dati a una query grezza che non carica mai il tuo componente. La decisione di accesso deve vivere dove l'utente non può arrivare: nel database stesso.

Cos'è davvero la Row-Level Security

La Row-Level Security (RLS) sposta la decisione di accesso dentro il database, riga per riga. Con RLS attiva, il database rifiuta ogni richiesta per impostazione predefinita, e tu scrivi policy che dicono esattamente quali righe un dato utente può vedere o modificare. La policy classica: un utente può leggere una riga solo se la colonna del proprietario corrisponde al suo id autenticato. Ora la query diretta che scavalcava la tua UI non riceve nulla, perché a decidere è il database, non il browser.

sqlCopia il prompt
-- Turn RLS on, THEN add a policy. RLS on with no policy = deny everything.
alter table orders enable row level security;

create policy "owners read their own orders"
  on orders for select
  using ( (select auth.uid()) = user_id );

È tutta qui l'idea: negare per impostazione predefinita, poi concedere in modo ristretto. È lo stesso principio del minimo privilegio applicato ovunque: parti da "nessuno può toccare niente" e apri la porta più piccola che fa funzionare la funzionalità.

Gli errori che fanno trapelare dati anche con RLS 'attiva'

Attivare la RLS è necessario ma non sufficiente. Questi sono i modi in cui le app perdono dati comunque, e uno strumento IA li produrrà tutti con allegria:

  • RLS abilitata su alcune tabelle, dimenticata su altre: la tabella aggiunta la settimana scorsa non ha regole ed è spalancata.
  • Una policy così permissiva da concedere tutto (using (true)), che equivale a nessuna policy con passaggi in più.
  • Consegnare al client la chiave service-role / admin: quella chiave scavalca del tutto la RLS, quindi ogni policy diventa irrilevante.
  • Verificare la proprietà contro un valore inviato dal client, invece dell'id di autenticazione verificato dal server: al client basta inviare un valore diverso.

Come verificare davvero

Non fidarti che sia fatto solo perché l'interruttore è attivo. Verificalo come farebbe un attaccante:

  • Elenca ogni tabella. Conferma che la RLS sia abilitata E che ciascuna abbia una policy reale e circoscritta, non solo abilitata.
  • Interroga come utente anonimo con la chiave pubblica e conferma di non ottenere nulla che non dovresti.
  • Fai login come secondo utente e conferma di non poter leggere né scrivere le righe del primo utente.
  • Cerca nel bundle client e nella config d'ambiente la chiave service-role/admin. Non dovrebbe essere lì.

Sono quindici minuti di lavoro ed è il controllo di sicurezza a più alta leva che puoi eseguire su un'app costruita con l'IA. La CVE di Lovable era, in fondo, la versione su scala industriale dell'averlo saltato.

Una riga per ciascuno

  • I pattern browser-verso-database consegnano una chiave che l'utente controlla; qualsiasi controllo nel front end è decorazione, non sicurezza.
  • La Row-Level Security sposta la decisione nel database, riga per riga: negare per impostazione predefinita, poi concedere in modo ristretto.
  • Con RLS 'attiva' i dati trapelano comunque tramite tabelle dimenticate, policy troppo permissive, una chiave service-role lato client, o id inviati dal client.
  • Verifica come un attaccante: ogni tabella ha una policy reale, l'anonimo non ottiene nulla, un secondo utente è bloccato, nessuna chiave admin nel client.
Row-Level Security: il buco sotto ogni tabella · Cours d'IA · SDEN