跳到正文

Supabase Storage 公开存储桶:你的用户文件是否已经暴露?

头像可以公开,身份证件、发票和合同则不行。本文介绍如何检查 Supabase Storage 的存储桶和规则,以及如何在不影响上传的前提下修复。

阅读约 4 分钟

大多数用 Lovable、Bolt 等工具开发的应用,都允许用户上传点什么:头像、收据、合同,或用于验证身份的证件照片。在 Supabase 中,这些文件存放在 Storage 里,按存储桶(bucket)分类。

AI 工具创建存储桶时,经常把它设为公开,因为这样图片无需额外处理就能显示。对于 logo 来说没问题;但对于任何个人文件,这意味着任何拿到链接的人都能下载,有时甚至任何人都能列出桶里的全部文件。

本指南是我们 10 分钟 Supabase 自查的 Storage 篇,所需时间也差不多。

公开存储桶与私有存储桶

公开存储桶 私有存储桶(默认)
下载文件 任何拥有 URL 的人都可以,不检查任何规则 只有规则允许,或使用会过期的签名链接时才可以
上传、删除、移动 由你的规则控制 由你的规则控制
列出文件 由你的规则控制 由你的规则控制

从这张表可以得出两点:

  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 步:查看 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 的 INSERT 规则,或面向 authenticated 却没有文件夹条件的规则。它允许任何用户在任意位置上传;如果 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,而是向 Supabase 请求一个会过期的签名 URL,例如一小时后过期。在 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)。在存储桶设置里只需一分钟,就能防止你的 Storage 变成别人的免费网盘。

如果你的应用处理证件、身份信息或付款记录,希望有人和你一起检查,我们免费的 Security Check 会以只读方式从外部检查 Storage,以及数据库规则、密钥和身份验证。

SupabaseRow Level SecurityAI 开发的应用

本文还有以下语言版本: EnglishPortuguêsEspañol日本語

全部文章