Vai al contenuto

Sicurezza Appwrite vs Supabase: stessi attacchi su entrambi

Visualizza come MarkdownApri in ClaudeApri in ChatGPTApri in Perplexity

Anche la domanda viene copiata. Se la chat si apre vuota, la incolli.

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. 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 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.

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.
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. 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.

Come si controlla un progetto Supabase?

Il Controllo sicurezza Supabase 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?. 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.

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

© 2026 Denys Zaichenko. Articolo originale: https://madsalt.tech/it/blog/appwrite-vs-supabase/