---
title: "Sicurezza app Lovable: 12 controlli e 8 che ho aggiunto"
description: "Checklist di sicurezza per app Lovable su Supabase: per ogni controllo la query o il test che lo prova, più 8 falle che le liste di solito perdono."
url: "https://madsalt.tech/it/blog/lovable-app-security-checklist/"
author: "Denys Zaichenko"
language: "it"
published: 2026-10-18
updated: 2026-10-25
---

# Sicurezza app Lovable: 12 controlli e 8 che ho aggiunto

In breve

Un'app Lovable è sicura quanto il suo database Supabase, e "la RLS è attiva" dimostra molto poco. Testi con due utenti in due aziende, tenga i ruoli fuori dai metadati utente, tenga la chiave segreta fuori dal browser e controlli separatamente grant, viste, storage, funzioni e rate limit, perché ognuno aggira le sue policy a modo suo. Qui sotto trova la lista che seguo, il prompt da incollare in Lovable o in Claude e otto controlli che ho aggiunto dopo che l'app Lovable di un cliente mi ha mostrato cosa perdono i primi dodici.

Sono Denys, ingegnere full-stack e di IA. Questo mese ho riparato l'app di un cliente costruita con Lovable. Tutto girava su Supabase, come al solito, e le correzioni di sicurezza erano così tante che l'app sembrava fatta di quelle. Questa è la lista che ora seguo su ogni progetto Lovable o Supabase prima di dire che i dati dei clienti sono protetti.

## Lovable è sicuro? I problemi di sicurezza finora

La piattaforma costruisce e ospita la sua app, ma la sicurezza dell'app dipende soprattutto dal database Supabase che ha dietro, perché l'app legge e scrive su quel database direttamente dal browser. Il registro pubblico mostra entrambi i tipi di problema:

