Supabase Storage 公开存储桶:你的用户文件是否已经暴露?
头像可以公开,身份证件、发票和合同则不行。本文介绍如何检查 Supabase Storage 的存储桶和规则,以及如何在不影响上传的前提下修复。
阅读约 4 分钟
大多数用 Lovable、Bolt 等工具开发的应用,都允许用户上传点什么:头像、收据、合同,或用于验证身份的证件照片。在 Supabase 中,这些文件存放在 Storage 里,按存储桶(bucket)分类。
AI 工具创建存储桶时,经常把它设为公开,因为这样图片无需额外处理就能显示。对于 logo 来说没问题;但对于任何个人文件,这意味着任何拿到链接的人都能下载,有时甚至任何人都能列出桶里的全部文件。
本指南是我们 10 分钟 Supabase 自查的 Storage 篇,所需时间也差不多。
公开存储桶与私有存储桶
| 公开存储桶 | 私有存储桶(默认) | |
|---|---|---|
| 下载文件 | 任何拥有 URL 的人都可以,不检查任何规则 | 只有规则允许,或使用会过期的签名链接时才可以 |
| 上传、删除、移动 | 由你的规则控制 | 由你的规则控制 |
| 列出文件 | 由你的规则控制 | 由你的规则控制 |
从这张表可以得出两点:
- 公开存储桶在下载时完全绕过你的规则。如果文件路径可以预测(例如
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 步:查看 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的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 开发的应用
继续阅读
全部文章

应用安全
用 AI 开发的应用里有 OpenAI 或 Stripe 密钥?把它们移到服务器函数中
前端里的任何密钥,任何人都能复制。哪些密钥可以放在浏览器中,如何找出不该放的密钥,以及如何把它们移到 Supabase Edge Function。
阅读约 4 分钟

应用安全
在浏览器里发现了 Supabase 的密钥?第一个小时该做什么
前端中的 service role 密钥或 secret 密钥,会让任何人完全掌控你的数据库。正确的顺序是:作废、替换、查看日志,再修复根源。
阅读约 5 分钟