---
title: "Appwrite vs Supabase: sikkerhed, samme angreb på begge"
description: "Samme adgangsangreb mod Appwrite 2.3.0 og en lokal Supabase. Begge låser nye tabeller; de adskiller sig i, hvordan fejl viser sig, og hvad en nøgle åbner."
url: "https://madsalt.tech/da/blog/appwrite-vs-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/da/about/"
language: "da"
published: 2026-10-30
---

# Appwrite vs Supabase: sikkerhed, samme angreb på begge

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](https://madsalt.tech/da/blog/supabase-rls-policy-examples/). 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](https://appwrite.io/docs/products/databases/tablesdb/permissions) 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](https://madsalt.tech/da/blog/supabase-october-30-grant-change/).

## 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.](https://madsalt.tech/charts/da/appwrite-vs-supabase.svg)

*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](https://madsalt.tech/da/blog/supabase-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](https://madsalt.tech/da/blog/how-to-test-supabase-rls/).

## Hvordan tjekker du et Supabase-projekt?

Det gratis [Supabase-sikkerhedstjek](https://madsalt.tech/da/tools/supabase-security-check/) 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?](https://madsalt.tech/da/blog/is-supabase-secure/). 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](https://madsalt.tech/da/blog/firebase-security-rules-examples/).

**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](https://madsalt.tech/da/#contact)
