本文へ移動

Supabase Storage の公開バケット:ユーザーのファイルが丸見えになっていませんか?

アバター画像は公開でも構いませんが、身分証、請求書、契約書は違います。Supabase Storage のバケットとルールの確認方法と、アップロードを壊さずに直す手順を解説します。

5分で読めます

Lovable や Bolt などで作ったアプリの多くは、ユーザーに何かをアップロードさせます。プロフィール写真、領収書、契約書、本人確認のための身分証の写真などです。Supabase では、これらのファイルはバケットに分けて Storage に保存されます。

AIツールがバケットを作るとき、公開(public) に設定することがよくあります。そのほうが手間をかけずに画像が表示されるからです。ロゴならそれで問題ありません。しかし個人に関わるファイルの場合、リンクを知っている人なら誰でもダウンロードでき、場合によってはバケット内のファイルを誰でも一覧表示できてしまいます。

このガイドは、10分でできる Supabase チェックの Storage 編です。所要時間もほぼ同じです。

公開バケットと非公開バケット

公開バケット 非公開バケット(初期設定)
ファイルのダウンロード URL を知っていれば誰でも可能。ルールは確認されない ルールが許可した場合、または期限付きの署名付きリンクでのみ可能
アップロード・削除・移動 ルールで制御される ルールで制御される
ファイルの一覧表示 ルールで制御される ルールで制御される

この表から、2つのことがわかります。

  1. 公開バケットでは、ダウンロード時にルールがまったく適用されません。ファイルのパスが推測しやすい場合(invoices/1043.pdf、ids/user-15.jpg など)、第三者でも当てられてしまいます。
  2. バケットを非公開にしても、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 のルールを読む

storage.objects のすべてのルール
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で作ったアプリ

この記事は次の言語でも読めます: EnglishPortuguêsEspañol中文

記事一覧