TL;DR
Begge låser en ny tabel som standard: Appwrite svarede 401 til en gæst og til en logget bruger, og Supabase svarer 401/403, indtil du giver API-rollerne ret til tabellen. Forskellen viser sig, når en regel er forkert, eller en anmodning krydser brugere. På Supabase gav en opdatering af en andens række 200 [], og en for løs insert-policy lod en bruger tilmelde sig en anden virksomhed; på Appwrite endte hvert forsøg på tværs af brugere i min test som 401 eller 404, og en bruger kunne ikke give sin række til andre end sig selv eller alle. Appwrites servernøgler har scopes; en Supabase secret key springer alle policies over.
Jeg er Denys, fullstack- og AI-ingeniør. De fleste apps, jeg bygger eller retter, kører på Supabase, og klienter, der stadig vælger backend, spørger, hvordan Appwrite står sig på sikkerhed. Funktionslister svarer ikke på det, så jeg sendte begge de samme anmodninger.
Sådan testede jeg. Appwrite 2.3.0, selvhostet med Docker den 30. oktober 2026, gennem dets REST API som gæst, som to loggede brugere A og B og med to servernøgler. Supabase-siden er min test på en lokal stack (CLI 2.109.1, Postgres 17.6) fra 24. oktober 2026 med de samme roller, beskrevet fuldt i Eksempler på Supabase RLS-policies. Opfundne rækker på begge.
Hvordan beskytter Appwrite og Supabase data?
Supabase beskytter tabeller med Postgres: en rettighed afgør, om API-rollerne overhovedet når en tabel, og row level security-policies, skrevet i SQL, afgør, hvilke rækker hver anmodning ser og skriver. Appwrite gemmer tilladelser ved siden af data: en tabel har en liste over roller, der må læse, oprette, opdatere eller slette, og med row security slået til har hver række sin egen liste. Ifølge Appwrites dokumentation om tilladelser gives adgang kun, aldrig nægtes, og en bruger skal have den på tabelniveau eller rækkeniveau, ikke begge.
Spørgsmålet er altså ikke, hvem der har adgangsstyring. Det har begge. Det er, hvad hver af dem lader dig tage fejl i.
Hvad gør hver af dem ved en ny tabel?
| Anmodning | Appwrite, ingen tilladelser sat | Supabase, RLS slået til, ingen rettighed (24. okt.) |
|---|---|---|
| Gæst viser rækker | 401 user_unauthorized |
401 permission denied |
| Logget bruger viser rækker | 401 user_unauthorized |
403 permission denied |
| Logget bruger tilføjer en række | 401 No permissions provided for action ‘create’ |
ikke testet |
En ny tabel er lukket på begge. På Supabase er det nyt: tabeller oprettet efter 30. oktober 2026 får ikke længere automatisk Data API-rettigheder, som jeg beskrev i ændringen af rettigheder 30. oktober.
Hvad sker der, når en tabel er åben for alle?
De to “åbne” indstillinger er ikke det samme. I Appwrite gav jeg tabellen én tilladelse, read("any"). På Supabase er den almindelige åbne tilstand RLS slået fra med standardrettighederne intakte.
| Anmodning fra en gæst | Appwrite, read("any") |
Supabase, RLS slået fra, standardrettigheder (24. okt.) |
|---|---|---|
| Vis rækker | 200, begge rækker |
200, begge rækker |
| Slet en række | 401 |
200, begge rækker slettet |
Appwrites “offentlig” betyder kun læsning, medmindre du også giver skrivning. En Supabase-tabel med RLS slået fra kan læses og skrives af enhver med publishable key, og derfor er “RLS disabled in public” det første, jeg tjekker.
Hvordan fungerer “egne rækker” i hver?
I Appwrite slog jeg row security til og gav tabellen én tilladelse, create("users"). Da A oprettede en række uden at angive tilladelser, satte Appwrite dem selv: read, update og delete kun for bruger A. På Supabase er den samme hensigt fire policies på (select auth.uid()) = user_id.
| Anmodning | Appwrite (30. okt.) | Supabase (24. okt.) |
|---|---|---|
| Gæst viser rækker | 200, 0 rækker |
401 (ingen rettighed til anon) |
| A viser rækker | 200, A’s 2 rækker |
200, A’s 2 rækker |
| A læser B’s række | 404 row_not_found |
kommer ikke med i listen |
| A opdaterer B’s række | 401 |
200 [] |
| A sletter B’s række | 401 |
200 [] |
Ingen af dem lækkede B’s række. Forskellen er svaret. Supabase filtrerer rækker, som en policy skjuler, så en opdatering, der ikke ramte noget, er en succes med nul rækker; en app, der tolker “ingen fejl” som “gemt”, fortæller brugeren, at ændringen virkede. Appwrite afviser med en fejl, appen ikke kan overse.
Kan en bruger give sine data til en anden?
Her går genereret kode oftest galt, så jeg prøvede det på begge.
| Anmodning fra A | Appwrite | Supabase (24. okt.) |
|---|---|---|
| Opret en række, B kan læse | 401, Permissions must be one of: any, users, user:A… |
afhænger af policyen |
| Ret egen række, så B ejer den | 401, samme besked |
403 med fuld with check; 200 med with check (true) |
| Opret en række, alle kan læse | 201, og en gæst kunne derefter vise den |
kun hvis en policy tillader det |
Appwrite begrænser, hvad en bruger kan give, til roller, vedkommende selv har: any, users, sig selv og sine egne teams. Det blokerede “giv min række til en anden” uden nogen regel fra mig. Det forhindrede ikke A i at udgive sin egen række til alle med read("any"). Hvis en række aldrig må være offentlig, bør klienten ikke sætte dens tilladelser; opret den fra en serverfunktion i stedet.
På Supabase er udfaldet, hvad policyen siger. En fuldstændig with check afviste flytningen med 403; with check (true), som nogle generatorer skriver, lod A flytte rækken ind på B’s konto.
Kan en bruger tilmelde sig en andens virksomhed?
På Supabase er teamadgang som regel en memberships-tabel plus policies. I min test lod den insert-policy, mange tutorials foreslår, with check (user_id = auth.uid()), A tilføje sig selv til virksomhed 2 med 201 Created og derefter læse dens lønprojekt.
Appwrite har teams indbygget. B oprettede et team; A prøvede at tilføje sig selv som medlem og som ejer. Begge anmodninger gav 404 team_not_found: A kunne ikke engang se teamet. Teammedlemskab afgøres af serveren, ikke af en regel, du skriver, så netop denne lækage er ikke mulig. Rækker kræver stadig read("team:…")-tilladelser for at blive delt, og dem sætter du selv.
Hvad åbner en servernøgle?
| Nøgle | Appwrite | Supabase (fra min test af anon vs service role) |
|---|---|---|
| Fuld servernøgle | 200, alle 4 rækker fra begge brugere |
alle 3 rækker, RLS sprunget over |
Nøgle med snævert scope (users.read) |
401, missing scope rows.read |
ingen nøgler med scope |
En Supabase secret key svarer til service_role, som har BYPASSRLS; hvad den åbner, står i anon key vs service role key. En Appwrite-nøgle ignorerer også rækketilladelser, men kun for de scopes, den blev oprettet med, så en nøgle til et job, der administrerer brugere, kan ikke læse dine tabeller. Begge nøgler i et browser-bundle er en fuld lækage af alt, de kan nå.
To mindre fund på Appwrite: en gæst og en logget bruger, der bad om listen over tabeller, fik begge 401 (kaldet kræver tables.read, som kun nøgler har), og read("users") betyder enhver logget konto, den samme fælde som auth.uid() is not null på Supabase, når tilmelding er åben.
Hvilken er mest sikker?
Ingen af dem er sikker eller usikker som standard; begge startede lukkede. De adskiller sig i, hvor en fejl kan gemme sig:
| Spørgsmål | Appwrite | Supabase |
|---|---|---|
| Hvor adgangsreglerne bor | Rolelister på tabeller og rækker | SQL-rettigheder og policies |
| En afvist skrivning på tværs af brugere | Fejl (401 / 404) |
200 med nul rækker ved update og delete |
| At give rækker til andre | Kun til roller, brugeren selv har | Hvad policyen tillader |
| Regler på kolonneværdier, MFA-niveau | Ikke i tilladelser; kræver serverkode | I policyen, fx aal2 for følsomme rækker |
| Servernøgler | Med scopes | Secret key springer alle policies over |
Appwrites model er snævrere, og i min test afviste den snævre model hvert trick på tværs af brugere, uden at jeg skrev noget. Supabases model er SQL, så den kan udtrykke regler, Appwrites tilladelser ikke kan, som “kun med MFA” eller “kun rækker fra virksomheder, jeg tilhører”, og den kan også udtrykke den forkerte regel. Hvis dit team ikke vil læse policies, tilgiver Appwrites standarder mere. Hvis du har brug for regler, der afhænger af data, lader Supabase dig skrive dem, og så skal de testes; metoden, jeg bruger, står i sådan tester du Supabase RLS.
Hvordan tjekker du et Supabase-projekt?
Det gratis Supabase-sikkerhedstjek læser dit projekt skrivebeskyttet og flager tabeller med RLS slået fra, policies, der altid er sande, skrive-policies for gæster og rettigheder, der giver mere, end policies antyder. Hvad Supabase dækker for dig, og hvad det overlader til dig, står i er Supabase sikkert?. Der er ingen tilsvarende til Appwrite; til Appwrite kører du ovenstående anmodninger som to brugere. For Firebase har jeg kørt samme slags angreb i eksempler på Firebase security rules.
Begrænsninger. Én selvhostet Appwrite 2.3.0 på en bærbar og én lokal Supabase-stack, seks dage fra hinanden, opfundne rækker. Appwrite Cloud kan afvige fra selvhostet. Storage, funktioner, realtime, auth-indstillinger og compliance-certifikater sammenlignes ikke her.
Vælger du backend til en app med private data?
Jeg kigger gerne på din adgangsmodel sammen med dig. Ingen salgstale.
Kontakt mig