---
title: "Segurança Appwrite vs Supabase: os mesmos ataques nos dois"
description: "Os mesmos ataques de acesso no Appwrite 2.3.0 e no Supabase local. Ambos bloqueiam tabelas novas; muda a forma como falham e o que uma chave abre."
url: "https://madsalt.tech/pt/blog/appwrite-vs-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/pt/about/"
language: "pt"
published: 2026-10-30
---

# Segurança Appwrite vs Supabase: os mesmos ataques nos dois

Em resumo

Ambos bloqueiam uma tabela nova por omissão: o Appwrite respondeu `401` a um convidado e a um utilizador autenticado, e o Supabase responde `401`/`403` até dar a tabela aos roles da API. A diferença aparece quando uma regra está errada ou um pedido passa de um utilizador para outro. No Supabase, um update na linha de outro utilizador devolveu `200 []` e uma policy de insert demasiado larga deixou um utilizador juntar-se a outra empresa; no Appwrite, todas as tentativas entre utilizadores na minha execução acabaram em `401` ou `404`, e um utilizador não podia dar a sua linha a ninguém senão a si próprio ou a todos. As chaves de servidor do Appwrite têm scopes; uma secret key do Supabase ignora todas as policies.

Sou o Denys, engenheiro full-stack e de IA. A maioria das apps que construo ou corrijo corre em Supabase, e os clientes que ainda estão a escolher um backend perguntam-me como se compara o Appwrite em segurança. Uma lista de funcionalidades não responde a isso, por isso enviei aos dois os mesmos pedidos.

