// docs / baas security / supabase storage
Danh sách kiểm tra bảo mật storage bucket Supabase: 22 mục
Supabase Storage là một lớp bao mỏng quanh một bucket tương thích S3 cộng với cùng mô hình Row-Level Security như cơ sở dữ liệu. Điều đó có nghĩa là cùng những cạm bẫy RLS ảnh hưởng đến các bảng cũng ảnh hưởng đến truy cập file — và một vài cạm bẫy đặc thù cho storage xuất hiện khi các công cụ AI lập trình kết nối các thao tác upload. Danh sách này gồm 22 mục trên năm phần: cấu hình bucket, policy RLS, xác thực upload, signed URL và vệ sinh vận hành. Mỗi mục có thể xác minh trong dưới 15 phút.
Mỗi mục bên dưới đều thiết yếu. Để hiểu cơ chế RLS nền tảng, xem Supabase RLS scanner. Để biết lớp phơi nhiễm key liền kề với storage, xem Supabase service role key bị lộ trong JavaScript.
Cấu hình bucket
Bắt đầu với mặc định đúng. Một bucket cấu hình sai sẽ rò rỉ file bất kể RLS của bạn có đúng hay không.
- Mặc định mọi bucket là private. Trong Supabase Dashboard → Storage → Buckets, đặt nút Public bucket ở trạng thái tắt trừ khi bạn có lý do rõ ràng (asset marketing, avatar công khai không có PII). Bucket công khai bỏ qua RLS cho thao tác đọc — bất kỳ ai có tên bucket đều có thể liệt kê và tải xuống.
- Đặt giới hạn kích thước file cứng trên mọi bucket. Dashboard → Cài đặt bucket → Giới hạn kích thước file. 50 MB là mặc định hợp lý cho upload của người dùng; tăng nó có chủ ý cho các trường hợp dùng video / file lớn. Không có giới hạn, một upload độc hại duy nhất có thể làm cạn kiệt quota storage hoặc băng thông tháng của bạn.
- Giới hạn các loại MIME được phép cho mỗi bucket. Danh sách MIME types được phép — danh sách trắng rõ ràng, không phải danh sách đen.
image/jpeg,image/png,image/webpcho bucket chỉ chứa ảnh. Đừng cho phéptext/html,application/javascripthoặcimage/svg+xmltrong bucket nội dung người dùng — chúng thực thi trong trình duyệt khi được phục vụ qua signed URL. - Dùng một bucket cho mỗi loại nội dung, không phải một bucket chia sẻ. Cài đặt theo bucket (kích thước, loại MIME, policy RLS) là mức độ chi tiết mà bạn có. Một bucket
user-avatars, một bucketdocument-uploadsvà một bucketpublic-assetsdễ khóa hơn một bucket trộn lẫn. - Xác minh cấu hình CORS nếu upload từ frontend. Nếu người dùng upload trực tiếp từ trình duyệt đến một signed URL, CORS của bucket phải liệt kê origin production của bạn.
*chỉ chấp nhận được cho bucket công khai — không bao giờ cho bucket chứa PII của người dùng.
Policy RLS trên storage.objects
Supabase Storage lưu metadata file trong bảng storage.objects. RLS trên bảng đó kiểm soát ai có thể đọc, upload, cập nhật hoặc xóa file. Không có RLS, cờ public/private của bucket là sự bảo vệ duy nhất của bạn.
- Xác nhận RLS được bật trên storage.objects.
SELECT rowsecurity FROM pg_tables WHERE schemaname = 'storage' AND tablename = 'objects';phải trả vềtrue. Supabase bật nó mặc định trên các dự án mới; xác minh rằng nó chưa bị tắt. - Viết một policy SELECT giới hạn theo
auth.uid()cho bucket private.CREATE POLICY "users_read_own_files" ON storage.objects FOR SELECT USING (auth.uid()::text = (storage.foldername(name))[1]);. Quy ước là lưu file dưới dạng[user-id]/[filename]và dùngstorage.foldername()để trích xuất chủ sở hữu từ đường dẫn. - Viết một policy INSERT thực thi cùng quy ước đường dẫn.
CREATE POLICY "users_upload_own" ON storage.objects FOR INSERT WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);. Không có WITH CHECK, một người dùng đã xác thực có thể upload vào thư mục của người khác. - Thêm policy UPDATE và DELETE nếu ứng dụng hỗ trợ chỉnh sửa hoặc xóa file. Mỗi lệnh cần policy riêng. Bỏ qua DELETE có nghĩa là người dùng đã xác thực không thể xóa file của chính họ; bỏ qua UPDATE có nghĩa là ghi đè file âm thầm thất bại.
- Kiểm tra truy cập chéo người dùng trong hai phiên trình duyệt. Đăng nhập với tư cách User A, upload một file, sao chép đường dẫn. Đăng nhập với tư cách User B trong một trình duyệt khác, thử tải file qua REST API. Phản hồi phải là
403hoặc404, không bao giờ là200.
-- Confirm RLS on storage.objects
SELECT rowsecurity
FROM pg_tables
WHERE schemaname = 'storage' AND tablename = 'objects';
-- SELECT policy: scope reads to the owning user's folder.
CREATE POLICY "users_read_own_files"
ON storage.objects
FOR SELECT
USING (auth.uid()::text = (storage.foldername(name))[1]);
-- INSERT policy: enforce the [user-id]/[filename] path convention.
CREATE POLICY "users_upload_own"
ON storage.objects
FOR INSERT
WITH CHECK (auth.uid()::text = (storage.foldername(name))[1]);Xác thực upload
Xác thực mọi upload phía server, ngay cả khi bucket có ràng buộc MIME và kích thước. Các công cụ AI lập trình tạo ra xác thực chỉ phía client theo mặc định; điều đó không bảo vệ gì cả.
- Kiểm tra lại loại MIME phía server từ các byte thực của file, không phải từ header
Content-Type. Dùng thư viện nhưfile-type(Node) hoặc magic-byte sniffing. Kẻ tấn công có thể tuyên bốContent-Type: image/jpegtrên một file thực chất là payload HTML / JavaScript đa hình. - Loại bỏ metadata EXIF từ ảnh được upload. EXIF có thể chứa tọa độ GPS, số serial thiết bị và dấu thời gian. Dùng
sharpvới.withMetadata(false)hoặcexif-parserđể loại bỏ trước khi lưu. - Từ chối SVG chứa thẻ
scripthoặc handleronload. SVG là XML — và nhiều ứng dụng do AI tạo cho phép upload SVG như "chỉ là một ảnh". DùngDOMPurifyphía server hoặc từ chối hoàn toàn upload SVG. - Dùng tên file xác định, không thể đoán. Đừng giữ tên file gốc. Dùng UUID hoặc hash nội dung file. Tên file gốc làm rò rỉ ("
passport_scan_2024_01_15.jpg") và tên có thể dự đoán cho phép liệt kê.
Signed URL
Signed URL là cách client truy cập bucket private. Thời hạn, phạm vi bucket và những gì được ghi lại đều quan trọng.
- Mặc định thời hạn signed URL là 1 giờ hoặc ít hơn.
createSignedUrl(path, expiresIn)của Supabase JS SDK nhận giá trị tính bằng giây. Đừng bao giờ dùng các giá trị như31536000(một năm) — URL trở thành một liên kết bán công khai vĩnh viễn. - Đừng bao giờ lưu signed URL trong cơ sở dữ liệu. Tạo signed URL mới phía server trong mỗi yêu cầu. Một signed URL đã lưu với hạn 1 năm bị rò rỉ qua dump cơ sở dữ liệu cấp quyền truy cập dài hạn.
- Ghi log việc tạo signed URL, không chỉ upload file. Nếu bạn nghi ngờ bị xâm phạm sau này, bạn cần biết ai đã tạo URL nào khi nào. Ghi log
auth.uid()+ bucket + đường dẫn object + dấu thời gian. - Dùng tùy chọn
downloadAskhi phục vụ file do người dùng upload.createSignedUrl(path, expiresIn, { download: '.jpg' })ép buộc headerContent-Disposition: attachmentđể file được tải xuống thay vì được render — chặn lớp thực thi HTML / SVG / HTML-trong-PDF.
Vệ sinh vận hành
Cấu hình storage trôi dạt theo thời gian. Bốn mục vận hành này giữ cho bề mặt được siết chặt.
- Kiểm toán bucket hàng quý. Dashboard → Storage → Buckets. Xác nhận trạng thái public/private và danh sách MIME-type khớp với những gì ứng dụng mong đợi. Bucket được tạo "tạm thời" sẽ trở thành vĩnh viễn nếu không ai xóa chúng.
- Giám sát thao tác liệt kê ẩn danh. Log storage (Dashboard → Logs → Storage) ghi lại các yêu cầu
LIST. Một đợt tăng đột biến các yêu cầu liệt kê ẩn danh đối với bucket private có nghĩa là ai đó đang thăm dò nó từ bên ngoài. - Đặt chính sách lưu giữ cho upload tạm thời. Bucket tạm (xem trước ảnh, upload bản nháp) phải tự động xóa sau 24-72 giờ qua một hàm theo lịch. Lưu giữ vô hạn là một trách nhiệm pháp lý theo nghĩa vụ giảm thiểu dữ liệu của GDPR / CCPA.
- Chạy một lượt quét FixVibe hàng tháng. Check
baas.supabase-storage-publicthăm dò các bucket phản hồi vớiGET+LISTẩn danh. Các bucket mới được thêm vào; các bucket cũ thay đổi khả năng hiển thị — chỉ có việc quét liên tục mới bắt được sự trôi dạt.
Các bước tiếp theo
Chạy một lượt quét FixVibe với URL production — các danh sách storage ẩn danh xuất hiện dưới baas.supabase-storage-public. Hãy ghép cặp danh sách kiểm tra này với Supabase RLS scanner cho lớp bảng và Supabase service role key bị lộ trong JavaScript cho phơi nhiễm key liền kề. Để biết các cấu hình sai storage trên các nhà cung cấp BaaS khác, xem BaaS misconfiguration scanner.
