---
title: "Appwrite vs Supabase Sicherheit: dieselben Angriffe"
description: "Dieselben Zugriffsangriffe auf Appwrite 2.3.0 und einen lokalen Supabase-Stack. Beide sperren neue Tabellen; bei Fehlern und Schlüsseln unterscheiden sie sich."
url: "https://madsalt.tech/de/blog/appwrite-vs-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/de/about/"
language: "de"
published: 2026-10-30
---

# Appwrite vs Supabase Sicherheit: dieselben Angriffe

TL;DR

Beide sperren eine neue Tabelle standardmäßig: Appwrite antwortete einem Gast und einem angemeldeten Nutzer mit `401`, Supabase antwortet mit `401`/`403`, bis Sie die Tabelle den API-Rollen freigeben. Der Unterschied zeigt sich, wenn eine Regel falsch ist oder eine Anfrage Nutzergrenzen überschreitet. Bei Supabase lieferte ein Update auf die Zeile eines anderen `200 []`, und eine zu lockere Insert-Policy ließ einen Nutzer einer fremden Firma beitreten; bei Appwrite kam jeder nutzerübergreifende Versuch in meinem Lauf als `401` oder `404` zurück, und ein Nutzer konnte seine Zeile nur sich selbst oder allen freigeben. Appwrite-Server-Keys haben Scopes; ein Supabase Secret Key umgeht jede Policy.

Ich bin Denys, Full-Stack- und KI-Entwickler. Die meisten Apps, die ich baue oder repariere, laufen auf Supabase, und Kunden, die noch ein Backend wählen, fragen, wie Appwrite bei der Sicherheit abschneidet. Featurelisten beantworten das nicht, also habe ich beiden dieselben Anfragen geschickt.