**Como testei.** Appwrite 2.3.0, self-hosted com Docker a 30 de outubro de 2026, pela sua API REST como convidado, como dois utilizadores autenticados A e B e com duas chaves de servidor. O lado Supabase é a minha execução numa stack local (CLI 2.109.1, Postgres 17.6) de 24 de outubro de 2026 com os mesmos roles, descrita por inteiro em [Exemplos de RLS policy no Supabase](https://madsalt.tech/pt/blog/supabase-rls-policy-examples/). Linhas inventadas nos dois.

## Como protegem os dados o Appwrite e o Supabase?

O Supabase protege as tabelas com o Postgres: um grant decide se os roles da API chegam sequer a uma tabela, e as policies de row level security, escritas em SQL, decidem que linhas cada pedido vê e escreve. O Appwrite guarda as permissões junto dos dados: uma tabela tem uma lista de roles que podem ler, criar, atualizar ou apagar, e com a row security ligada cada linha traz a sua própria lista. Na [documentação de permissões do Appwrite](https://appwrite.io/docs/products/databases/tablesdb/permissions) o acesso só é concedido, nunca negado, e o utilizador precisa dele ao nível da tabela ou da linha, não dos dois.

Portanto, a pergunta não é qual dos dois tem controlo de acessos. Ambos têm. É o que cada um lhe deixa errar.

## O que faz cada um com uma tabela nova?

| Pedido                                | Appwrite, sem permissões definidas                | Supabase, RLS ligado, sem grant (24 out) |
| ------------------------------------- | ------------------------------------------------- | ---------------------------------------- |
| Convidado lista linhas                | `401` user_unauthorized                           | `401` permission denied                  |
| Utilizador autenticado lista linhas   | `401` user_unauthorized                           | `403` permission denied                  |
| Utilizador autenticado adiciona linha | `401` No permissions provided for action 'create' | não testado                              |

Uma tabela recém-criada está fechada nos dois. No Supabase isso é novidade: as tabelas criadas depois de 30 de outubro de 2026 deixam de receber grants da Data API automaticamente, o que expliquei em [Supabase, 30 de outubro: o que falha com os novos grants](https://madsalt.tech/pt/blog/supabase-october-30-grant-change/).

## O que acontece quando uma tabela está aberta a todos?

As duas definições «abertas» não são a mesma coisa. No Appwrite dei à tabela uma só permissão, `read("any")`. No Supabase, o estado aberto mais comum é o RLS desligado com os grants por omissão ainda no sítio.

| Pedido de um convidado | Appwrite, `read("any")` | Supabase, RLS desligado, grants por omissão (24 out) |
| ---------------------- | ----------------------- | ---------------------------------------------------- |
| Listar linhas          | `200`, as duas linhas   | `200`, as duas linhas                                |
| Apagar uma linha       | `401`                   | `200`, as duas linhas apagadas                       |

O «público» do Appwrite é só de leitura, a não ser que dê também escrita. Uma tabela Supabase com RLS desligado é legível e gravável por qualquer pessoa com a publishable key, e é por isso que «RLS disabled in public» é a primeira coisa que verifico.

## Como funcionam as «linhas próprias» em cada um?

No Appwrite liguei a row security e dei à tabela uma só permissão, `create("users")`. Quando A criou uma linha sem indicar permissões, o Appwrite definiu-as por si: `read`, `update` e `delete` só para o utilizador A. No Supabase a mesma intenção são quatro policies sobre `(select auth.uid()) = user_id`.

| Pedido                | Appwrite (30 out)       | Supabase (24 out)             |
| --------------------- | ----------------------- | ----------------------------- |
| Convidado lista       | `200`, 0 linhas         | `401` (sem grant para `anon`) |
| A lista linhas        | `200`, as 2 linhas de A | `200`, as 2 linhas de A       |
| A lê a linha de B     | `404` row_not_found     | não devolvida na lista        |
| A atualiza linha de B | `401`                   | `200 []`                      |
| A apaga linha de B    | `401`                   | `200 []`                      |

![O mesmo pedido, dois backends. O utilizador A edita uma linha do utilizador B, pela API REST de cada um. Supabase, policy só do dono: Utilizador A → edita linha de B → 200 \[\] → 0 linhas, sem erro; Appwrite, row security ligada: Utilizador A → edita linha de B → 401 → erro devolvido.](https://madsalt.tech/charts/pt/appwrite-vs-supabase.svg)

*Figura 1. Os meus testes: stack Supabase local (CLI 2.109.1), 24 de outubro de 2026; Appwrite 2.3.0 self-hosted, 30 de outubro de 2026.*

Nenhum deixou vazar a linha de B. A diferença é a resposta. O Supabase filtra as linhas que uma policy esconde, por isso um update que não apanhou nada é um sucesso com zero linhas; uma app que trata «sem erro» como «guardado» diz ao utilizador que a edição funcionou. O Appwrite recusa com um erro que a app não pode deixar de ver.

## Um utilizador pode passar os seus dados a outra pessoa?

É aqui que o código gerado mais falha, por isso testei nos dois.

| Pedido de A                             | Appwrite                                               | Supabase (24 out)                                                 |
| --------------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------- |
| Criar linha legível pelo utilizador B   | `401`, Permissions must be one of: any, users, user:A… | depende da policy                                                 |
| Mudar a própria linha para B ser o dono | `401`, a mesma mensagem                                | `403` com um `with check` completo; `200` com `with check (true)` |
| Criar linha legível por todos           | `201`, e um convidado listou-a a seguir                | só se uma policy o permitir                                       |

O Appwrite limita o que um utilizador pode conceder aos roles que ele próprio tem: `any`, `users`, o seu utilizador e as suas equipas. Isso bloqueou o gesto «dar a minha linha a outra pessoa» sem qualquer regra minha. Não impediu A de publicar a sua própria linha para todos com `read("any")`. Se uma linha nunca deve ser pública, não deve ser o cliente a definir as suas permissões; crie-a a partir de uma função de servidor.

No Supabase o resultado é o que a policy disser. Um `with check` completo recusou a mudança com `403`; `with check (true)`, que alguns geradores escrevem, deixou A mover a linha para a conta de B.

## Um utilizador pode juntar-se à empresa de outra pessoa?

No Supabase, o acesso por equipa costuma ser uma tabela `memberships` mais policies. Na minha execução, a policy de insert que muitos tutoriais sugerem, `with check (user_id = auth.uid())`, deixou A adicionar-se à empresa 2 com `201 Created` e depois ler o seu projeto de salários.

O Appwrite tem equipas integradas. B criou uma equipa; A tentou adicionar-se como membro e como dono. Os dois pedidos devolveram `404 team_not_found`: A nem sequer via a equipa. A pertença a uma equipa é decidida pelo servidor, não por uma regra que escreva, por isso esta fuga em concreto não existe. As linhas continuam a precisar de permissões `read("team:…")` para serem partilhadas, e essas define-as você.

## O que abre uma chave de servidor?

| Chave                                   | Appwrite                                 | Supabase (do meu teste anon vs service role) |
| --------------------------------------- | ---------------------------------------- | -------------------------------------------- |
| Chave de servidor completa              | `200`, as 4 linhas dos dois utilizadores | as 3 linhas, RLS ignorado                    |
| Chave com scope estreito (`users.read`) | `401`, missing scope rows.read           | não há chaves com scope                      |

Uma secret key do Supabase corresponde a `service_role`, que tem `BYPASSRLS`; o que abre está em [anon key vs service role key](https://madsalt.tech/pt/blog/supabase-anon-key-vs-service-role-key/). Uma chave do Appwrite também ignora as permissões de linha, mas só para os scopes com que foi criada, por isso uma chave para uma tarefa que gere utilizadores não consegue ler as suas tabelas. Qualquer das chaves num bundle de browser é uma fuga total de tudo o que ela alcança.

Duas constatações menores sobre o Appwrite: um convidado e um utilizador autenticado a pedir a lista de tabelas receberam ambos `401` (a chamada precisa de `tables.read`, que só as chaves têm), e `read("users")` significa qualquer conta autenticada, a mesma armadilha de `auth.uid() is not null` no Supabase quando o registo está aberto.

## Qual é mais seguro?

Nenhum é seguro ou inseguro por omissão; ambos partiram fechados. Diferem no sítio onde um erro se pode esconder:

| Pergunta                                   | Appwrite                                     | Supabase                                       |
| ------------------------------------------ | -------------------------------------------- | ---------------------------------------------- |
| Onde vivem as regras de acesso             | Listas de roles em tabelas e linhas          | Grants e policies em SQL                       |
| Uma escrita negada entre utilizadores      | Erro (`401` / `404`)                         | `200` com zero linhas em update e delete       |
| Dar linhas a outros                        | Só aos roles que o utilizador tem            | O que a policy permitir                        |
| Regras sobre valores de colunas, nível MFA | Não nas permissões; exige código de servidor | Na policy, p. ex. `aal2` para linhas sensíveis |
| Chaves de servidor                         | Com scope                                    | A secret key ignora todas as policies          |

O modelo do Appwrite é mais estreito, e na minha execução o modelo estreito recusou todos os truques entre utilizadores sem eu escrever nada. O modelo do Supabase é SQL, por isso consegue exprimir regras que as permissões do Appwrite não conseguem, como «só com MFA» ou «só linhas das empresas a que pertenço», e também consegue exprimir a regra errada. Se a sua equipa não vai ler as policies, os valores por omissão do Appwrite perdoam mais. Se precisa de regras que dependam dos dados, o Supabase deixa-o escrevê-las, e depois têm de ser testadas; o método que uso está em [como testar o Supabase RLS](https://madsalt.tech/pt/blog/how-to-test-supabase-rls/).

## Como verificar um projeto Supabase?

A [Auditoria de segurança Supabase](https://madsalt.tech/pt/tools/supabase-security-check/) gratuita lê o seu projeto só em leitura e assinala tabelas com RLS desligado, policies sempre verdadeiras, policies de escrita para convidados e grants que dão mais do que as policies deixam supor. O que o Supabase cobre por si e o que lhe deixa a si está em [o Supabase é seguro?](https://madsalt.tech/pt/blog/is-supabase-secure/). Não existe equivalente para o Appwrite; no Appwrite, corra os pedidos acima como dois utilizadores. Para o Firebase, corri o mesmo tipo de ataques em [exemplos de regras de segurança do Firebase](https://madsalt.tech/pt/blog/firebase-security-rules-examples/).

**Limites.** Um Appwrite 2.3.0 self-hosted num portátil e uma stack Supabase local, com seis dias de intervalo, com linhas inventadas. O Appwrite Cloud pode diferir do self-hosted. Storage, funções, realtime, definições de auth e certificados de conformidade não são comparados aqui.

A escolher um backend para uma app com dados privados?

Terei gosto em ver o seu modelo de acessos consigo. Sem pitch.

[Fale comigo](https://madsalt.tech/pt/#contact)
