// docs / baas security / supabase service role exposure
Service role key Supabase bị lộ trong JavaScript: ý nghĩa và cách tìm
Service role key của Supabase là master key của cơ sở dữ liệu bạn. Bất kỳ ai giữ nó đều bỏ qua Row-Level Security, có thể đọc mọi cột của mọi bảng và có thể ghi hoặc xóa bất cứ thứ gì họ muốn. Nó được thiết kế để chỉ tồn tại trong mã phía server — không bao giờ trong trình duyệt. Khi một công cụ AI lập trình đẩy nó vào bundle JavaScript, cơ sở dữ liệu của bạn về cơ bản trở nên công khai. Bài viết này giải thích hình dạng JWT để nhận diện key bị rò rỉ, ba mẫu công cụ AI tạo ra rò rỉ, cần làm gì trong giờ đầu tiên sau khi phát hiện, và cách quét tự động trước khi người dùng phát hiện.
Service role key là gì
Supabase cấp hai key riêng biệt cho mỗi dự án: key anon (còn gọi là publishable key trong các dự án mới hơn) và key service_role. Cả hai đều là JSON Web Token được ký bằng JWT secret của dự án. Sự khác biệt là claim role được nướng vào payload JWT — anon cho key công khai, service_role cho master key. PostgREST, Supabase Storage và Supabase Auth đều chuyển sang chế độ bỏ-qua-mọi-thứ khi nhìn thấy claim service_role.
Giải mã bất kỳ key Supabase nào tại jwt.io và xem payload. Hình dạng của một JWT service-role không thể nhầm lẫn:
Payload đã giải mã của một JWT service-role (hiển thị dưới dạng khối tô sáng cú pháp bên dưới).
{
"iss": "supabase",
"ref": "[project-ref]",
"role": "service_role",
"iat": 1700000000,
"exp": 2000000000
}Các dự án Supabase mới hơn cấp các key kiểu bí mật với tiền tố sb_secret_ thay vì JWT. Hành vi giống hệt nhau — bất cứ thứ gì mang sb_secret_ trong bundle công khai đều thảm khốc như nhau.
Cách các công cụ AI lập trình làm rò rỉ service role key
Chúng tôi đã thấy ba mẫu giống nhau trên hàng nghìn ứng dụng vibe-code. Mỗi mẫu bắt đầu khi developer hỏi công cụ AI để được giúp đỡ và kết thúc với service key được nhúng vào bundle.
Mẫu 1: Một file .env duy nhất với tiền tố NEXT_PUBLIC_
Developer yêu cầu công cụ AI "thiết lập Supabase" và chấp nhận một file .env duy nhất chứa cả hai key. Công cụ AI — được huấn luyện trên một tập corpus mà hầu hết các biến môi trường được hiển thị qua NEXT_PUBLIC_* — thêm tiền tố NEXT_PUBLIC_ vào cả hai. Next.js sẽ nội tuyến bất cứ thứ gì khớp với tiền tố đó vào bundle client tại thời điểm build. Triển khai lên Vercel và service key sẽ nằm trong main.[hash].js.
Mẫu 2: Sai key trong lệnh gọi createClient
Developer dán cả hai key vào file config.ts mà AI tạo, và AI điền vào lệnh gọi createClient() phía trình duyệt với process.env.SUPABASE_SERVICE_ROLE_KEY do nhầm. Bản build kéo biến vào và JWT rơi vào bundle.
Mẫu 3: Service-role key hardcode trong script seed
Developer yêu cầu công cụ AI viết một script để seed cơ sở dữ liệu. AI hardcode service-role key trực tiếp vào file (thay vì đọc từ môi trường), commit file vào kho, và kho GitHub công khai hoặc route /scripts/seed.js của ứng dụng đã triển khai giờ đang phục vụ key đó.
Cách lượt quét bundle của FixVibe phát hiện rò rỉ
Check bundle-secrets của FixVibe tải xuống mọi file JavaScript được tham chiếu bởi ứng dụng đã triển khai — entry chunk, lazy-loaded chunk, web worker, service worker — và chạy chúng qua một bộ phát hiện giải mã bất cứ thứ gì khớp với hình dạng JWT (eyJ[base64-header].eyJ[base64-payload].[signature]). Nếu payload đã giải mã chứa "role": "service_role", lượt quét báo cáo nó là phát hiện critical với đường dẫn file và dòng chính xác nơi key xuất hiện. Cùng một check cũng khớp với mẫu sb_secret_* mới bằng tiền tố.
Lượt quét không bao giờ xác thực với key đã phát hiện. Nó nhận diện hình dạng và báo cáo rò rỉ — sử dụng key để chứng minh khả năng khai thác sẽ là truy cập trái phép vào cơ sở dữ liệu của bạn. Bằng chứng nằm trong chính payload JWT.
Đã phát hiện — phải làm gì trong giờ đầu tiên
Service role key bị rò rỉ là tình huống khẩn cấp ở runtime. Hãy giả định key đã bị scrape — kẻ tấn công theo dõi các bundle công khai theo thời gian thực. Hãy coi cơ sở dữ liệu là đã bị xâm phạm cho đến khi bạn xoay vòng key và kiểm toán hoạt động gần đây.
- Xoay vòng key ngay lập tức. Trong Supabase Dashboard, vào Project Settings → API → Service role key → Reset. Key cũ bị vô hiệu hóa trong vòng vài giây. Bất kỳ mã phía server nào sử dụng key đó phải được cập nhật và triển khai lại trước khi việc xoay vòng có hiệu lực.
- Kiểm toán hoạt động cơ sở dữ liệu gần đây. Mở Database → Logs trong dashboard. Lọc 7 ngày gần nhất. Tìm các truy vấn
SELECT *bất thường trên các bảng có PII, các câu lệnhUPDATEhoặcDELETElớn, và các yêu cầu từ IP nằm ngoài cơ sở hạ tầng đã biết. Supabase ghi lại headerx-real-iptrên mọi yêu cầu. - Kiểm tra các object storage. Truy cập Storage → Logs và xem lại các lượt tải file gần đây. Một service-role key bị rò rỉ cũng cấp quyền bỏ-qua-mọi-thứ vào các bucket private.
- Xóa key khỏi source control. Ngay cả sau khi xoay vòng, để JWT trong lịch sử git có nghĩa là nó có thể được phát hiện trong repo công khai. Dùng
git filter-repohoặc BFG Repo-Cleaner để xóa nó khỏi lịch sử, sau đó force-push (cảnh báo cộng tác viên trước). - Quét lại sau khi sửa. Chạy một lượt quét FixVibe mới với ứng dụng đã triển khai lại. Phát hiện bundle-secrets sẽ biến mất. Xác nhận không còn JWT
service_rolevà không còn chuỗisb_secret_*trong bất kỳ chunk nào.
Ngăn chặn rò rỉ ngay từ đầu
Bản sửa cấu trúc là kỷ luật đặt tên cộng với rào chắn ở cấp công cụ:
- Không bao giờ đặt tiền tố service key bằng
NEXT_PUBLIC_*,VITE_*, hoặc bất kỳ tiền tố nội tuyến vào bundle nào khác. Quy ước đặt tên là ranh giới — mọi framework đều tôn trọng nó. - Hoàn toàn không để service key trong
.envtrên máy của developer. Đọc nó từ một secret manager (Doppler, Infisical, biến môi trường mã hóa của Vercel) khi triển khai, không bao giờ commit nó cục bộ. - <strong>Mark every Supabase client construction with explicit context.</strong> Files named <code>supabase/browser.ts</code> use the anon key; files named <code>supabase/server.ts</code> use the service-role key with <code>import 'server-only'</code> at the top. The <code>server-only</code> import causes a build error if a client component tries to consume the module.
- <strong>Add a pre-commit hook that greps for JWT-shaped strings.</strong> <code>git diff --staged | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'</code> catches both anon and service tokens before they leave your machine.
- Thêm một cổng CI quét output build. Sau
next build, grep output.next/static/chunks/để tìm chuỗiservice_role. Hãy fail build nếu khớp bất cứ thứ gì.
# Pre-commit hook: refuse any staged JWT-shaped string.
git diff --staged \
| grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' \
&& echo "JWT detected in staged changes — refusing commit" \
&& exit 1
# CI gate: fail the build if "service_role" shipped to the static bundle.
grep -RE 'service_role|sb_secret_' .next/static/chunks/ \
&& echo "Service-role credential leaked into bundle" \
&& exit 1Câu hỏi thường gặp
Kẻ tấn công thực sự tìm thấy service-role key Supabase bị rò rỉ nhanh đến mức nào?
Các scanner bundle công khai rà các deployment mới trong vòng vài phút. Các nhà nghiên cứu đã ghi nhận các khai thác hoạt động trên các dự án Supabase mới trong chưa đến một giờ kể từ lần triển khai đầu tiên. Hãy coi bất kỳ rủi ro service-role nào là cửa sổ 60 phút, không phải 60 ngày.
Xoay vòng key đã đủ chưa, hay tôi phải giả định dữ liệu đã bị đánh cắp?
Việc xoay vòng làm vô hiệu hóa key bị rò rỉ nhưng không hoàn tác dữ liệu đã bị kéo. Nếu các bảng của bạn chứa PII, dữ liệu thanh toán hoặc bất kỳ dữ liệu nào được quản lý, bạn có thể có nghĩa vụ thông báo theo GDPR (72 giờ), CCPA hoặc HIPAA. Hãy kiểm toán log và tham khảo ý kiến luật sư nếu kiểm toán cho thấy truy cập đáng ngờ.
RLS có thể bảo vệ tôi nếu service-role key bị rò rỉ không?
Không. Row-Level Security bị bỏ qua hoàn toàn bởi claim service_role. Đây là thiết kế có chủ ý — key tồn tại chính xác để cho phép mã backend bỏ qua RLS cho các thao tác quản trị. Biện pháp giảm thiểu là đảm bảo key không bao giờ tiếp cận một ngữ cảnh mà kẻ tấn công có thể đọc.
Điều này có áp dụng cho mô hình publishable / secret key mới của Supabase (<code>sb_publishable_</code> / <code>sb_secret_</code>) không?
Có — cùng cấp độ rủi ro. Key sb_secret_* là định dạng secret-key mới thay thế JWT service-role cho các dự án mới hơn. Bất cứ thứ gì mang sb_secret_* trong bundle đều thảm khốc như JWT service-role bị rò rỉ. Bộ phát hiện bundle-secrets của FixVibe khớp với cả hai hình dạng.
Còn anon / publishable key thì sao — có an toàn trong bundle không?
Có, theo thiết kế. Anon key được thiết kế để sống trong trình duyệt và là thứ mà mọi web client Supabase sử dụng. Sự an toàn của nó hoàn toàn phụ thuộc vào việc RLS được cấu hình đúng trên mọi bảng public. Xem bài Supabase RLS scanner để biết những gì cần kiểm tra.
Các bước tiếp theo
Chạy một lượt quét FixVibe với URL production — check bundle-secrets miễn phí, không cần đăng ký và báo cáo phơi nhiễm service_role trong chưa đầy một phút. Hãy ghép cặp với bài Supabase RLS scanner để xác minh lớp RLS hoạt động đúng, và Danh sách kiểm tra bảo mật storage bucket Supabase để khóa quyền truy cập file. Để hiểu lý do tại sao các công cụ AI tạo ra lớp rò rỉ này đáng tin cậy đến vậy, đọc Vì sao công cụ AI lập trình để lại lỗ hổng bảo mật.
