---
title: "Sicurezza Appwrite vs Supabase: stessi attacchi su entrambi"
description: "Stessi attacchi di accesso su Appwrite 2.3.0 e Supabase locale. Entrambi bloccano le tabelle nuove; cambiano come falliscono e cosa apre una chiave."
url: "https://madsalt.tech/it/blog/appwrite-vs-supabase/"
author: "Denys Zaichenko"
author_url: "https://madsalt.tech/it/about/"
language: "it"
published: 2026-10-30
---

# Sicurezza Appwrite vs Supabase: stessi attacchi su entrambi

In breve

Entrambi bloccano una tabella nuova di default: Appwrite ha risposto `401` a un ospite e a un utente autenticato, e Supabase risponde `401`/`403` finché non concede la tabella ai ruoli dell'API. La differenza si vede quando una regola è sbagliata o una richiesta passa da un utente all'altro. Su Supabase, un update sulla riga di un altro utente ha restituito `200 []` e una policy di insert troppo larga ha permesso a un utente di unirsi a un'altra azienda; su Appwrite, ogni tentativo tra utenti nella mia prova è finito con `401` o `404`, e un utente non poteva concedere la propria riga a nessuno se non a sé stesso o a tutti. Le chiavi server di Appwrite hanno scope; una secret key di Supabase salta ogni policy.

Sono Denys, ingegnere full-stack e di IA. La maggior parte delle app che costruisco o riparo gira su Supabase, e i clienti che stanno ancora scegliendo un backend mi chiedono come si confronta Appwrite sulla sicurezza. Un elenco di funzionalità non risponde, così ho inviato a entrambi le stesse richieste.

