Vai al contenuto

Basi della sicurezza

Il tuo bucket Supabase Storage è pubblico. È un problema?

Un bucket Supabase Storage pubblico significa che chi ha l'URL di un file può aprirlo. Non significa che qualcuno possa elencare quello che c'è dentro.

Vlad Tkachenko8 min di lettura
Documenti dentro un contenitore. Una barra copre quasi tutta l'apertura e si interrompe prima della fine, lasciandone quattro nel varco.

In breve

  • Un bucket Supabase Storage pubblico significa una cosa sola: chi ha l'URL di un file può aprirlo senza accedere. Non dice nulla su cos'altro possa vedere lì dentro.
  • A concedere l'elenco è una policy di accesso e non l'interruttore pubblico, quindi un bucket privato può essere elencabile e uno pubblico può non esserlo.
  • L'elenco è quello da sistemare oggi, perché risparmia a uno sconosciuto la fatica di indovinare anche un solo nome di file.

Qualcuno ha aperto la tua app, ci ha frugato dentro per un minuto e ti ha mandato un messaggio: il tuo bucket Supabase Storage è pubblico, e chiunque può elencare quello che c'è dentro.

Sono due affermazioni distinte. Una riguarda con ogni probabilità un'impostazione che hai scelto apposta e che faresti bene a tenere. L'altra vale un pomeriggio.

Ecco la parte che guida dopo guida racconta male: pubblico ed elencabile sono due interruttori diversi, e quello che preoccupa non è quello che tutti ti dicono di spostare. Rendere privato un bucket chiude il primo. Può lasciare il secondo spalancato, e un bucket che non è mai stato pubblico in vita sua può essere elencabile questo pomeriggio.

Un bucket Supabase Storage pubblico è un problema di sicurezza?

Di per sé no. Significa una cosa ben precisa, e quella cosa molto spesso è proprio quella che volevi.

Rendere pubblico un bucket dà a ogni file che contiene un URL che funziona senza accedere. È tutta qui la funzione. È così che si carica l'avatar sotto un commento per chi non ha un account, così compare il tuo logo in una e-mail, così vede la foto di un prodotto chi sta ancora decidendo se iscriversi.

Un bucket pubblico assomiglia più a un numero di telefono fuori elenco che a una porta non chiusa a chiave. La linea risponde a chiunque lo componga, e non c'è nessun elenco in cui cercarlo. Se questo vada bene dipende da una sola domanda: quanto facilmente qualcuno arriverebbe a quel numero senza che glielo diano.

Supabase crea i bucket nuovi come privati e mette un avviso accanto all'interruttore. Un bucket pubblico è quindi qualcosa che qualcuno ha acceso: tu, o il tuo builder, per far funzionare una funzione di caricamento.

Cosa accende davvero l'interruttore pubblico

Una riga della tabella qui sotto, e nient'altro al suo interno.

Cosa prova a fare qualcunoBucket pubblicoBucket privato
Aprire un file di cui ha già l'URL esattoFunziona, senza accedereServe una policy o un link firmato
Chiedere l'elenco del contenuto del bucketSolo se una policy lo permetteSolo se una policy lo permette
Caricare un fileSolo se una policy lo permetteSolo se una policy lo permette
Cancellare o sostituire un fileSolo se una policy lo permetteSolo se una policy lo permette

La pagina di aiuto di Supabase lo dice con tutta la chiarezza possibile: un bucket pubblico significa che esiste un URL pubblico con cui scaricare il file, e qualsiasi altra operazione deve comunque soddisfare le policy di quel bucket.

L'interruttore è quindi un comando più piccolo di quanto il nome faccia credere. Tre di quelle quattro righe si decidono altrove, sotto Storage → Policies, e il messaggio che hai ricevuto riguardava quasi di sicuro la seconda.

L'interruttore in alto cambia una cosa. Il riquadro sotto decide se uno sconosciuto ottiene l'elenco, e lo fa allo stesso modo che il bucket sia pubblico o privato.

Perché un bucket privato può restare elencabile

Perché a concedere l'elenco è una policy di accesso, e la policy che quasi tutti incollano lo concede a chiunque.

Ogni lettura da Storage passa da Row Level Security su una tabella chiamata storage.objects. Elencare un bucket è una lettura, archiviata sotto SELECT, esattamente come lo è scaricare un file. La guida rapida di Supabase mostra una policy di questa forma, ed è da lì che di solito arrivano le copie:

