Hoppa till innehållet

Appwrite vs Supabase säkerhet: samma attacker på båda

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

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

TL;DR

Båda låser en ny tabell som standard: Appwrite svarade 401 till en gäst och till en inloggad användare, och Supabase svarar 401/403 tills du ger API-rollerna grant på tabellen. Skillnaden syns när en regel är fel eller en förfrågan korsar användare. På Supabase gav en uppdatering av en annan användares rad 200 [], och en slarvig insert-policy lät en användare gå med i ett annat företag; på Appwrite kom varje försök över användargränsen tillbaka som 401 eller 404, och en användare kunde inte ge sin rad till någon annan än sig själv eller alla. Appwrites servernycklar har scopes; en Supabase secret key hoppar över varje policy.

Jag heter Denys och är full-stack- och AI-ingenjör. De flesta appar jag bygger eller reparerar körs på Supabase, och kunder som fortfarande väljer backend frågar hur Appwrite står sig på säkerhet. Funktionslistor svarar inte på det, så jag skickade samma förfrågningar till båda.

Så testade jag. Appwrite 2.3.0, självhostat med Docker den 30 oktober 2026, via dess REST-API som gäst, som två inloggade användare A och B och med två servernycklar. Supabase-sidan är min körning på en lokal stack (CLI 2.109.1, Postgres 17.6) från den 24 oktober 2026 med samma roller, beskriven i sin helhet i Supabase RLS policy-exempel. Påhittade rader på båda.

Hur skyddar Appwrite och Supabase data?

Supabase skyddar tabeller med Postgres: ett grant avgör om API-rollerna alls når en tabell, och row level security-policyer, skrivna i SQL, avgör vilka rader varje förfrågan ser och skriver. Appwrite lagrar behörigheter bredvid datan: en tabell har en lista över roller som får läsa, skapa, uppdatera eller ta bort, och med row security på har varje rad sin egen lista. I Appwrites dokumentation om behörigheter ges åtkomst bara, den nekas aldrig, och en användare behöver den antingen på tabellnivå eller radnivå, inte båda.

Frågan är alltså inte vilken som har åtkomstkontroll. Båda har. Frågan är vad var och en låter dig göra fel.

Vad gör varje system med en ny tabell?

Förfrågan Appwrite, inga behörigheter satta Supabase, RLS på, inget grant (24 okt)
Gäst listar rader 401 user_unauthorized 401 permission denied
Inloggad användare listar rader 401 user_unauthorized 403 permission denied
Inloggad användare lägger till en rad 401 No permissions provided for action ‘create’ inte testat

En ny tabell är stängd på båda. På Supabase är det nytt: tabeller som skapas efter den 30 oktober 2026 får inte längre Data API-grants automatiskt, vilket jag gick igenom i grant-ändringen den 30 oktober.

Vad händer när en tabell är öppen för alla?

De två “öppna” inställningarna är inte samma sak. I Appwrite gav jag tabellen en enda behörighet, read("any"). På Supabase är det vanliga öppna läget RLS av med standardgrants kvar.

Förfrågan av en gäst Appwrite, read("any") Supabase, RLS av, standardgrants (24 okt)
Lista rader 200, båda raderna 200, båda raderna
Ta bort en rad 401 200, båda raderna borttagna

Appwrites “publik” betyder skrivskyddad om du inte också ger skrivrätt. En Supabase-tabell med RLS av går att läsa och skriva för alla som har publishable key, och därför är “RLS disabled in public” det första jag kontrollerar.

Hur fungerar “egna rader” i varje system?

I Appwrite slog jag på row security och gav tabellen en enda behörighet, create("users"). När A skapade en rad utan att ange behörigheter satte Appwrite dem själv: read, update och delete bara för användare A. På Supabase är samma avsikt fyra policyer på (select auth.uid()) = user_id.

Förfrågan Appwrite (30 okt) Supabase (24 okt)
Gäst listar rader 200, 0 rader 401 (inget grant för anon)
A listar rader 200, A:s 2 rader 200, A:s 2 rader
A läser B:s rad 404 row_not_found returneras inte i listan
A uppdaterar B:s rad 401 200 []
A tar bort B:s rad 401 200 []
Samma förfrågan, två backends. Användare A ändrar en rad som ägs av användare B, via respektive REST-API. Supabase, ägar-policy: Användare A → uppdaterar B:s rad → 200 [] → 0 rader, inget fel; Appwrite, row security på: Användare A → uppdaterar B:s rad → 401 → fel returneras.
Figur 1. Mina tester: lokal Supabase-stack (CLI 2.109.1), 24 oktober 2026; självhostad Appwrite 2.3.0, 30 oktober 2026.

Ingen av dem läckte B:s rad. Skillnaden är svaret. Supabase filtrerar bort rader som en policy döljer, så en uppdatering som inte matchade något är en framgång med noll rader; en app som tar “inget fel” som “sparat” säger till användaren att ändringen gick igenom. Appwrite avvisar med ett fel som appen inte kan missa.

Kan en användare lämna över sin data till någon annan?

