Supabase Storage の公開バケット:ユーザーのファイルが丸見えになっていませんか?
アバター画像は公開でも構いませんが、身分証、請求書、契約書は違います。Supabase Storage のバケットとルールの確認方法と、アップロードを壊さずに直す手順を解説します。
5分で読めます
Lovable や Bolt などで作ったアプリの多くは、ユーザーに何かをアップロードさせます。プロフィール写真、領収書、契約書、本人確認のための身分証の写真などです。Supabase では、これらのファイルはバケットに分けて Storage に保存されます。
AIツールがバケットを作るとき、公開(public) に設定することがよくあります。そのほうが手間をかけずに画像が表示されるからです。ロゴならそれで問題ありません。しかし個人に関わるファイルの場合、リンクを知っている人なら誰でもダウンロードでき、場合によってはバケット内のファイルを誰でも一覧表示できてしまいます。
このガイドは、10分でできる Supabase チェックの Storage 編です。所要時間もほぼ同じです。
公開バケットと非公開バケット
| 公開バケット | 非公開バケット(初期設定) | |
|---|---|---|
| ファイルのダウンロード | URL を知っていれば誰でも可能。ルールは確認されない | ルールが許可した場合、または期限付きの署名付きリンクでのみ可能 |
| アップロード・削除・移動 | ルールで制御される | ルールで制御される |
| ファイルの一覧表示 | ルールで制御される | ルールで制御される |
この表から、2つのことがわかります。
- 公開バケットでは、ダウンロード時にルールがまったく適用されません。ファイルのパスが推測しやすい場合(
invoices/1043.pdf、ids/user-15.jpgなど)、第三者でも当てられてしまいます。 - バケットを非公開にしても、
storage.objectsのルールが誰にでもすべての読み取りを許可していれば意味がありません。Storage のルールはこのテーブルに対する通常の Row Level Security ポリシーであり、甘いルールがあれば非公開バケットも同じように開いてしまいます。
ステップ1:バケットを一覧にする
Supabase のダッシュボードで SQL Editor を開き、次を実行します。
select id, public, file_size_limit, allowed_mime_types
from storage.buckets
order by public desc, id;public = true の行それぞれについて、「このバケットのファイルがすべて検索エンジンに出ても問題ないか」を考えてください。商品画像なら問題ありません。ユーザーが自分自身について登録したものなら、問題があります。
最後の2列も確認します。制限が空欄なら、アップロードを許可された人は誰でも、どんなサイズ・どんな種類のファイルでも、あなたの費用負担でプロジェクトに保存できます。
ステップ2:Storage のルールを読む
select policyname, cmd, roles, qual, with_check
from pg_policies
where schemaname = 'storage' and tablename = 'objects'
order by cmd, policyname;注意すべき点は次のとおりです。
anonやpublic向けのSELECTルールで、条件がbucket_id = '…'だけのもの。誰でもバケット内のすべてのファイルを一覧表示し、読み取れます。anon向け、またはフォルダの条件がないauthenticated向けのINSERTルール。どのユーザーでもどこにでもアップロードでき、UPDATEルールも緩ければ、ほかのユーザーのパスに上書きすることもできます。- ファイルを所有者と結び付けていない
DELETEルール。
ダッシュボードの Security Advisor も、こうした問題の一部を指摘します。変更のたびに確認してください。
ステップ3:外部から確かめる
プロジェクトの URL と公開キー(Project Settings → API Keys で確認できます)だけを使い、第三者と同じ方法でバケットの一覧取得を試みます。
curl -X POST "https://YOUR-PROJECT.supabase.co/storage/v1/object/list/BUCKET_NAME" \
-H "apikey: YOUR_PUBLIC_KEY" \
-H "Content-Type: application/json" \
-d '{"prefix": "", "limit": 20}'非公開のファイルを保存しているバケットでファイル名が返ってきたら、一覧表示が開放されています。個人に関わるファイルがあるバケットすべてで繰り返してください。
直し方
個人に関わるファイルを非公開バケットに移す
個人を特定できるものや業務データを含むものはすべて、非公開バケット(例:documents)に保存します。公開バケットは、本当に公開してよいファイルだけに使います。
次に、アプリでのファイルの表示方法を変えます。恒久的な公開 URL ではなく、たとえば1時間で期限切れになる署名付き URL を Supabase から取得します。JavaScript クライアントでは createSignedUrl(path, 3600) です。切り替えた時点で古い公開リンクは使えなくなるため、この変更はコードの変更と同時に行い、先に済ませないようにしてください。
ユーザーごとに専用のフォルダを用意する
一般的なのは、各ファイルをユーザー ID 名のフォルダ(例:documents/8f1c…/contract.pdf)にアップロードし、それを確認するルールを書く方法です。
create policy "Read own documents"
on storage.objects for select
to authenticated
using (
bucket_id = 'documents'
and (storage.foldername(name))[1] = (select auth.uid()::text)
);
create policy "Upload to own folder"
on storage.objects for insert
to authenticated
with check (
bucket_id = 'documents'
and (storage.foldername(name))[1] = (select auth.uid()::text)
);ユーザーがファイルを差し替えたり削除したりできる場合は、同じ条件で update と delete のルールも追加します。上書きを有効にしてアップロードする場合は、select、insert、update のすべてが必要です。
公開バケットは「読めるが一覧は出ない」状態にする
アバター用の公開バケットでは、ダウンロードのための SELECT ルールは不要です。公開 URL はルールを経由しないからです。アプリが本当に中身を一覧表示する必要がない限り、公開バケットにある anon 向けの広すぎる SELECT ルールは削除してください。
制限を設定する
すべてのバケットに、最大ファイルサイズと許可するファイル形式(例:image/png、image/jpeg、application/pdf)を設定します。バケットの設定画面で1分ほどで終わり、あなたの Storage が他人の無料ホスティングとして使われるのを防げます。
書類、身分証、決済記録などを扱うアプリで、一緒に確認してほしい場合は、無料の Security Check をご利用ください。データベースのルール、キー、認証とあわせて Storage も、外部から読み取り専用で確認します。
SupabaseRow Level SecurityAIで作ったアプリ
あわせて読みたい
記事一覧
アプリのセキュリティ
Lovable で作ったアプリを公開する前に:リリース前のセキュリティチェックリスト
実際のユーザーとデータが集まる前に行う15の確認。データベースのルール、キー、ストレージ、ログイン、決済、関数、バックアップ。ほとんどは数分で終わります。
5分で読めます

アプリのセキュリティ
AIで作ったアプリに OpenAI や Stripe のキーが入っていませんか? サーバー側の関数に移す方法
フロントエンドにあるキーは誰でもコピーできます。ブラウザに置いてよいキーとそうでないキーの見分け方、見つけ方、Supabase の Edge Function への移し方を解説します。
4分で読めます

アプリのセキュリティ
Supabase のシークレットキーがブラウザから見えていたら? 最初の1時間でやること
フロントエンドに service role キーやシークレットキーがあると、誰でもデータベースを自由に操作できます。無効化、差し替え、ログ確認、原因の修正という正しい順番を解説します。
6分で読めます