Hoppa till innehållet

Lovable-app säkerhet: 12 kontroller, och 8 jag la till

Uppdaterad

Visa som MarkdownÖppna i ClaudeÖppna i ChatGPTÖppna i Perplexity

Frågan kopieras också. Om chatten öppnas tom, klistra in den.

Kort sagt

En Lovable-app är bara så säker som sin Supabase-databas, och “RLS är på” bevisar mycket lite. Testa med två användare i två företag, håll roller utanför användarmetadata, håll den hemliga nyckeln utanför webbläsaren och kontrollera behörigheter, vyer, storage, funktioner och rate limits var för sig, eftersom var och en går förbi dina policyer på sitt eget sätt. Nedan finns listan jag kör, prompten du kan klistra in i Lovable eller Claude och åtta kontroller jag lade till efter att en kunds Lovable-app visade mig vad de första tolv missar.

Jag heter Denys och är full-stack- och AI-ingenjör. Den här månaden rättade jag till en kunds app byggd med Lovable. Allt körde på Supabase, som vanligt, och det fanns så många säkerhetsåtgärder att det kändes som om appen var gjord av dem. Det här är listan jag nu går igenom på varje Lovable- eller Supabase-projekt innan jag kallar kunddata skyddad.

Är Lovable säkert? Säkerhetsproblemen hittills

Plattformen bygger och hostar din app, men om appen är säker beror mest på Supabase-databasen bakom den, eftersom appen läser och skriver i databasen direkt från webbläsaren. Den offentliga historiken visar båda sorterna av problem:

När Vad som hände Källa
Mars 2025 CVE-2025-48757: en skanning av 1 645 Lovable-showcase-appar hittade 303 endpoints i 170 av dem som levererade data till vem som helst med den offentliga nyckeln, eftersom RLS saknades eller var för lös Matt Palmers redogörelse, maj 2025
Februari 2026 En Lovable-hostad app exponerade 18 697 användarposter; en åtkomstkontroll i dess funktioner släppte in anonyma besökare och stängde ute inloggade användare. Lovable sa att en del av koden inte var genererad av Lovable The Register, 27 februari 2026
3 februari – 20 april 2026 En regression i plattformen lät andra Lovable-användare läsa chatthistoriken och källkoden för publika projekt Lovables incidentinlägg, 22 april 2026

De två första är hål på appnivå, sådana som kontrollerna nedan fångar. Den tredje var Lovables egen, och Lovable åtgärdade den; din del är att hålla nycklar borta från chattar och kod (kontroll 3) och byta ut alla som har legat där. Lovable kör en säkerhetsskanning före publicering, men det är upp till dig att agera på den, och när CVE:n publicerades kontrollerade skanningen att en policy fanns, inte att den var rätt. Så “är Lovable säkert” handlar om huruvida din databas klarar kontrollerna nedan.

Varför räcker inte “RLS är på” i en Lovable-app?

En Lovable-app pratar med databasen direkt från webbläsaren. Det finns ingen egen backend emellan: sidan anropar Supabase Data API med en offentlig nyckel, och Postgres avgör vad som kommer tillbaka (vilka scheman det API:et serverar är en egen inställning, som Supabase exposed schemas går igenom). Så varje regel som skyddar kunddata måste ligga i databasen, och databasen har flera dörrar.

Row level security är en av dem. Behörigheter (grants) avgör om en roll över huvud taget får röra en tabell, policyer avgör vilka rader, och tre saker hoppar över policyer helt: den hemliga nyckeln, vyer skapade utan security_invoker och SECURITY DEFINER-funktioner. Det här är inte hypotetiskt. Lovables egen säkerhetsguide ber dig granska de RLS-policyer den sätter upp, och CVE-2025-48757 ovan är vad som händer när ingen gör det.

Fyra vägar för en förfrågan till dina data. Bara den första kontrolleras av dina RLS-policyer. Vanlig förfrågan: Webbläsare → Behörighet → RLS-policy → Egna rader; Hemlig nyckel i edge function: Webbläsare → Edge function → Hemlig nyckel → Alla rader; Vy utan security_invoker: Webbläsare → Vy → Ägarens rättigheter → Alla rader; SECURITY DEFINER-funktion: Webbläsare → RPC → Ägarens rättigheter → Alla rader.
Figur 1. Bara den första vägen kontrolleras av dina policyer. De andra tre körs med rättigheter som hoppar över RLS. Källa: Supabase-dokumentation om RLS och att säkra API:et, oktober 2026.

