← Torna al blog
SicurezzaScanaris Team · 8 min di lettura · October 03, 2026

Lovable e la vulnerabilità RLS (CVE-2025-48757): perché la tua app potrebbe esporre dati

CVE-2025-48757 è una vulnerabilità di Lovable classificata come critica (CVSS 9.3) che ha esposto più di 170 applicazioni. Il problema: chiunque, senza fare login, poteva leggere, e in molti casi modificare, i dati del tuo database: nomi, email, chiavi API di terze parti, perfino lo stato dei pagamenti. La causa di fondo non è un bug nascosto di Lovable ma uno schema condiviso da quasi tutte le app "vibe-coded": affidare tutta la sicurezza del database alle policy RLS di Supabase… e lasciarle incomplete. Questa guida spiega cosa è successo esattamente, come verificare in cinque minuti se la tua app ha lo stesso buco, come chiuderlo con SQL concreto e cosa fare se hai già esposto dei dati.

Cosa è successo davvero

Lovable costruisce app in cui il browser parla direttamente con il database (Supabase) tramite chiamate REST, usando la chiave pubblica anon. È un design solido a patto che ogni tabella abbia policy di Row-Level Security (RLS) che decidono quali righe ogni utente può leggere o toccare. La vulnerabilità è comparsa perché molti progetti sono andati online con tabelle senza RLS o con RLS insufficiente. A un attaccante bastava modificare la richiesta REST, cambiare un filtro nell'URL, per chiedere "tutte le righe" di una tabella che avrebbe dovuto essere protetta.

Per capire perché basta modificare un URL, devi sapere come funziona Supabase sotto il cofano. Davanti al tuo database Postgres c'è uno strato chiamato PostgREST che trasforma ogni tabella in un endpoint HTTP: GET /rest/v1/customers?select=* restituisce righe della tabella customers. La chiave anon non è un segreto: viaggia nel JavaScript della tua app e chiunque può vederla. L'unica cosa che decide quali righe escono da quell'endpoint sono le policy RLS della tabella. Senza, la risposta è l'intera tabella.

È stata segnalata a marzo 2025 da Matt Palmer e Kody Low, e in modo indipendente da Danial Asaria, con divulgazione pubblica il 29 maggio 2025 (analisi tecnica, scheda CVE). Qualsiasi progetto Lovable con un database creato entro il 15 aprile 2025 potrebbe essere coinvolto.

Cronologia in breve

  • Marzo 2025: prime segnalazioni private a Lovable; si trovano app pubblicate con tabelle leggibili senza sessione.
  • 15 aprile 2025: la data limite che Lovable indica per i progetti coinvolti; le app generate dopo includono cambiamenti nel modo in cui vengono create le tabelle.
  • 29 maggio 2025: divulgazione pubblica e assegnazione del CVE con punteggio critico.
  • In seguito: Lovable aggiunge avvisi di sicurezza nell'editor, ma le policy restano responsabilità di chi pubblica l'app.

Perché ti riguarda anche se non usi Lovable

Non è un problema solo di Lovable. Qualsiasi app che parla con Supabase direttamente dal browser, che tu l'abbia costruita con Bolt, v0, Cursor, Replit o a mano, dipende dal fatto che l'RLS sia corretto su ogni tabella. L'app sembra perfetta, il login funziona, ma la vera sicurezza vive nel database, non nell'interfaccia. Nascondere un pulsante nel frontend non protegge nulla: la richiesta REST è sempre lì per chi sa dove guardare.

Lo stesso vale per Firebase con regole in "modalità test", per PocketBase senza regole sulle collection, o per qualsiasi backend-as-a-service che il browser chiama direttamente. Lo schema è sempre lo stesso: l'IA che ha generato la tua app sa come farla funzionare, ma raramente scrive le regole che le impediscono di funzionare per chi non dovrebbe.

Come sapere se la tua app è vulnerabile (5 minuti)