create policy "Public Access"
  on storage.objects for select
  using ( bucket_id = 'public' );

Leggi cosa dice. Qualsiasi richiesta può leggere qualsiasi cosa in quel bucket. Non chiede chi sta chiedendo, e non chiede se il file abbia a che fare con chi lo chiede. Leggere comprende scaricare un file, e comprende consegnare l'elenco dei file, perché è lo stesso permesso con due cappelli.

L'interruttore del bucket non entra mai in gioco. Una policy così su un bucket privato lo rende elencabile, e il pannello continuerà a descriverlo, con ragione, come privato.

La parola che fa il danno è «qualsiasi». Una richiesta che porta la chiave pubblicabile spedita dentro il codice della tua app soddisfa quella policy senza sforzo, e quella chiave è fatta per essere leggibile da tutti, che è precisamente il motivo per cui a lavorare dovrebbe essere la policy. È la stessa forma del problema qui accanto, dove Row Level Security è accesa e lascia passare tutto lo stesso.

Cosa ottiene qualcuno da un elenco dei tuoi file

Nomi, soprattutto. Che è parecchio più di quanto sembri.

I nomi dei file tendono a descriverne il contenuto, perché qualcuno li ha scelti pensando a cosa ci fosse dentro: fattura-marzo-acme.pdf, passaporto-fronte.jpg, stipendi-finale-v2.xlsx. Un elenco di quelli è un riassunto discreto della tua attività, e la sua lunghezza dice più o meno quanti clienti hai. Se il bucket è anche pubblico, ogni nome di quell'elenco è un collegamento che funziona.

Torniamo al numero di telefono. Stare fuori elenco vale qualcosa fino al giorno in cui l'elenco esce, e dopo non ha mai contato quanto fosse difficile indovinare il numero.

Senza elenco uno sconosciuto deve arrivare a un nome di file in qualche modo. Con l'elenco gli vengono consegnati tutti i nomi che hai.

Il modo in cui questo viene a galla è ordinario. Un documento che appartiene a un cliente compare davanti a un altro. Qualcuno ti ripete il nome di un file che non aveva modo di conoscere. Una cartella di caricamenti viene copiata per intero da un crawler automatico che stava leggendo tutti i progetti Supabase che riusciva a trovare.

La nostra scansione gratuita chiede un elenco al tuo Storage usando solo la chiave che è già nel codice della tua app, e ti dice quali bucket hanno risposto con del contenuto. Legge nomi e non scarica mai un file. Ci mette una ventina di secondi e non richiede un account: scansiona la tua app.

Come capire in quale dei due casi sei

Due controlli nel pannello Supabase, e il secondo riguarda quello di cui parlava davvero il messaggio.

Storage → Buckets. Quelli pubblici sono etichettati come pubblici. Per ciascuno chiediti se proprio ogni singolo file che contiene è qualcosa che mostreresti a uno sconosciuto. Non la maggior parte dei file. Ognuno, compreso quello che ci finirà la settimana prossima da una funzione che non hai ancora costruito.

Storage → Policies. Leggi ogni policy SELECT che tocca il bucket. Una condizione che nomina solo il bucket lascia che chiunque lo legga. Una condizione che confronta il proprietario del file con chi fa la richiesta sta facendo un lavoro vero. Un elenco di policy vuoto su un bucket privato significa che non viene letto proprio nulla, il che è restrittivo e sicuro.

Se la tua app deve mostrare un file privato alla persona giusta, lo strumento è un link firmato: il tuo server chiede a Supabase un URL che funziona per un numero fissato di minuti e poi smette. Così il file resta privato e arriva comunque sulla pagina.

Quando pubblico è la risposta giusta

Più spesso di quanto un articolo di sicurezza di solito ammetta, ed è giusto dirlo.

Se il file è destinato a tutti, pubblico è corretto, e aggirarlo ti compra un'app più lenta e altro codice da mantenere. Avatar, loghi, immagini di copertina, tutto ciò che un visitatore non autenticato deve vedere: dentro un bucket pubblico, e poi non pensarci più.

L'unica cosa da fare anche lì è lasciare l'elenco spento. Un bucket pubblico pieno di avatar è una funzione. Lo stesso bucket che consegna l'elenco completo di tutti quelli che ne hanno caricato uno è un'altra cosa, e non l'hai chiesta.

Se il file appartiene a una persona precisa, va in un bucket privato con una policy che controlla chi sta chiedendo, e arriva sulla pagina tramite un link firmato. Il test è una domanda sola: per chi è questo file? Per tutti, o per un account con un nome.