Prompten: klistra in den i Lovable eller Claude

Du kan klistra in den som den är. Kontrollera sedan resultatet själv: AI älskar att säga “klart” när det inte är det, och en prompt som får ett fel att försvinna kan göra det genom att vidga åtkomsten.

Granska det här projektet ur säkerhetssynpunkt, åtgärda det du hittar och berätta vad du ändrade:

1. Testa med två användare i två företag. Användare A försöker läsa, redigera och radera Bs poster via id, API, sökning och export. Allt måste misslyckas.
2. RLS på varje tabell, policyer kontrollerar radens ägare eller företag. "Inloggad" räcker inte.
3. Service role-nyckeln aldrig i frontend. Edge functions som använder den kontrollerar användaren och filtrerar på företag själva, nyckeln hoppar över RLS.
4. Roller, plan och företags-id aldrig från användarmetadata, användare kan redigera den. Lagra dem i en tabell som bara servern skriver till.
5. När en förfrågan skickar två id (projekt + uppgift), kontrollera att uppgiften hör till projektet.
6. Dra tillbaka alla tabellbehörigheter från anon och authenticated, ge tillbaka bara det policyerna behöver. RLS stoppar inte TRUNCATE. Varje migrering som skapar en tabell innehåller dess GRANT: från och med 30 oktober 2026 ger Supabase inte längre nya tabeller behörigheter automatiskt, och appen får felet 42501.
7. Alla vyer med security_invoker = true, annars hoppar de över RLS.
8. Storage-buckets: användare läser och skriver bara sina egna filer, inga publika buckets för privata data.
9. SECURITY DEFINER-funktioner: NULL måste betyda neka, inte tillåt.
10. Rate limits på inloggning, registrering, återställning och OTP, lagrade i databasen, inte i minnet.
11. Validera all indata på servern.
12. Kör Supabase security advisor, åtgärda varje varning.

De 12 kontrollerna, och hur du bevisar var och en

1. Två användare i två företag

Skapa användare A i företag 1 och användare B i företag 2, var och en med några poster. Logga in som A och försök läsa, redigera och radera Bs poster via id, genom API:et, genom sökning och genom varje export. Varje försök måste returnera ingenting eller ett fel. Det är det enda test som fångar det mesta av det som följer, eftersom det testar vad angriparen gör, inte vad du skrev.

Två användare, två företag. Användare A i företag 1 går efter företag 2:s poster. Varje väg måste returnera ingenting. Via id genom API:et: Användare A → Bs post-id → Läs, redigera, radera → 0 rader eller ett fel; Genom sökning: Användare A → Sök Bs text → Resultat → Inga B-rader; Genom en export: Användare A → Export → Fil → Inga B-rader.
Figur 2. Tvåföretagstestet. Godkänt betyder att varje väg returnerar ingenting från företag 2.

Lägg till en tredje användare som tillhör båda företagen. En policy av formen “den som anropar är medlem i radens företag” låter den användaren läsa företag 2:s rader medan hen arbetar i företag 1. I en annan Supabase-app returnerade min sond på en sådan användare det andra företagets dokument tills frågan även filtrerade på förfrågans företag. Det skrev jag upp i en användare i två organisationer som RLS inte isolerade.

2. RLS på varje tabell, avgränsat till ägaren eller företaget

auth.uid() is not null betyder “vilken inloggad användare som helst”, vilket på en öppen registreringssida betyder vem som helst. Varje policy jämför radens ägare eller företag med den som anropar. Lista de tabeller som fortfarande har RLS av:

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. Den hemliga nyckeln aldrig i frontend

Service role-nyckeln och den nyare sb_secret_-nyckeln kringgår alla policyer. Sök i den byggda JavaScripten efter båda och rotera varje nyckel som någonsin levererats ut. Edge functions som använder den måste själva kontrollera vem användaren är och filtrera på företag, för med den nyckeln gör inget annat det.

4. Roller, plan och företags-id aldrig från användarmetadata

