Một kiểu panic rất phổ biến khi audit app vibe-coded:

Tôi thấy Supabase anon key trong JavaScript. App bị leak key rồi.

Có thể đúng. Nhưng thường câu này thiếu nửa sau.

Với Supabase-style architecture, một số public key được thiết kế để chạy trong browser. Vấn đề không chỉ là key có public hay không. Vấn đề là key đó mở được dữ liệu gì, role nào đang dùng nó, Row Level Security có bật không, và policy có đúng không.

Nói ngắn gọn: anon key public không tự động là thảm họa. Database không có RLS mới là thảm họa.

Public key không phải service role key

Trong app frontend, bạn thường thấy:

SUPABASE_URL
SUPABASE_ANON_KEY

Đây không giống service_role key.

service_role key là chìa khóa admin, không được đưa vào frontend. Nếu key đó xuất hiện trong bundle public, dừng lại ngay.

anon hoặc publishable key thì khác. Nó có thể public, nhưng chỉ an toàn nếu database policy được cấu hình đúng. Nó giống một cổng vào có kiểm soát, không phải giấy phép đọc hết database.

Sai lầm của vibe-coded app là agent thấy app chạy được, form save được, dashboard load được, rồi bỏ qua câu hỏi:

Ai được đọc row nào?

RLS là ranh giới thật

Row Level Security trả lời câu hỏi ở cấp database:

  • Anonymous user có đọc được table này không?
  • Logged-in user có đọc được row của user khác không?
  • User có update/delete row không thuộc về mình không?
  • Admin role được enforce ở đâu?
  • Storage bucket có cùng policy với database không?

Nếu RLS tắt, một public key có thể trở thành đường vào quá rộng. Nếu RLS bật nhưng policy viết sai, app vẫn có thể leak data.

Vì vậy audit không nên dừng ở việc tìm key. Audit phải đi tới policy.

Moltbook là case study dễ nhớ

Moltbook bị chú ý không phải chỉ vì có Supabase key trong frontend. Điểm nguy hiểm là cấu hình backend cho phép truy cập database quá rộng.

Các report về incident này mô tả vấn đề theo cùng một pattern: public-facing app, Supabase-backed database, key visible trong client-side code, và thiếu hoặc sai Row Level Security khiến dữ liệu nhạy cảm bị đọc/ghi ngoài ý muốn.

Đây là bài học rất thực dụng cho vibe coding: app nhìn như chạy ổn trong browser không có nghĩa access model đã đúng.

AI có thể build UI rất nhanh. AI cũng có thể quên boundary giữa viewer, owner, team member, admin, và anonymous.

Non-tech kiểm tra RLS như thế nào

Bạn không cần viết SQL giỏi để hỏi đúng câu.

Sau khi agent dựng app với Supabase, yêu cầu nó tạo báo cáo:

Do not edit code.
Audit Supabase security.
List every public schema table.
For each table, report:
- Is Row Level Security enabled?
- Which policies exist?
- Can anon read, insert, update, or delete?
- Can authenticated user A read user B's rows?
- Which column represents owner, team, or tenant?
Mark unknown instead of guessing.

Sau đó test bằng hành vi:

  1. Tạo account A.
  2. Tạo một record.
  3. Tạo account B.
  4. Thử mở URL record của account A bằng account B.
  5. Thử đổi ID trong URL.
  6. Thử export hoặc search data.
  7. Nếu agent có API route, yêu cầu test direct request.

Nếu account B đọc được data của account A, đừng sửa màu button. Sửa access control trước.

Prompt build app Supabase an toàn hơn

Khi yêu cầu AI build app dùng Supabase, prompt nên có constraints rõ:

Use Supabase with Row Level Security.
Every user-owned table must include owner_id or tenant_id.
Do not rely on frontend checks for access control.
Do not expose service_role keys to client code.
Before implementation, propose RLS policies for each table.
After implementation, report which policy enforces each read/write action.

Quan trọng nhất là câu cuối. Nó ép agent nối feature với policy, thay vì nói chung chung “secure”.

Dấu hiệu phải dừng

Dừng và gọi dev review nếu thấy:

  • service_role key trong frontend.
  • RLS disabled trên table có user data.
  • Table không có owner_id, user_id, team_id, hoặc tenant boundary tương đương.
  • Policy cho anon quá rộng.
  • App có dashboard nhiều user nhưng không có ownership model.
  • Agent không giải thích được policy nào bảo vệ route nào.

Đừng chấp nhận câu trả lời kiểu “Supabase handles security”. Supabase cung cấp công cụ. App của bạn vẫn phải cấu hình đúng.

Chốt lại

Trong vibe coding, câu hỏi đúng không phải là “có thấy anon key không?” Câu hỏi đúng là “key này được phép làm gì?”

Public key có thể là thiết kế bình thường. Public database thì không.

Nếu app dùng Supabase, RLS không phải phần polish sau cùng. Nó là một phần của data model. Làm UI trước rồi quên RLS giống như xây cửa hàng đẹp, mở hết kho hàng ra phố, rồi nói vì cửa chính có logo nên an toàn.

Tham khảo