Cosa fare questa settimana

Cosa fare

  • Apri Storage → Buckets e annota quali sono pubblici. Per ciascuno decidi se contiene file destinati a tutti o file destinati a una sola persona.
  • Leggi ogni policy SELECT su storage.objects. Una condizione che nomina solo il bucket consegna l'elenco dei file a chiunque; è quella che ha reso vero il messaggio che hai ricevuto.
  • Sposta tutto ciò che appartiene a un singolo utente in un bucket privato e servilo con link firmati, che scadono da soli.
  • Smetti di dare ai file caricati il nome del loro contenuto. Un identificatore casuale non costa nulla e fa sì che un elenco trapelato consegni molto meno di quanto avrebbe consegnato.
  • Fai il backup dello Storage separatamente da quello del database. I tuoi caricamenti non stanno né nel codice né nelle tabelle Postgres, quindi nulla di ciò che copia quei due sta copiando i tuoi file.

Apri Storage → Policies nel tuo progetto e leggi cosa c'è scritto, prima di toccare un solo interruttore. Quella schermata è ciò che decide se la seconda metà del messaggio che hai ricevuto fosse vera. La checklist di sicurezza in 10 minuti lo copre insieme al resto di ciò che un'app appena lanciata tende a lasciare aperto, e se un bucket è risultato leggibile, cosa raggiunge uno sconosciuto nel tuo database è la lettura successiva, dato che la policy che consegna un elenco di file ha la stessa forma di quella che consegna una tabella.

FAQ

Mi hanno detto che il mio bucket Supabase Storage è pubblico. Devo renderlo privato?

Non prima di sapere cosa contiene. I bucket pubblici esistono per un motivo: foto profilo, loghi, immagini di prodotto e tutto ciò che la tua app mostra a un visitatore non autenticato. Per quei file pubblico è l'impostazione giusta. Per fatture, documenti d'identità, esportazioni o qualsiasi cosa appartenga a una persona precisa è quella sbagliata, e allora ti serve privato più link firmati. La domanda non è mai stata se pubblico sia un male. È se proprio quei file fossero pensati per essere visti da chiunque lo chieda.

Che differenza c'è tra un bucket pubblico e uno elencabile?

Pubblico decide se un file si apre per chi ha già il suo URL. Elencabile decide se uno sconosciuto può chiedere al tuo progetto l'intero contenuto di un bucket e ricevere una risposta. Si impostano in punti diversi del pannello Supabase, e il secondo è quello che trasforma una supposizione in un elenco.

Qualcuno può indovinare gli URL dei file nel mio bucket pubblico?

Dipende interamente da come li nomina la tua app. Se li carica con il nome originale, o con qualcosa di ordinato come fattura-4.pdf, allora sì, e costa pochissima fatica. Se li carica sotto un identificatore lungo e casuale, indovinare diventa impraticabile. È esattamente per questo che l'elenco pesa così tanto: elimina del tutto il passaggio dell'indovinare.

Come impedisco che qualcuno elenchi il mio bucket?

L'elenco passa da una policy Row Level Security sulla tabella storage.objects, quindi è lì che si sistema e non sul bucket. Apri Storage → Policies nel pannello Supabase e leggi ogni policy SELECT che tocca il bucket. Una la cui unica condizione è il nome del bucket lascia che qualsiasi richiesta lo legga, elenco dei file compreso. Restringila perché confronti il file con chi lo sta chiedendo, oppure rimuovila e distribuisci link firmati dal tuo server.

Il backup del database copre anche i file caricati?

No. Storage sta fuori dal tuo database Postgres, quindi un backup del database contiene le righe che puntano ai tuoi file e nessuno dei file. Ripristinarlo ti restituisce una tabella piena di collegamenti a cose che non ci sono più. I file caricati vanno copiati a parte, e quasi tutti lo scoprono il giorno in cui conta.

Scritto da

Vlad Tkachenko

Fondatore di Reeve

Passo le giornate a guardare app costruite con Lovable, Bolt, v0, Cursor e Replit, e la breve lista di errori che vi ricompaiono di continuo.

Altro sull'autore

Da leggere dopo

Non sai come sta messa la tua app?

Esegui una scansione gratuita e ottieni un voto chiaro da A a F in una ventina di secondi. Senza account e senza carta.

Scansiona la tua app gratis

Controllo esterno automatizzato, non un audit completo. L'assenza di risultati non è una garanzia di sicurezza.