Gå til indhold

Appwrite vs Supabase: sikkerhed, samme angreb på begge

Vis som MarkdownÅbn i ClaudeÅbn i ChatGPTÅbn i Perplexity

Spørgsmålet kopieres også. Hvis chatten åbner tom, så indsæt det.

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 []
Samme anmodning, to backends. Bruger A redigerer en række, som bruger B ejer, via hvert REST API. Supabase, policy kun for ejer: Bruger A → opdater B's række → 200 [] → 0 rækker, ingen fejl; Appwrite, row security slået til: Bruger A → opdater B's række → 401 → fejl returneret.
Figur 1. Mine tests: lokal Supabase-stack (CLI 2.109.1), 24. oktober 2026; selvhostet Appwrite 2.3.0, 30. oktober 2026.

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

© 2026 Denys Zaichenko. Original artikel: https://madsalt.tech/da/blog/appwrite-vs-supabase/