Här går genererad kod oftast fel, så jag provade det på båda.

Förfrågan av A Appwrite Supabase (24 okt)
Skapa en rad som B kan läsa 401, Permissions must be one of: any, users, user:A… beror på policyn
Ändra egen rad så att B äger den 401, samma meddelande 403 med ett fullständigt with check; 200 med with check (true)
Skapa en rad som alla kan läsa 201, och en gäst listade den därefter bara om en policy tillåter det

Appwrite begränsar vad en användare kan ge till de roller hon själv har: any, users, sin egen användare och sina egna team. Det stoppade draget “ge min rad till någon annan” utan någon regel från mig. Det hindrade inte A från att publicera sin egen rad för alla med read("any"). Om en rad aldrig får vara publik ska klienten inte vara den som sätter dess behörigheter; skapa den från en serverfunktion i stället.

På Supabase blir utfallet det policyn säger. Ett fullständigt with check nekade flytten med 403; with check (true), som vissa generatorer skriver, lät A flytta raden till B:s konto.

Kan en användare gå med i någon annans företag?

På Supabase är teamåtkomst oftast en memberships-tabell plus policyer. I min körning lät insert-policyn som många guider föreslår, with check (user_id = auth.uid()), A lägga till sig själv i företag 2 med 201 Created och sedan läsa dess payroll-projekt.

Appwrite har team inbyggda. B skapade ett team; A försökte lägga till sig själv som medlem och som ägare. Båda förfrågningarna gav 404 team_not_found: A kunde inte ens se teamet. Teammedlemskap avgörs av servern, inte av en regel du skriver, så just den här läckan finns inte. Rader behöver fortfarande read("team:…")-behörigheter för att delas, och dem sätter du själv.

Vad öppnar en servernyckel?

Nyckel Appwrite Supabase (från mitt test av anon vs service role)
Full servernyckel 200, alla 4 rader från båda användarna alla 3 rader, RLS hoppas över
Nyckel med smal scope (users.read) 401, missing scope rows.read inga nycklar med scope

En Supabase secret key mappar till service_role, som har BYPASSRLS; vad den öppnar står i anon key vs service role key. En Appwrite-nyckel ignorerar också radbehörigheter, men bara för de scopes den skapades med, så en nyckel för ett jobb som hanterar användare kan inte läsa dina tabeller. Båda nycklarna i en webbläsarbundle är en fullständig läcka av allt de når.

Två mindre fynd på Appwrite: en gäst och en inloggad användare som bad om listan över tabeller fick båda 401 (anropet kräver tables.read, som bara nycklar har), och read("users") betyder vilket inloggat konto som helst, samma fälla som auth.uid() is not null på Supabase när registrering är öppen.

Vilket är säkrast?

Ingen av dem är säker eller osäker som standard; båda började stängda. De skiljer sig i var ett misstag kan gömma sig:

Fråga Appwrite Supabase
Var åtkomstreglerna bor Rollistor på tabeller och rader SQL-grants och policyer
En nekad skrivning över användare Fel (401 / 404) 200 med noll rader för update och delete
Att ge rader till andra Bara till roller användaren har Vad policyn tillåter
Regler på kolumnvärden, MFA-nivå Inte i behörigheter; kräver serverkod I policyn, t.ex. aal2 för känsliga rader
Servernycklar Med scope Secret key hoppar över alla policyer

Appwrites modell är smalare, och i min körning nekade den smala modellen varje trick över användargränsen utan att jag skrev något. Supabases modell är SQL, så den kan uttrycka regler som Appwrites behörigheter inte kan, som “bara med MFA” eller “bara rader från företag jag tillhör”, och den kan också uttrycka fel regel. Om ditt team inte kommer att läsa policyerna förlåter Appwrites standardinställningar mer. Om du behöver regler som beror på datan låter Supabase dig skriva dem, och då måste de testas; metoden jag använder står i hur du testar Supabase RLS.

Hur kontrollerar du ett Supabase-projekt?

Den gratis Supabase-säkerhetskontrollen läser ditt projekt skrivskyddat och flaggar tabeller med RLS av, policyer som alltid är sanna, skrivpolicyer för gäster och grants som ger mer än policyerna antyder. Vad Supabase täcker åt dig och vad det lämnar åt dig står i Är Supabase säkert?. Det finns ingen motsvarighet för Appwrite i den; för Appwrite kör du förfrågningarna ovan som två användare. För Firebase körde jag samma sorts attacker i Firebase säkerhetsregler: exempel.

Begränsningar. En självhostad Appwrite 2.3.0 på en bärbar dator och en lokal Supabase-stack, sex dagar emellan, påhittade rader. Appwrite Cloud kan skilja sig från självhostat. Storage, functions, realtime, auth-inställningar och efterlevnadscertifikat jämförs inte här.

Väljer du backend till en app med privat data?

Jag tittar gärna på din åtkomstmodell tillsammans med dig. Ingen säljpitch.

Hör av dig

© 2026 Denys Zaichenko. Originalartikel: https://madsalt.tech/sv/blog/appwrite-vs-supabase/