**Come ho testato.** Appwrite 2.3.0, self-hosted con Docker il 30 ottobre 2026, tramite la sua API REST come ospite, come due utenti autenticati A e B e con due chiavi server. Il lato Supabase è la mia prova su uno stack locale (CLI 2.109.1, Postgres 17.6) del 24 ottobre 2026 con gli stessi ruoli, descritta per intero in [Esempi di Supabase RLS policy](https://madsalt.tech/it/blog/supabase-rls-policy-examples/). Righe inventate su entrambi.

## Come proteggono i dati Appwrite e Supabase?

Supabase protegge le tabelle con Postgres: un grant decide se i ruoli dell'API raggiungono una tabella, e le policy di row level security, scritte in SQL, decidono quali righe ogni richiesta vede e scrive. Appwrite tiene i permessi accanto ai dati: una tabella ha un elenco di ruoli autorizzati a leggere, creare, modificare o eliminare, e con la row security attiva ogni riga porta il proprio elenco. Nella [documentazione sui permessi di Appwrite](https://appwrite.io/docs/products/databases/tablesdb/permissions) l'accesso viene solo concesso, mai negato, e a un utente serve a livello di tabella o di riga, non a entrambi.

Quindi la domanda non è quale dei due abbia un controllo degli accessi. Ce l'hanno entrambi. È che cosa ciascuno Le lascia sbagliare.

## Cosa fa ciascuno con una tabella nuova?

| Richiesta                          | Appwrite, nessun permesso impostato               | Supabase, RLS attiva, nessun grant (24 ott) |
| ---------------------------------- | ------------------------------------------------- | ------------------------------------------- |
| L'ospite elenca le righe           | `401` user_unauthorized                           | `401` permission denied                     |
| L'utente autenticato elenca righe  | `401` user_unauthorized                           | `403` permission denied                     |
| L'utente autenticato aggiunge riga | `401` No permissions provided for action 'create' | non testato                                 |

Una tabella appena creata è chiusa su entrambi. Su Supabase è una novità: le tabelle create dopo il 30 ottobre 2026 non ricevono più automaticamente i grant della Data API, come ho spiegato in [Supabase, cambio dei grant del 30 ottobre](https://madsalt.tech/it/blog/supabase-october-30-grant-change/).

## Cosa succede quando una tabella è aperta a tutti?

Le due impostazioni "aperte" non sono la stessa cosa. In Appwrite ho dato alla tabella un solo permesso, `read("any")`. Su Supabase lo stato aperto più comune è la RLS disattivata con i grant predefiniti ancora presenti.

| Richiesta di un ospite | Appwrite, `read("any")`  | Supabase, RLS disattivata, grant predefiniti (24 ott) |
| ---------------------- | ------------------------ | ----------------------------------------------------- |
| Elencare le righe      | `200`, entrambe le righe | `200`, entrambe le righe                              |
| Eliminare una riga     | `401`                    | `200`, entrambe le righe eliminate                    |

Il "pubblico" di Appwrite è di sola lettura, a meno che non conceda anche la scrittura. Una tabella Supabase con la RLS disattivata è leggibile e scrivibile da chiunque abbia la publishable key, ed è per questo che "RLS disabled in public" è la prima cosa che controllo.

## Come funzionano le "righe proprie" in ciascuno?

Su Appwrite ho attivato la row security e ho dato alla tabella un solo permesso, `create("users")`. Quando A ha creato una riga senza indicare permessi, Appwrite li ha impostati da sé: `read`, `update` e `delete` solo per l'utente A. Su Supabase lo stesso intento richiede quattro policy su `(select auth.uid()) = user_id`.

| Richiesta            | Appwrite (30 ott)   | Supabase (24 ott)               |
| -------------------- | ------------------- | ------------------------------- |
| L'ospite elenca      | `200`, 0 righe      | `401` (nessun grant per `anon`) |
| A elenca le righe    | `200`, 2 righe di A | `200`, 2 righe di A             |
| A legge la riga di B | `404` row_not_found | non restituita nell'elenco      |
| A modifica riga di B | `401`               | `200 []`                        |
| A elimina riga di B  | `401`               | `200 []`                        |

![Stessa richiesta, due backend. L'utente A modifica una riga dell'utente B, tramite l'API REST di ciascuno. Supabase, policy solo-proprietario: Utente A → modifica riga di B → 200 \[\] → 0 righe, nessun errore; Appwrite, row security attiva: Utente A → modifica riga di B → 401 → errore restituito.](https://madsalt.tech/charts/it/appwrite-vs-supabase.svg)

*Figura 1. I miei test: stack Supabase locale (CLI 2.109.1), 24 ottobre 2026; Appwrite 2.3.0 self-hosted, 30 ottobre 2026.*

Nessuno dei due ha fatto trapelare la riga di B. Cambia la risposta. Supabase filtra le righe che una policy nasconde, quindi un update che non ha trovato nulla è un successo con zero righe; un'app che considera "nessun errore" come "salvato" dice all'utente che la modifica è andata a buon fine. Appwrite rifiuta con un errore che l'app non può non vedere.

## Un utente può passare i propri dati a qualcun altro?

È qui che il codice generato sbaglia più spesso, quindi l'ho provato su entrambi.

| Richiesta di A                           | Appwrite                                               | Supabase (24 ott)                                                 |
| ---------------------------------------- | ------------------------------------------------------ | ----------------------------------------------------------------- |
| Creare una riga leggibile dall'utente B  | `401`, Permissions must be one of: any, users, user:A… | dipende dalla policy                                              |
| Cambiare la propria riga perché sia di B | `401`, stesso messaggio                                | `403` con un `with check` completo; `200` con `with check (true)` |
| Creare una riga leggibile da tutti       | `201`, e poi un ospite l'ha elencata                   | solo se una policy lo consente                                    |

Appwrite limita ciò che un utente può concedere ai ruoli che possiede lui stesso: `any`, `users`, il proprio utente e i propri team. Questo ha bloccato la mossa "do la mia riga a qualcun altro" senza alcuna regola da parte mia. Non ha impedito ad A di pubblicare la propria riga per tutti con `read("any")`. Se una riga non deve mai essere pubblica, non dovrebbe essere il client a impostarne i permessi; la crei da una funzione server.

Su Supabase il risultato è quello che dice la policy. Un `with check` completo ha rifiutato lo spostamento con `403`; `with check (true)`, che alcuni generatori scrivono, ha lasciato che A spostasse la riga nell'account di B.

## Un utente può unirsi all'azienda di qualcun altro?

Su Supabase l'accesso di team è di solito una tabella `memberships` più delle policy. Nella mia prova la policy di insert che molti tutorial suggeriscono, `with check (user_id = auth.uid())`, ha permesso ad A di aggiungersi all'azienda 2 con `201 Created` e poi di leggerne il progetto sui salari.

Appwrite ha i team integrati. B ha creato un team; A ha provato ad aggiungersi come membro e come proprietario. Entrambe le richieste hanno restituito `404 team_not_found`: A non riusciva nemmeno a vedere il team. L'appartenenza a un team la decide il server, non una regola che scrive Lei, quindi questa falla specifica non è disponibile. Le righe richiedono comunque permessi `read("team:…")` per essere condivise, e quelli li imposta Lei.

## Cosa apre una chiave server?

| Chiave                                  | Appwrite                            | Supabase (dal mio test anon vs service role) |
| --------------------------------------- | ----------------------------------- | -------------------------------------------- |
| Chiave server completa                  | `200`, tutte le 4 righe di entrambi | tutte e 3 le righe, RLS saltata              |
| Chiave con scope stretto (`users.read`) | `401`, missing scope rows.read      | nessuna chiave con scope                     |

Una secret key di Supabase corrisponde a `service_role`, che ha `BYPASSRLS`; cosa apre è in [anon key vs service role key](https://madsalt.tech/it/blog/supabase-anon-key-vs-service-role-key/). Anche una chiave di Appwrite ignora i permessi di riga, ma solo per gli scope con cui è stata creata, quindi una chiave per un lavoro che gestisce utenti non può leggere le Sue tabelle. Qualunque delle due chiavi in un bundle del browser è una fuga completa di tutto ciò che può raggiungere.

Due rilievi minori su Appwrite: un ospite e un utente autenticato che chiedevano l'elenco delle tabelle hanno ottenuto entrambi `401` (la chiamata richiede `tables.read`, che hanno solo le chiavi), e `read("users")` significa qualsiasi account autenticato, la stessa trappola di `auth.uid() is not null` su Supabase quando la registrazione è aperta.

## Quale dei due è più sicuro?

Nessuno è sicuro o insicuro di default; entrambi partivano chiusi. Differiscono per dove può nascondersi un errore:

| Domanda                                      | Appwrite                              | Supabase                                        |
| -------------------------------------------- | ------------------------------------- | ----------------------------------------------- |
| Dove vivono le regole di accesso             | Elenchi di ruoli su tabelle e righe   | Grant e policy SQL                              |
| Una scrittura tra utenti negata              | Errore (`401` / `404`)                | `200` con zero righe per update e delete        |
| Concedere righe ad altri                     | Solo ai ruoli che l'utente possiede   | Quello che consente la policy                   |
| Regole sui valori delle colonne, livello MFA | Non nei permessi; serve codice server | Nella policy, p. es. `aal2` per righe sensibili |
| Chiavi server                                | Con scope                             | La secret key salta tutte le policy             |

Il modello di Appwrite è più stretto, e nella mia prova il modello stretto ha rifiutato ogni trucco tra utenti senza che scrivessi nulla. Il modello di Supabase è SQL, quindi può esprimere regole che i permessi di Appwrite non possono, come "solo con MFA" o "solo righe delle aziende a cui appartengo", e può anche esprimere la regola sbagliata. Se il Suo team non leggerà le policy, i default di Appwrite perdonano di più. Se servono regole che dipendono dai dati, Supabase Le permette di scriverle, e poi vanno testate; il metodo che uso è in [come testare la Supabase RLS](https://madsalt.tech/it/blog/how-to-test-supabase-rls/).

## Come si controlla un progetto Supabase?

Il [Controllo sicurezza Supabase](https://madsalt.tech/it/tools/supabase-security-check/) gratuito legge il progetto in sola lettura e segnala le tabelle con la RLS disattivata, le policy sempre vere, le policy di scrittura per gli ospiti e i grant che concedono più di quanto le policy lascino intendere. Cosa Supabase copre per Lei e cosa Le lascia è in [Supabase è sicuro?](https://madsalt.tech/it/blog/is-supabase-secure/). Non esiste un equivalente per Appwrite; per Appwrite, esegua le richieste qui sopra come due utenti. Per Firebase ho eseguito lo stesso tipo di attacchi in [esempi di regole di sicurezza Firebase](https://madsalt.tech/it/blog/firebase-security-rules-examples/).

**Limiti.** Un solo Appwrite 2.3.0 self-hosted su un portatile e un solo stack Supabase locale, a sei giorni di distanza, con righe inventate. Appwrite Cloud potrebbe differire dal self-hosted. Storage, funzioni, realtime, impostazioni di autenticazione e certificazioni di conformità non sono confrontati qui.

Sta scegliendo un backend per un'app con dati privati?

Guardo volentieri il Suo modello di accesso insieme a Lei. Nessuna vendita.

[Mi contatti](https://madsalt.tech/it/#contact)