raw_user_meta_data är det klienten skickade till signUp, och användare kan ändra det senare. I en annan app läste new-user-triggern den inbjudna rollen därifrån, så vem som helst som registrerade sig kunde skicka med ägarrollen och bli Owner. Lagra roller, planer och företags-id i en tabell som bara servern skriver till, eller i app_metadata. Hela historien: en roll hämtad från registreringsmetadata.

5. Två id i en förfrågan: kontrollera att de hör ihop

När en förfrågan bär ett projekt-id och ett uppgifts-id räcker det inte att auktorisera projektet. En användare med rättigheter på projekt A skickar A plus ett sektions-id från projekt B, och raden hamnar i B. RLS fångar det inte, eftersom insert stämplar angriparens eget projekt-id, vilket är precis det policyn kontrollerar. Kontrollera att barnet hör till föräldern innan du skriver.

6. Behörigheter: dra tillbaka, ge sedan tillbaka bara det som behövs

Behörigheter och policyer är två skilda lager. Att lägga till policyer tar inte tillbaka en behörighet, och därför säger Supabases RLS-guide att du uttryckligen ska dra tillbaka och ge tillbaka. I ett annat projekt hade supabase/postgres 17.6.1-imagen gett anon och authenticated TRUNCATE, REFERENCES, TRIGGER och MAINTAIN på varje tabell i public, och ingen policy gäller för TRUNCATE. Data API:et har ingen TRUNCATE-endpoint, så det här är ingen radering med en enda förfrågan, men ingen klientroll behöver det, och varje funktion som kör SQL som den rollen ärver det.

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;

Ett datum ändrar den här kontrollen. Supabase slutar ge nya tabeller behörigheter automatiskt: nya projekt sedan 30 maj 2026 och alla befintliga projekt från 30 oktober 2026, även de som Lovable skapat. Efter det är en tabell som Lovable eller en migrering skapar utan en uttrycklig GRANT osynlig för appen: förfrågan misslyckas med 42501 och funktionen som använder tabellen går sönder. Så varje migrering som skapar en tabell bär nu sina behörigheter, bredvid enable row level security och policyerna.

Två saker förblir desamma. Befintliga tabeller behåller de behörigheter de har, så det är fortfarande ditt jobb att dra tillbaka det du inte behöver. Och ändringen tar bara tillbaka select, insert, update och delete: i Lovable-projektet kom nya tabeller med de nya standardvärdena fortfarande med TRUNCATE, REFERENCES, TRIGGER och MAINTAIN för anon och authenticated. Dra först tillbaka allt, ge sedan tillbaka, är fortfarande den säkra ordningen.

7. Varje vy med security_invoker = true

En vy körs med ägarens rättigheter, och i Supabase är ägaren oftast postgres, så en vy över en skyddad tabell delar ut varje rad som policyerna håller tillbaka. På Postgres 15 och senare:

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

8. Storage: bara egna filer, ingen publik bucket för privata data

En publik bucket serverar vilken fil som helst till alla som har URL:en. Privata filer hör hemma i en privat bucket med policyer på storage.objects som kontrollerar ägaren eller företaget, oftast utifrån första mappen i sökvägen. Efter att ha flyttat ett projekt: räkna de aktiva storage-policyerna. I en app misslyckades varje uppladdning efter en flytt, eftersom policy-migreringen aldrig kördes och tabellen hade RLS på utan en enda policy.

9. SECURITY DEFINER-funktioner: NULL betyder neka

I PL/pgSQL kastar if not (has_permission or assignee_id = auth.uid()) inget fel när assignee_id är null: hela villkoret är null, och if hoppar över det. Samma uttryck är säkert i en policy, som behandlar null som neka. I en app lät det en roll med alla behörigheter avstängda agera på alla 3683 uppgifter, eftersom ingen hade en ansvarig. Lägg villkoret i coalesce(…, false). Detaljer: en NULL i PL/pgSQL IF NOT hoppade över kontrollen.

10. Rate limits lagrade i databasen

Begränsa inloggning, registrering, lösenordsåterställning och OTP, och håll räknaren i Postgres. I Lovable-appen låg gränserna i en variabel inne i edge function:en. Jag provade det hostade projektet den 17 oktober 2026: varje förfrågan kördes i en ny isolate, så räknaren i minnet var alltid 1 och gränsen kunde aldrig slå till. Lokal supabase functions serve återanvänder isolaten, så samma kod klarade alla lokala tester.

