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 [] |
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