| Quando                           | Cosa è successo                                                                                                                                                                                                                                               | Fonte                                                                                                                                                 |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| Marzo 2025                       | CVE-2025-48757: una scansione di 1.645 app vetrina di Lovable ha trovato 303 endpoint in 170 di esse che servivano dati a chiunque avesse la chiave pubblica, perché la RLS mancava o era troppo larga                                                        | [Divulgazione di Matt Palmer](https://mattpalmer.io/posts/2025/05/statement-on-CVE-2025-48757/), maggio 2025                                          |
| Febbraio 2026                    | Un'app ospitata su Lovable ha esposto 18.697 record di utenti; un controllo di accesso nelle sue funzioni faceva entrare i visitatori anonimi e bloccava gli utenti autenticati. Lovable ha dichiarato che parte del codice non era stata generata da Lovable | [The Register](https://www.theregister.com/software/2026/02/27/ai-built-app-on-lovable-exposed-18k-users-researcher-claims/5038511), 27 febbraio 2026 |
| Dal 3 febbraio al 20 aprile 2026 | Una regressione della piattaforma ha permesso ad altri utenti di Lovable di leggere la cronologia delle chat e il codice sorgente dei progetti pubblici                                                                                                       | [Articolo di Lovable sull'incidente](https://lovable.dev/blog/our-response-to-the-april-2026-incident), 22 aprile 2026                                |

I primi due sono falle a livello di app, del tipo che i controlli qui sotto individuano. Il terzo era di Lovable stessa e Lovable lo ha corretto; la sua parte è tenere le chiavi fuori dalle chat e dal codice (controllo 3) e ruotare quelle che ci sono state. Lovable esegue una scansione di sicurezza prima della pubblicazione, ma agire sul risultato spetta a Lei, e quando è stata pubblicata la CVE quella scansione verificava che esistesse una policy, non che fosse corretta. Quindi "Lovable è sicuro?" si riduce a sapere se il suo database supera i controlli qui sotto.

## Perché "la RLS è attiva" non basta in un'app Lovable?

Un'app Lovable parla con il database direttamente dal browser. In mezzo non c'è un backend suo: la pagina chiama la Data API di Supabase con una chiave pubblica, e Postgres decide cosa torna indietro (quali schemi serva quell'API è un'impostazione a parte, trattata in [Supabase exposed schemas](https://madsalt.tech/it/notes/supabase-exposed-schemas/)). Quindi ogni regola che protegge i dati dei clienti deve vivere nel database, e il database ha più porte.

La row level security è una di queste. I grant decidono se un ruolo può toccare una tabella, le policy decidono quali righe, e tre cose saltano del tutto le policy: la chiave segreta, le viste create senza `security_invoker` e le funzioni `SECURITY DEFINER`. Non è un'ipotesi. La [guida alla sicurezza](https://docs.lovable.dev/tips-tricks/security-best-practices) di Lovable stessa chiede di controllare le policy RLS che imposta, e la [CVE-2025-48757](https://www.upguard.com/blog/everything-everywhere-systemic-data-exposure-in-supabase-apps) qui sopra è ciò che succede quando nessuno lo fa.

![Quattro strade verso i suoi dati. Solo la prima è controllata dalle sue policy RLS. Richiesta normale: Browser → Grant → Policy RLS → Righe proprie; Chiave segreta in una edge function: Browser → Edge function → Chiave segreta → Ogni riga; Vista senza security_invoker: Browser → Vista → Diritti del proprietario → Ogni riga; Funzione SECURITY DEFINER: Browser → RPC → Diritti del proprietario → Ogni riga.](https://madsalt.tech/charts/it/lovable-app-security-checklist-paths.svg)

*Figura 1. Solo il primo percorso è controllato dalle sue policy. Gli altri tre girano con diritti che saltano la RLS. Fonte: documentazione Supabase su RLS e sulla protezione dell'API, ottobre 2026.*

## Il prompt: lo incolli in Lovable o in Claude

Può incollarlo così com'è. Poi controlli il risultato di persona: l'IA ama dire "fatto" quando non lo è, e un prompt che fa sparire un errore può riuscirci allargando l'accesso.

```text
Verifica la sicurezza di questo progetto, correggi ciò che trovi, dimmi cosa hai cambiato:

1. Testa con due utenti in due aziende. L'utente A prova a leggere, modificare, eliminare i record di B per id, tramite API, ricerca ed esportazioni. Tutto deve fallire.
2. RLS su ogni tabella, le policy controllano il proprietario della riga o l'azienda. "Autenticato" non basta.
3. La service role key mai nel frontend. Le edge function che la usano controllano l'utente e filtrano da sole per azienda, la chiave salta la RLS.
4. Ruoli, piano, id azienda mai dai metadati utente, gli utenti possono modificarli. Tienili in una tabella in cui scrive solo il server.
5. Quando una richiesta invia due id (progetto + attività), controlla che l'attività appartenga a quel progetto.
6. Revoca ad anon e authenticated tutti i privilegi sulle tabelle, concedi di nuovo solo ciò che serve alle policy. La RLS non ferma TRUNCATE. Ogni migrazione che crea una tabella include il suo GRANT: dal 30 ottobre 2026 Supabase non concede più automaticamente i grant sulle nuove tabelle, e l'app riceve l'errore 42501.
7. Tutte le viste con security_invoker = true, altrimenti saltano la RLS.
8. Bucket di storage: gli utenti leggono e scrivono solo i propri file, nessun bucket pubblico per dati privati.
9. Funzioni SECURITY DEFINER: NULL deve significare nega, non consenti.
10. Rate limit su login, registrazione, reset, OTP, salvati nel DB, non in memoria.
11. Valida tutto l'input sul server.
12. Esegui il Security Advisor di Supabase, correggi ogni avviso.
```

## I 12 controlli, e come dimostrare ciascuno

### 1. Due utenti in due aziende

Crei l'utente A nell'azienda 1 e l'utente B nell'azienda 2, ciascuno con qualche record. Acceda come A e provi a leggere, modificare ed eliminare i record di B per id, tramite l'API, tramite la ricerca e tramite ogni esportazione. Ogni tentativo deve restituire nulla o un errore. È l'unico test che intercetta la maggior parte di ciò che segue, perché verifica ciò che fa l'attaccante, non ciò che ha scritto lei.

![Due utenti, due aziende. L'utente A dell'azienda 1 punta ai record dell'azienda 2. Ogni percorso deve restituire nulla. Per id tramite l'API: Utente A → id record di B → Leggi, modifica, elimina → 0 righe o errore; Tramite la ricerca: Utente A → Cerca testo di B → Risultati → Nessuna riga di B; Tramite un'esportazione: Utente A → Esportazione → File → Nessuna riga di B.](https://madsalt.tech/charts/it/lovable-app-security-checklist-test.svg)

*Figura 2. Il test delle due aziende. Superato significa che nessun percorso restituisce dati dell'azienda 2.*

Aggiunga un terzo utente che appartiene a entrambe le aziende. Una policy della forma "il chiamante è membro dell'azienda della riga" lascia quell'utente leggere le righe dell'azienda 2 mentre lavora nell'azienda 1. Su un'altra app Supabase la mia sonda con un utente così ha restituito il documento dell'altra azienda, finché la query non ha filtrato anche per l'azienda della richiesta. L'ho descritto in [un utente in due org che la RLS non isolava](https://madsalt.tech/it/notes/supabase-rls-user-in-two-organizations/).

### 2. RLS su ogni tabella, limitata al proprietario o all'azienda

`auth.uid() is not null` significa "qualsiasi utente autenticato", che su una pagina di registrazione pubblica vuol dire chiunque. Ogni policy confronta il proprietario o l'azienda della riga con il chiamante. Elenchi le tabelle che hanno ancora la RLS disattivata:

```sql
select c.relname
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public' and c.relkind in ('r', 'p') and not c.relrowsecurity;
```

### 3. La chiave segreta mai nel frontend

La service role key, e la più recente chiave `sb_secret_`, aggirano ogni policy. Cerchi entrambe nel JavaScript compilato e ruoti qualsiasi chiave sia mai stata pubblicata. Le edge function che la usano devono controllare da sole chi è l'utente e filtrare per azienda, perché con quella chiave nient'altro lo farà.

### 4. Ruoli, piano e id azienda mai dai metadati utente

`raw_user_meta_data` è ciò che il client ha inviato a `signUp`, e gli utenti possono modificarlo in seguito. Su un'altra app il trigger del nuovo utente leggeva da lì il ruolo dell'invito, quindi chiunque si registrasse poteva passare il ruolo owner e diventare Owner. Tenga ruoli, piani e id azienda in una tabella in cui scrive solo il server, oppure in `app_metadata`. La storia completa: [un ruolo preso dai metadati di registrazione](https://madsalt.tech/it/notes/supabase-raw-user-meta-data-role/).

### 5. Due id in una richiesta: controllare che stiano insieme

Quando una richiesta porta un id di progetto e un id di attività, autorizzare il progetto non basta. Un utente con diritti sul progetto A invia A più un id di sezione del progetto B, e la riga finisce in B. La RLS non lo intercetta, perché l'insert registra l'id di progetto dell'attaccante stesso, che è esattamente ciò che la policy controlla. Verifichi che il figlio appartenga al genitore prima di scrivere.

### 6. Grant: revocare, poi concedere di nuovo solo ciò che serve

Grant e policy sono due livelli separati. Aggiungere policy non toglie un grant, per questo la [guida alla RLS](https://supabase.com/docs/guides/database/postgres/row-level-security) di Supabase dice di revocare e concedere di nuovo in modo esplicito. In un altro progetto l'immagine `supabase/postgres` 17.6.1 aveva dato ad `anon` e `authenticated` TRUNCATE, REFERENCES, TRIGGER e MAINTAIN su ogni tabella di `public`, e nessuna policy si applica a TRUNCATE. La Data API non ha un endpoint TRUNCATE, quindi non è una cancellazione con una sola richiesta, ma nessun ruolo client ne ha bisogno, e ogni funzione che esegue SQL con quel ruolo lo eredita.

```sql
revoke all on public.orders from anon, authenticated;
grant select, insert, update on public.orders to authenticated;

select grantee, table_name, privilege_type
from information_schema.role_table_grants
where table_schema = 'public' and grantee in ('anon', 'authenticated')
order by table_name;
```

Una data cambia questo controllo. Supabase [smette di concedere automaticamente i grant sulle nuove tabelle](https://supabase.com/changelog/45329-breaking-change-tables-not-exposed-to-data-and-graphql-api-automatically): per i nuovi progetti dal 30 maggio 2026 e per tutti i progetti esistenti dal 30 ottobre 2026, compresi quelli creati da Lovable. Dopo, una tabella che Lovable o una migrazione crea senza un `GRANT` esplicito è invisibile all'app: la richiesta fallisce con 42501 e la funzione che usa la tabella si rompe. Quindi ogni migrazione che crea una tabella porta ormai i suoi grant, accanto a `enable row level security` e alle policy.

Due cose restano uguali. Le tabelle esistenti mantengono i grant che hanno, quindi revocare ciò che non serve resta compito suo. E la modifica toglie solo select, insert, update e delete: nel progetto Lovable, con i nuovi default, le nuove tabelle arrivavano ancora con TRUNCATE, REFERENCES, TRIGGER e MAINTAIN per `anon` e `authenticated`. Prima revocare tutto, poi concedere, resta l'ordine sicuro.

### 7. Ogni vista con `security_invoker = true`

Una vista gira con i diritti del suo proprietario, e in Supabase il proprietario è di solito `postgres`, quindi una vista su una tabella protetta consegna ogni riga che le policy trattengono. Da Postgres 15 in poi:

```sql
alter view public.customer_summary set (security_invoker = true);
```

### 8. Storage: solo i propri file, nessun bucket pubblico per dati privati

Un bucket pubblico serve qualsiasi file a chiunque abbia l'URL. I file privati vanno in un bucket privato con policy su `storage.objects` che controllano il proprietario o l'azienda, di solito dalla prima cartella del percorso. Dopo aver spostato un progetto, conti le policy di storage attive: su un'app, dopo uno spostamento ogni upload falliva perché la migrazione delle policy non era mai stata eseguita e la tabella aveva la RLS attiva con zero policy.

### 9. Funzioni SECURITY DEFINER: NULL significa nega

In PL/pgSQL, `if not (has_permission or assignee_id = auth.uid())` non solleva nulla quando `assignee_id` è null: l'intera condizione è null e `if` la salta. La stessa espressione è sicura dentro una policy, che tratta null come negazione. Su un'app questo lasciava un ruolo con tutti i permessi disattivati agire su tutte le 3683 attività, perché nessuna aveva un assegnatario. Avvolga la condizione in `coalesce(…, false)`. Dettagli: [un NULL in un IF NOT di PL/pgSQL ha saltato il controllo](https://madsalt.tech/it/notes/plpgsql-if-not-null-fails-open/).

### 10. Rate limit salvati nel database

Limiti login, registrazione, reset della password e OTP, e tenga il contatore in Postgres. Nell'app Lovable i limiti stavano in una variabile dentro l'edge function. Ho sondato il progetto ospitato il 17 ottobre 2026: ogni richiesta girava in un isolate nuovo, quindi il contatore in memoria valeva sempre 1 e il limite non poteva mai scattare. Il `supabase functions serve` locale riusa l'isolate, per questo lo stesso codice passava ogni test in locale.

![Dove vive il contatore decide tutto. Rate limit in una edge function Supabase ospitata. Contatore nella memoria della funzione: Richiesta → Nuovo isolate → Contatore = 1 → Mai limitato; Contatore in Postgres: Richiesta → claim_rate_limit → Riga condivisa → 429 al limite.](https://madsalt.tech/charts/it/lovable-app-security-checklist-ratelimit.svg)

*Figura 3. La mia sonda su una edge function Supabase ospitata, 17 ottobre 2026: ogni richiesta avviava un isolate nuovo.*

La chiave conta quanto l'archivio. Sulla piattaforma ospitata l'ultimo salto di `X-Forwarded-For` è il proxy stesso di Supabase, quindi usarlo come chiave mette tutti i chiamanti nello stesso bucket, e `x-client-ip` lo imposta il client. Nella mia sonda `cf-connecting-ip` portava il vero client. La correzione è stata una funzione `SECURITY DEFINER` `claim_rate_limit(bucket, key, max, window)` che solo la service role può eseguire, serializzata con un advisory lock, che salva un hash dell'IP o dell'id utente invece del valore grezzo.

### 11. Validare tutto l'input sul server

Qualunque cosa controlli il modulo, l'API accetta tutto ciò che il client invia. Lunghezze, formati, enum e proprietà si controllano nel database (constraint, trigger) o nell'edge function, mai solo in React.

### 12. Il Security Advisor di Supabase, ogni avviso

Apra Advisors, poi Security Advisor, e corregga ogni segnalazione. Segnala tabelle con la RLS disattivata, viste che saltano la RLS, policy che leggono i metadati utente, policy permissive `using (true)` e funzioni con un `search_path` modificabile. Non vede i controlli 1, 5, 9 e 10, perché riguardano la logica, non la configurazione.

## Cosa mi ha insegnato l'app Lovable che i 12 controlli perdono?

Queste cose vengono dall'app del cliente e dalle sue revisioni. Nessuna compare in uno scanner.

1. **Campi che il client non deve impostare.** Il client inviava da solo `status: 'approved'`, saltando la moderazione. Una policy UPDATE il cui `with check` è più permissivo del suo `using` permette anche a un utente di spostare una riga a un altro proprietario. Una policy non può calcolare un valore, quindi un trigger `BEFORE INSERT OR UPDATE` ricava di nuovo `status`, ruolo, prezzo e azienda dai dati del server e ignora ciò che il client ha inviato.
2. **Ogni percorso di scrittura, non solo l'insert principale.** Dopo aver corretto l'insert principale, lo stesso aggiramento stava negli allegati, nei commenti, nel ripristino di un elemento eliminato in modo logico e nei contatori. Elenchi ogni scrittore di una colonna protetta prima di correggere il primo.
3. **Allow-list, non deny-list.** I record falsificati hanno richiesto cinque giri di revisione, perché ogni correzione bloccava una combinazione e ne lasciava aperta una sorella. Un trigger che consente solo le combinazioni elencate di tipo e stato ha chiuso l'intera classe in una volta.
4. **Anche le funzioni sono un'API.** Ogni funzione in `public` è richiamabile tramite `/rpc` da qualsiasi ruolo con EXECUTE, e la RLS non si applica alle funzioni. Dia a ogni funzione `SECURITY DEFINER` un `set search_path = ''`, revochi EXECUTE a `public`, `anon` e `authenticated` e lo conceda solo al ruolo che ne ha bisogno.
5. **Revocare EXECUTE può rompere le pagine pubbliche.** Postgres controlla EXECUTE su ogni funzione usata da ogni policy che si applica al ruolo, anche in un ramo che non verrebbe mai eseguito. Quando `anon` ha perso EXECUTE su `is_admin()`, ogni lettura anonima è fallita con 42501. Le policy solo per admin vanno `to authenticated`, e le letture pubbliche hanno una policy propria senza il ramo admin.
6. **Registrazione con conferma email attiva.** Con la conferma attiva, `signUp` non restituisce una sessione, quindi qualsiasi scrittura del profilo subito dopo gira come `anon` e non scrive nulla, in silenzio. I progetti Lovable hanno spesso l'auto-conferma attiva, che nasconde il bug. Passi i campi in `signUp({ options: { data } })`, lasci che il trigger del nuovo utente li validi e li scriva, e tratti quei metadati come non attendibili (controllo 4).
7. **Test che passano per il motivo sbagliato.** Lo stack locale e il progetto ospitato avevano privilegi predefiniti diversi, quindi "anon non può inserire" passava in locale perché mancava il grant, non perché la RLS funzionasse. Un test negativo ha senso solo se esiste il grant positivo. Confronti `pg_default_acl` in locale e in produzione.
8. **Il database live, non i file di migrazione.** Dopo una migrazione o uno spostamento di progetto, confronti ciò che è davvero attivo: `pg_policies`, `pg_get_functiondef` per ogni funzione di controllo e i grant visti sopra. In un'altra app una nuova migrazione ha copiato il corpo di una vecchia funzione e ha annullato in silenzio una correzione successiva.

## Un'app Lovable è conforme al GDPR?

Lovable non rende un'app conforme o non conforme: lo decide ciò che l'app fa con i dati personali, e una revisione di sicurezza non risponde a questo. Queste sono le sei lacune che controllo in un'app Lovable Cloud, ciascuna con la correzione:

1. **Elenchi ogni responsabile del trattamento.** Lovable Cloud esegue database, autenticazione, storage e funzioni su Supabase, Lovable ospita il sito e può inviare le email di accesso, e il Lovable AI Gateway passa il testo a un fornitore di modelli. Aggiunga ogni servizio di email, IA o scraping che le sue funzioni chiamano. Ognuno che riceve dati personali richiede un accordo sul trattamento dei dati (DPA), e uno fuori dall'UE anche un meccanismo di trasferimento. La regione di hosting non compare nel codice, quindi la chieda.
2. **Elimini un utente che ha contenuti.** Le chiavi esterne di ciò che quell'utente ha scritto possono bloccare l'eliminazione, e allora una richiesta di cancellazione non si può completare. Imposti quelle chiavi su cascade o set null, aggiunga un trigger di eliminazione che rimuova anche i log email e i token di accesso dell'utente, e tenga un test di database che elimini un utente così.
3. **Fissi un limite di conservazione.** Log email, tracciamento delle visualizzazioni, code delle email fallite e righe eliminate in modo logico tendono a tenere dati personali senza data di fine. Un job pianificato che elimina definitivamente quelle righe dopo un periodo fisso, insieme ai loro file, chiude la maggior parte del problema.
4. **Tracciamento delle email solo con consenso.** Registrare aperture e clic per destinatario richiede il consenso secondo le regole ePrivacy: un flag di consenso, disattivato di default, con il timestamp impostato dal server, non dal client.
5. **Tenga gli indirizzi fuori dai log delle funzioni.** I log delle edge function non vengono ripuliti, quindi un `console.log` con l'indirizzo di un destinatario lo lascia lì. Registri id e conteggi.
6. **Attivi la conferma email.** Con l'auto-conferma chiunque può registrarsi con un indirizzo che non è suo e portare nell'app l'indirizzo di una persona reale. Il punto 6 dell'elenco sopra copre il lato della registrazione.

Per controllare la sua app: cerchi le chiavi API nelle funzioni per elencare i servizi, elimini un utente di prova con contenuti, cerchi `@` nei log delle funzioni e chieda a ogni fornitore il suo accordo sul trattamento dei dati. Non è una consulenza legale.

## Come evitare che torni?

Ogni controllo qui sopra è una query o un test, quindi li tenga nel repository e li esegua prima di ogni deploy. I [test pgTAP](https://supabase.com/docs/guides/database/extensions/pgtap) di Supabase coprono grant, policy e funzioni; il test delle due aziende va accanto a loro. Se la sua app ha ruoli e una schermata dei permessi, nemmeno un test per policy basta: io dimostro ogni casella di permesso su una copia della produzione, come descritto in [come testare la Supabase RLS](https://madsalt.tech/it/blog/how-to-test-supabase-rls/), dove 30 caselle su 87 non facevano nulla. Il [Controllo sicurezza Supabase](https://madsalt.tech/it/tools/supabase-security-check/) gratuito esegue 46 regole OWASP Top 10:2025 su ruoli, grant, policy e impostazioni di autenticazione di un progetto, quindi lo usi per il primo passaggio; il test delle due aziende e tutto ciò che riguarda la sua interfaccia restano manuali.

Cosa non copre questa lista: pagamenti e webhook, segreti nella cronologia Git, header di sicurezza sull'host, backup. Contano, ma sono un'altra checklist.

Se ha ereditato un'app Lovable o Supabase e non è sicuro di quali di questi controlli superi, questo audit è il punto da cui parte un [recupero di progetto](https://madsalt.tech/it/services/project-rescue/). Il [caso del time tracking per una società di revisione](https://madsalt.tech/it/work/audit-firm-time-tracking/) mostra come appaiono i controlli sui ruoli in un'app in produzione.

Ha trovato una falla che questa lista perde?

Mi piacerebbe saperlo. Mi confronto volentieri su come mettere in sicurezza le app Lovable e Supabase.

[Mi contatti](https://madsalt.tech/it/#contact)