Var räknaren bor avgör om den slår till. Rate limit i en hostad Supabase edge function. Räknare i funktionens minne: Förfrågan → Ny isolate → Räknare = 1 → Aldrig begränsad; Räknare i Postgres: Förfrågan → claim_rate_limit → Delad rad → 429 vid gränsen.
Figur 3. Min sond av en hostad Supabase edge function, 17 oktober 2026: varje förfrågan startade en ny isolate.

Nyckeln spelar lika stor roll som lagringen. På den hostade plattformen är det sista X-Forwarded-For-ledet Supabases egen proxy, så att använda det som nyckel lägger alla som anropar i en hink, och x-client-ip sätts av klienten. I min sond bar cf-connecting-ip den verkliga klienten. Åtgärden var en SECURITY DEFINER-funktion claim_rate_limit(bucket, key, max, window) som bara service role kan köra, serialiserad med en advisory lock, som lagrar en hash av IP-adressen eller användar-id:t i stället för råvärdet.

11. Validera all indata på servern

Oavsett vad formuläret kontrollerar accepterar API:et allt klienten skickar. Längder, format, enums och ägarskap kontrolleras i databasen (constraints, triggers) eller i edge function:en, aldrig bara i React.

12. Supabase Security Advisor, varje varning

Öppna Advisors, sedan Security Advisor, och åtgärda varje fynd. Den flaggar tabeller med RLS av, vyer som hoppar över RLS, policyer som läser användarmetadata, öppna using (true)-policyer och funktioner med en föränderlig search_path. Den kan inte se kontroll 1, 5, 9 eller 10, eftersom de handlar om logik, inte konfiguration.

Vad lärde Lovable-appen mig som de 12 missar?

De här kom ur kundens app och granskningarna av den. Ingen av dem syns i en skanner.

  1. Fält som klienten inte får sätta. Klienten skickade själv status: 'approved', vilket hoppade över modereringen. En UPDATE-policy vars with check är lösare än dess using låter också en användare flytta en rad till en annan ägare. En policy kan inte beräkna ett värde, så en BEFORE INSERT OR UPDATE-trigger härleder status, roll, pris och företag på nytt från serverns egna data och ignorerar det klienten skickade.
  2. Varje skrivväg, inte bara den primära insert. När den primära insert-en var åtgärdad satt samma kringgående i bilagor, kommentarer, återställning av ett mjukt raderat objekt och räknare. Lista varje skrivare till en skyddad kolumn innan du åtgärdar den första.
  3. Tillåtelselistor, inte nekandelistor. Förfalskade poster tog fem granskningsrundor, eftersom varje åtgärd blockerade en kombination och lämnade en syskonväg öppen. En trigger som bara tillåter de listade kombinationerna av typ och status stängde hela klassen på en gång.
  4. Funktioner är också ett API. Varje funktion i public kan anropas via /rpc av vilken roll som helst med EXECUTE, och RLS gäller inte funktioner. Ge varje SECURITY DEFINER-funktion set search_path = '', dra tillbaka EXECUTE från public, anon och authenticated, och ge det bara till rollen som behöver det.
  5. Att dra tillbaka EXECUTE kan förstöra publika sidor. Postgres kontrollerar EXECUTE på varje funktion som används av varje policy som gäller för rollen, även i en gren som aldrig skulle köras. När anon förlorade EXECUTE på is_admin() misslyckades varje anonym läsning med 42501. Policyer enbart för administratörer får to authenticated, och publika läsningar får en egen policy utan administratörsgrenen.
  6. Registrering med e-postbekräftelse på. Med bekräftelse på returnerar signUp ingen session, så varje profilskrivning direkt efteråt körs som anon och skriver tyst ingenting. Lovable-projekt har ofta auto-confirm på, vilket döljer felet. Skicka fälten i signUp({ options: { data } }), låt new-user-triggern validera och skriva dem, och behandla den metadatan som otillförlitlig (kontroll 4).
  7. Tester som klarar sig av fel anledning. Den lokala stacken och det hostade projektet hade olika standardbehörigheter, så “anon kan inte infoga” klarades lokalt eftersom behörigheten saknades, inte för att RLS fungerade. Ett negativt test betyder något först när den positiva behörigheten finns. Jämför pg_default_acl lokalt och i produktion.
  8. Den levande databasen, inte migreringsfilerna. Efter en migrering eller en flytt av projektet: jämför det som faktiskt är live: pg_policies, pg_get_functiondef för varje skydd och behörigheterna ovan. I en annan app kopierade en ny migrering en gammal funktionskropp och ångrade tyst en senare åtgärd.