**So habe ich getestet.** Appwrite 2.3.0, selbst gehostet mit Docker am 30. Oktober 2026, über die REST-API als Gast, als zwei angemeldete Nutzer A und B und mit zwei Server-Keys. Die Supabase-Seite ist mein Lauf auf einem lokalen Stack (CLI 2.109.1, Postgres 17.6) vom 24. Oktober 2026 mit denselben Rollen, ausführlich beschrieben in [Supabase RLS Policy Beispiele](https://madsalt.tech/de/blog/supabase-rls-policy-examples/). Auf beiden Seiten ausgedachte Zeilen.

## Wie schützen Appwrite und Supabase Daten?

Supabase schützt Tabellen mit Postgres: Ein Grant entscheidet, ob die API-Rollen eine Tabelle überhaupt erreichen, und Row-Level-Security-Policies, in SQL geschrieben, entscheiden, welche Zeilen jede Anfrage sieht und schreibt. Appwrite speichert Berechtigungen neben den Daten: Eine Tabelle hat eine Liste von Rollen, die lesen, anlegen, ändern oder löschen dürfen, und bei eingeschalteter Row Security trägt jede Zeile ihre eigene Liste. In den [Appwrite-Berechtigungsdokumenten](https://appwrite.io/docs/products/databases/tablesdb/permissions) wird Zugriff nur gewährt, nie verweigert, und ein Nutzer braucht ihn auf Tabellen- oder Zeilenebene, nicht auf beiden.

Die Frage ist also nicht, wer Zugriffskontrolle hat. Beide haben sie. Die Frage ist, wo sich jeweils Fehler einschleichen können.

## Was passiert mit einer neuen Tabelle?

| Anfrage                            | Appwrite, keine Berechtigungen gesetzt            | Supabase, RLS an, kein Grant (24. Okt.) |
| ---------------------------------- | ------------------------------------------------- | --------------------------------------- |
| Gast listet Zeilen                 | `401` user_unauthorized                           | `401` permission denied                 |
| Angemeldeter Nutzer listet Zeilen  | `401` user_unauthorized                           | `403` permission denied                 |
| Angemeldeter Nutzer fügt Zeile ein | `401` No permissions provided for action 'create' | nicht getestet                          |

Eine frische Tabelle ist bei beiden geschlossen. Bei Supabase ist das neu: Tabellen, die nach dem 30. Oktober 2026 angelegt werden, bekommen nicht mehr automatisch Data-API-Grants; das habe ich in [der Grant-Änderung vom 30. Oktober](https://madsalt.tech/de/blog/supabase-october-30-grant-change/) beschrieben.

## Was passiert, wenn eine Tabelle für alle offen ist?

Die beiden „offenen“ Einstellungen sind nicht dasselbe. In Appwrite habe ich der Tabelle eine einzige Berechtigung gegeben, `read("any")`. Bei Supabase ist der übliche offene Zustand RLS aus, während die Standard-Grants bestehen bleiben.

| Anfrage eines Gastes | Appwrite, `read("any")` | Supabase, RLS aus, Standard-Grants (24. Okt.) |
| -------------------- | ----------------------- | --------------------------------------------- |
| Zeilen listen        | `200`, beide Zeilen     | `200`, beide Zeilen                           |
| Zeile löschen        | `401`                   | `200`, beide Zeilen gelöscht                  |

„Öffentlich“ heißt bei Appwrite nur lesbar, solange Sie nicht auch Schreibrechte vergeben. Eine Supabase-Tabelle mit ausgeschaltetem RLS ist für jeden mit dem Publishable Key les- und schreibbar. Deshalb prüfe ich „RLS disabled in public“ als Erstes.

## Wie funktionieren „eigene Zeilen“ jeweils?

In Appwrite habe ich Row Security eingeschaltet und der Tabelle eine Berechtigung gegeben, `create("users")`. Als A eine Zeile ohne Berechtigungsangabe anlegte, setzte Appwrite sie selbst: `read`, `update` und `delete` nur für Nutzer A. Bei Supabase sind es für dieselbe Absicht vier Policies auf `(select auth.uid()) = user_id`.

| Anfrage            | Appwrite (30. Okt.) | Supabase (24. Okt.)           |
| ------------------ | ------------------- | ----------------------------- |
| Gast listet Zeilen | `200`, 0 Zeilen     | `401` (kein Grant für `anon`) |
| A listet Zeilen    | `200`, As 2 Zeilen  | `200`, As 2 Zeilen            |
| A liest Bs Zeile   | `404` row_not_found | nicht in der Liste enthalten  |
| A ändert Bs Zeile  | `401`               | `200 []`                      |
| A löscht Bs Zeile  | `401`               | `200 []`                      |

![Dieselbe Anfrage, zwei Backends. Nutzer A ändert eine Zeile von Nutzer B, über die jeweilige REST-API. Supabase, Policy nur für Besitzer: Nutzer A → Zeile von B ändern → 200 \[\] → 0 Zeilen, kein Fehler; Appwrite, Row Security an: Nutzer A → Zeile von B ändern → 401 → Fehler zurückgegeben.](https://madsalt.tech/charts/de/appwrite-vs-supabase.svg)

*Abbildung 1. Meine Tests: lokaler Supabase-Stack (CLI 2.109.1), 24. Oktober 2026; selbst gehostetes Appwrite 2.3.0, 30. Oktober 2026.*

Keiner von beiden gab Bs Zeile preis. Der Unterschied ist die Antwort. Supabase filtert Zeilen heraus, die eine Policy verbirgt, daher ist ein Update ohne Treffer ein Erfolg mit null Zeilen; eine App, die „kein Fehler“ als „gespeichert“ versteht, sagt dem Nutzer, seine Änderung habe geklappt. Appwrite lehnt mit einem Fehler ab, den die App nicht übersehen kann.

## Kann ein Nutzer seine Daten an jemand anderen übergeben?

Hier geht generierter Code am häufigsten schief, deshalb habe ich es bei beiden versucht.

| Anfrage von A                              | Appwrite                                               | Supabase (24. Okt.)                                                 |
| ------------------------------------------ | ------------------------------------------------------ | ------------------------------------------------------------------- |
| Zeile anlegen, die B lesen darf            | `401`, Permissions must be one of: any, users, user:A… | hängt von der Policy ab                                             |
| Eigene Zeile so ändern, dass B sie besitzt | `401`, gleiche Meldung                                 | `403` mit vollständigem `with check`; `200` mit `with check (true)` |
| Zeile anlegen, die alle lesen dürfen       | `201`, und ein Gast konnte sie danach listen           | nur wenn eine Policy es erlaubt                                     |

Appwrite beschränkt, was ein Nutzer vergeben kann, auf Rollen, die er selbst hat: `any`, `users`, den eigenen Nutzer und die eigenen Teams. Das blockierte den Zug „meine Zeile jemand anderem geben“, ohne dass ich eine Regel geschrieben hätte. Es hinderte A aber nicht daran, die eigene Zeile mit `read("any")` für alle zu veröffentlichen. Wenn eine Zeile nie öffentlich sein darf, sollte nicht der Client ihre Berechtigungen setzen; legen Sie sie stattdessen aus einer Server-Funktion an.

Bei Supabase ergibt sich, was die Policy sagt. Ein vollständiges `with check` lehnte den Zug mit `403` ab; `with check (true)`, wie es manche Generatoren schreiben, ließ A die Zeile in Bs Konto verschieben.

## Kann ein Nutzer der Firma eines anderen beitreten?

Bei Supabase besteht der Teamzugriff meist aus einer `memberships`-Tabelle plus Policies. In meinem Lauf ließ die Insert-Policy, die viele Tutorials vorschlagen, `with check (user_id = auth.uid())`, A sich selbst zu Firma 2 hinzufügen (`201 Created`) und danach deren Gehaltsprojekt lesen.

Appwrite hat Teams eingebaut. B legte ein Team an; A versuchte, sich als Mitglied und als Owner hinzuzufügen. Beide Anfragen lieferten `404 team_not_found`: A konnte das Team nicht einmal sehen. Die Teammitgliedschaft entscheidet der Server, nicht eine Regel, die Sie schreiben, daher ist dieses Leck hier nicht möglich. Damit Zeilen geteilt werden, brauchen sie weiterhin `read("team:…")`-Berechtigungen, und die setzen Sie selbst.

## Was öffnet ein Server-Key?

| Key                                | Appwrite                           | Supabase (aus meinem Test Anon- vs. Service-Role-Key) |
| ---------------------------------- | ---------------------------------- | ----------------------------------------------------- |
| Voller Server-Key                  | `200`, alle 4 Zeilen beider Nutzer | alle 3 Zeilen, RLS übersprungen                       |
| Key mit engem Scope (`users.read`) | `401`, missing scope rows.read     | keine Scope-Keys                                      |

Ein Supabase Secret Key entspricht `service_role`, und die hat `BYPASSRLS`; was sie öffnet, steht in [Anon Key vs. Service-Role-Key](https://madsalt.tech/de/blog/supabase-anon-key-vs-service-role-key/). Auch ein Appwrite-Key ignoriert Zeilenberechtigungen, aber nur für die Scopes, mit denen er angelegt wurde; ein Key für einen Job, der Nutzer verwaltet, kann Ihre Tabellen also nicht lesen. Jeder der beiden Keys in einem Browser-Bundle ist ein vollständiges Leck dessen, was er erreicht.

Zwei kleinere Befunde zu Appwrite: Ein Gast und ein angemeldeter Nutzer, die die Liste der Tabellen abfragten, bekamen beide `401` (der Aufruf braucht `tables.read`, das nur Keys haben), und `read("users")` bedeutet jedes angemeldete Konto, dieselbe Falle wie `auth.uid() is not null` bei Supabase, wenn die Registrierung offen ist.

## Welches ist sicherer?

Keines ist standardmäßig sicher oder unsicher; beide starteten geschlossen. Sie unterscheiden sich darin, wo sich ein Fehler verstecken kann:

| Frage                                               | Appwrite                                    | Supabase                                        |
| --------------------------------------------------- | ------------------------------------------- | ----------------------------------------------- |
| Wo die Zugriffsregeln liegen                        | Rollenlisten an Tabellen und Zeilen         | SQL-Grants und Policies                         |
| Ein abgelehnter nutzerübergreifender Schreibzugriff | Fehler (`401` / `404`)                      | `200` mit null Zeilen bei Update und Delete     |
| Zeilen an andere vergeben                           | Nur an Rollen, die der Nutzer hat           | Was immer die Policy erlaubt                    |
| Regeln auf Spaltenwerte, MFA-Stufe                  | Nicht in Berechtigungen; braucht Servercode | In der Policy, z. B. `aal2` für sensible Zeilen |
| Server-Keys                                         | Mit Scopes                                  | Secret Key überspringt alle Policies            |

Appwrites Modell ist enger, und in meinem Lauf wehrte das enge Modell jeden nutzerübergreifenden Trick ab, ohne dass ich etwas schreiben musste. Supabases Modell ist SQL, es kann also Regeln ausdrücken, die Appwrite-Berechtigungen nicht können, etwa „nur mit MFA“ oder „nur Zeilen der Firmen, in denen ich Mitglied bin“, und es kann ebenso die falsche Regel ausdrücken. Wenn Ihr Team die Policies nicht lesen wird, verzeihen Appwrites Standardwerte mehr. Wenn Sie Regeln brauchen, die von den Daten abhängen, lässt Supabase Sie sie schreiben, und dann müssen sie getestet werden; meine Methode steht in [Supabase RLS testen](https://madsalt.tech/de/blog/how-to-test-supabase-rls/).

## Wie prüfen Sie ein Supabase-Projekt?

Der kostenlose [Supabase-Sicherheitscheck](https://madsalt.tech/de/tools/supabase-security-check/) liest Ihr Projekt nur lesend und meldet Tabellen mit ausgeschaltetem RLS, Policies, die immer wahr sind, Schreib-Policies für Gäste und Grants, die mehr geben, als die Policies vermuten lassen. Was Supabase für Sie abdeckt und was es Ihnen überlässt, steht in [Wie sicher ist Supabase?](https://madsalt.tech/de/blog/is-supabase-secure/). Ein Appwrite-Pendant gibt es darin nicht; bei Appwrite führen Sie die obigen Anfragen als zwei Nutzer aus. Für Firebase habe ich dieselbe Art von Angriffen in [Firebase Security Rules Beispiele](https://madsalt.tech/de/blog/firebase-security-rules-examples/) durchgespielt.

**Grenzen.** Ein selbst gehostetes Appwrite 2.3.0 auf einem Laptop und ein lokaler Supabase-Stack, sechs Tage auseinander, ausgedachte Zeilen. Appwrite Cloud kann sich vom selbst gehosteten unterscheiden. Storage, Functions, Realtime, Auth-Einstellungen und Compliance-Zertifikate sind hier nicht verglichen.

Ein Backend für eine App mit privaten Daten gesucht?

Gern schaue ich mit Ihnen auf Ihr Zugriffsmodell. Ohne Verkaufsgespräch.

[Kontakt aufnehmen](https://madsalt.tech/de/#contact)