Test 1: la richiesta in incognito

  • Apri i DevTools (F12) → scheda Network mentre usi la tua app e osserva le chiamate a *.supabase.co/rest/v1/. Copia uno di quegli URL per intero (l'apikey viaggia in un header; nell'URL vedrai la tabella e il filtro).
  • Aprilo in una finestra in incognito, senza sessione. Se restituisce dati, li ottiene anche qualsiasi visitatore.
  • Prova a chiedere di più: se una chiamata restituisce i tuoi dati, togli il filtro dall'URL (la parte ?user_id=eq.…). Se consegna le righe di altri utenti, il tuo RLS non è efficace.

Test 2: la dashboard di Supabase

Apri il tuo progetto → Table Editor. Ogni tabella senza RLS ha un badge rosso "RLS disabled". Poi vai su Authentication → Policies: una tabella con RLS attivo ma senza policy nega tutto (bene per i dati, ma la tua app smetterà di funzionare), e una con una policy using (true) è aperta come se l'RLS fosse spento. La dashboard ha anche un Security Advisor (sotto Advisors) che elenca esattamente queste tabelle.

Test 3: una query SQL

Nel SQL Editor della dashboard, questa query elenca le tabelle dello schema public (quello che l'API espone) che non hanno l'RLS attivo:

select c.relname as table_name
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind = 'r'
  and not c.relrowsecurity;

Se restituisce qualche riga con dati reali, quella tabella è aperta attraverso l'API in questo preciso momento.

Come correggere, passo dopo passo

1. Attiva l'RLS su ogni tabella

Non solo su quelle "importanti". La regola è negare per default: senza una policy che lo permetta, nessuno entra. Per ogni tabella dello schema public:

alter table public.customers enable row level security;

Appena lo fai, la tua app smette di vedere i dati finché non scrivi le policy. È normale: la porta ora è chiusa e stai per aprire solo il necessario.

2. Scrivi policy esplicite per operazione

Lo schema più comune è "ogni utente vede e modifica solo le proprie righe", confrontando auth.uid() con la colonna che memorizza il proprietario. Per una tabella customers con una colonna user_id:

create policy "customers: leggi le proprie"
  on public.customers for select
  to authenticated
  using ((select auth.uid()) = user_id);

create policy "customers: inserisci le proprie"
  on public.customers for insert
  to authenticated
  with check ((select auth.uid()) = user_id);

create policy "customers: aggiorna le proprie"
  on public.customers for update
  to authenticated
  using ((select auth.uid()) = user_id)
  with check ((select auth.uid()) = user_id);

create policy "customers: elimina le proprie"
  on public.customers for delete
  to authenticated
  using ((select auth.uid()) = user_id);

Tre dettagli fanno la differenza: to authenticated lascia fuori i visitatori anonimi; with check impedisce a chiunque di inserire o modificare righe a nome di qualcun altro (senza, un utente potrebbe creare dati con lo user_id di un altro); e avvolgere auth.uid() in un select fa sì che Postgres lo valuti una volta per query invece di una volta per riga, il che su tabelle grandi moltiplica le prestazioni. Aggiungi un indice su user_id se non ce l'hai.

3. Non usare mai la chiave service_role nel frontend

Bypassa completamente l'RLS: con lei, ogni policy che hai appena scritto non esiste. Quella chiave vive solo sul server (Edge Functions, il tuo backend, un worker). Se è mai stata nel codice client, trattala come compromessa e ruotala dalla dashboard. Supabase ora offre anche chiavi più nuove (sb_publishable_… per il browser e sb_secret_… per il server) che rendono la separazione più evidente.

4. Riprova

Ripeti il test in incognito: la richiesta senza sessione deve restituire una lista vuota o un errore 401. Poi fai login con due account diversi e verifica che nessuno veda i dati dell'altro. Se hai test automatici, questo è il caso che ne merita uno.

Errori comuni durante la correzione

  • Policy con using (true). Gli assistenti IA le suggeriscono spesso "per farlo funzionare". Lasciano la tabella aperta come se non ci fosse RLS affatto.
  • Dimenticare with check. Protegge le letture ma permette scritture a nome di qualcun altro.
  • Tabelle "catalogo" con dati personali. Una tabella pubblica di profili pensata per mostrare nomi finisce per esporre email o numeri di telefono. Esponi solo le colonne necessarie tramite una vista.
  • Viste che bypassano l'RLS. In Postgres una vista gira con i permessi di chi l'ha creata. Creata dalla dashboard, può restituire righe che le policy della tabella base vieterebbero. Crea le viste con security_invoker = true (Postgres 15 o successivo) o controllale una per una.
  • Storage senza policy. I bucket hanno regole proprie. Un bucket pubblico serve qualsiasi file a chiunque abbia l'URL; i documenti privati vanno in bucket privati con policy per utente.
  • Correggere solo la produzione. Se mantieni un progetto di sviluppo con dati copiati, è esposto allo stesso modo.

Se hai già esposto dei dati

Prima chiudi la porta (i passi sopra). Poi ruota ogni chiave che stava in tabelle leggibili: chiavi API di terze parti, token, webhook. Controlla i log dell'API nella dashboard di Supabase per stimare cosa è stato letto e da quando. Se i dati includono informazioni personali di utenti europei, il GDPR ti obbliga a valutare se l'autorità per la protezione dei dati vada notificata entro 72 ore e, nei casi ad alto rischio, gli utenti stessi. Documenta cosa hai trovato e cosa hai fatto: te lo chiederanno se qualcuno viene a bussare.

Un prompt per correggere con la tua IA

Se lavori con Cursor, Claude o la chat stessa di Lovable, questo prompt di solito dà un buon risultato: "Elenca tutte le tabelle dello schema public del mio progetto Supabase. Per ciascuna, attiva il Row-Level Security e scrivi policy separate per select, insert, update e delete limitate al ruolo authenticated, in modo che ogni utente acceda solo alle righe la cui colonna user_id è uguale a (select auth.uid()). Usa with check su insert e update. Dimmi quali tabelle non hanno una colonna proprietario così decido quelle a mano. Non usare mai using (true)."

Come lo verifica Scanaris

Scanaris scansiona il tuo sito in sola lettura e rileva l'impronta di questo schema: endpoint Supabase raggiungibili dal browser, CORS permissivo, chiavi e token esposti nel tuo JavaScript, e dati che l'app serve senza sessione. Se fornisci una connection string in sola lettura, verifica anche tabella per tabella se l'RLS è attivo e se esistono policy. Incolla il tuo URL e ti dice, in pochi secondi e in ordine di gravità, cosa è in bella vista, prima che lo trovi qualcun altro.

Domande frequenti

Lovable l'ha già corretto?

Lovable ha cambiato il modo in cui genera le tabelle e ha aggiunto avvisi, ma un'app pubblicata prima del 15 aprile 2025 resta come l'hai lasciata: le policy non si aggiungono da sole. E in qualsiasi progetto, nuovo o vecchio, ogni tabella che crei dopo ha bisogno delle sue.

Attivare l'RLS rompe la mia app?

Attivarlo senza policy sì: viene negato tutto. È il comportamento corretto. Scrivi le policy del passo 2 e l'app torna a funzionare, ma solo per chi deve.

Posso proteggere la tabella dal frontend?

No. Il frontend è codice che gira sul computer del visitatore e può essere modificato a piacere. L'unica cosa che non si può aggirare sono le regole applicate dal database.

E se la mia app non ha utenti, solo una tabella di contenuti pubblici?

Allora una policy di lettura pubblica è legittima: for select to anon, authenticated using (true). Quello che non deve esistere è una policy di scrittura aperta o colonne con dati personali in quella tabella.

Come se la cava il tuo sito?

Lascia che Scanaris lo analizzi e verifica in 30 secondi, gratis.

Scansiona il mio sito

Controlli correlati

Guide di sicurezza per stack