Är en Lovable-app GDPR-kompatibel?

Lovable gör inte en app kompatibel eller inkompatibel; det avgörs av vad appen gör med personuppgifter, och en säkerhetsgranskning svarar inte på det. Det här är de sex luckor jag kontrollerar i en Lovable Cloud-app, var och en med åtgärden:

  1. Lista varje personuppgiftsbiträde. Lovable Cloud kör databasen, auth, lagringen och funktionerna på Supabase, Lovable hostar sajten och kan skicka inloggningsmejlen, och Lovable AI Gateway skickar text vidare till en modellleverantör. Lägg till varje mejl-, AI- eller scrapingtjänst som dina funktioner anropar. Varje tjänst som får personuppgifter behöver ett personuppgiftsbiträdesavtal, och en utanför EU behöver dessutom en överföringsmekanism. Hostingregionen syns inte i koden, så fråga efter den.
  2. Radera en användare som har innehåll. Främmande nycklar från det användaren skrivit kan blockera raderingen, och då går en begäran om radering inte att genomföra. Låt de nycklarna kaskadera eller sättas till null, lägg till en raderingstrigger som också tar bort användarens mejlloggar och inloggningstoken, och behåll ett databastest som raderar en sådan användare.
  3. Sätt en lagringsgräns. Mejlloggar, visningsspårning, köer för misslyckade mejl och mjukraderade rader behåller ofta personuppgifter utan slutdatum. Ett schemalagt jobb som raderar mjukraderade rader för gott efter en fast period, och tar bort deras filer, täpper till det mesta.
  4. Mejlspårning bara med samtycke. Att registrera öppningar och klick per mottagare kräver samtycke enligt ePrivacy-reglerna: en samtyckesflagga, av som standard, med tidsstämpeln satt av servern, inte av klienten.
  5. Håll adresser borta från funktionsloggarna. Loggar från edge functions rensas inte, så en console.log med en mottagaradress blir kvar där. Logga id:n och antal.
  6. Slå på e-postbekräftelse. Med automatisk bekräftelse kan vem som helst registrera sig med en adress som inte är deras och föra in en verklig persons adress i appen. Punkt 6 i listan ovan täcker registreringssidan.

Så kontrollerar du din egen app: sök efter API-nycklar i funktionerna för att lista tjänsterna, radera en testanvändare med innehåll, sök efter @ i funktionsloggarna och be varje leverantör om personuppgiftsbiträdesavtalet. Det här är inte juridisk rådgivning.

Hur hindrar du att det kommer tillbaka?

Varje kontroll ovan är en fråga eller ett test, så ha dem i repot och kör dem före varje driftsättning. Supabases pgTAP-tester täcker behörigheter, policyer och funktioner; tvåföretagstestet hör hemma bredvid dem. Om din app har roller och en behörighetsskärm räcker inte heller ett test per policy: jag bevisar varje behörighetsruta mot en kopia av produktion, beskrivet i hur du testar Supabase RLS, där 30 av 87 rutor visade sig inte göra något. Den gratis Supabase säkerhetskontroll kör 46 OWASP Top 10:2025-regler mot ett projekts roller, behörigheter, policyer och auth-inställningar, så använd den för första genomgången; tvåföretagstestet och allt om ditt gränssnitt är fortfarande manuellt.

Vad listan inte täcker: betalningar och webhooks, hemligheter i din Git-historik, säkerhetsheaders på värden, backuper. De spelar roll, men det är en annan checklista.

Om du har ärvt en Lovable- eller Supabase-app och inte är säker på vilka av dessa den klarar, är den här granskningen där en projekträddning börjar. Caset om tidrapportering för en revisionsbyrå visar hur rollkontrollerna ser ut i en produktionsapp.

Hittade du en lucka som listan missar?

Det vill jag gärna höra. Jag jämför gärna erfarenheter om att säkra Lovable- och Supabase-appar.

Hör av dig

© 2026 Denys Zaichenko. Originalartikel: https://madsalt.tech/sv/blog/lovable-app-security-checklist/