// docs / baas security / supabase rls scanner
Scanner RLS Supabase: temukan tabel dengan row-level security yang hilang atau rusak
Row-level security (RLS) adalah satu-satunya hal yang berdiri antara data pelanggan Anda dan internet ketika Anda merilis aplikasi berbasis Supabase. AI coding tools menghasilkan kode berbentuk RLS yang ter-compile, terkirim, dan diam-diam membocorkan data โ tabel dibuat tanpa RLS diaktifkan, policy yang membaca tetapi tidak pernah membatasi, predikat yang membandingkan kolom dengan dirinya sendiri. Artikel ini menunjukkan apa yang dapat dibuktikan scanner RLS Supabase dari luar, empat bentuk RLS rusak yang muncul di aplikasi vibe-coded, dan cara men-scan deployment Anda sendiri dalam kurang dari semenit.
Apa yang dapat dibuktikan scan RLS eksternal
Scan RLS pasif berjalan terhadap endpoint PostgREST yang dieksposisi Supabase di https://[project].supabase.co/rest/v1/. Ia hanya menggunakan key anon publishable โ key yang sama yang digunakan browser Anda โ dan menyondir metadata daftar tabel, baca anonim, dan tulis anonim. Ia tidak pernah berautentikasi sebagai user dan tidak pernah menyentuh privilege service-role. Apapun yang dapat ia lakukan, penyerang tidak terautentikasi di internet juga dapat melakukannya.
Dari luar database, scanner dapat mengonfirmasi hal berikut dengan kepercayaan tinggi:
- RLS dinonaktifkan pada sebuah tabel. PostgREST mengembalikan baris untuk
SELECTanonim ketika RLS mati atau ketika sebuah policy mengizinkannya. Kedua kasus adalah temuan. - Role anonim dapat membuat daftar tabel.
GET /rest/v1/dengan anon key mengembalikan skema OpenAPI untuk setiap tabel yang roleanonmiliki privilege apa pun di dalamnya. Aplikasi yang dihasilkan AI sering memberikanUSAGEpada skema danSELECTpada setiap tabel, yang mengekspos seluruh peta skema bahkan ketika RLS menolak baca yang sebenarnya. - Role anonim dapat melakukan insert.
POSTpenyondiran dengan tebakan bentuk kolom akan berhasil jika RLS tidak memiliki policyINSERTyang menolaknya โ bahkan jikaSELECTterkunci. - Service-role key ada di bundle browser. Berdekatan dengan RLS: jika scanner menemukan
SUPABASE_SERVICE_ROLE_KEYatau JWT apa pun denganrole: service_roledi bundle JavaScript, RLS tidak relevan โ pemegang key itu mem-bypass setiap policy.
Apa yang tidak dapat dibuktikan scan eksternal
Jujurlah tentang batasan scanner. Scan RLS eksternal tidak dapat membaca tabel pg_policies Anda, file migrasi Anda, atau predikat persis dari policy apa pun. Ia menyimpulkan dari perilaku black-box, yang berarti kadang-kadang ia akan melaporkan temuan yang ternyata data publik yang disengaja (tabel newsletter pemasaran, katalog produk publik). Laporan FixVibe menandai ini sebagai kepercayaan menengah ketika scanner tidak dapat membedakan niat โ periksa nama tabel dan putuskan.
Empat bentuk RLS rusak yang dihasilkan AI tools
Ketika Anda mengarahkan Cursor, Claude Code, Lovable, atau Bolt ke Supabase, empat pola RLS rusak yang sama muncul di ribuan aplikasi. Masing-masing lolos type-check, ter-compile, dan terkirim:
Bentuk 1: RLS tidak pernah diaktifkan
Mode kegagalan paling umum. Migrasi membuat tabel tetapi pengembang (atau AI tool) lupa ALTER TABLE ... ENABLE ROW LEVEL SECURITY. PostgREST dengan senang hati melayani seluruh tabel kepada siapa saja yang memiliki anon key. Fix: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;. FORCE tidak opsional โ tanpanya, pemilik tabel (dan role apa pun dengan kepemilikan tabel) mem-bypass RLS.
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY;Bentuk 2: RLS diaktifkan, tanpa policy
Kegagalan yang lebih halus. RLS diaktifkan tetapi tidak ada policy yang ditulis. Default di PostgreSQL adalah deny, jadi user terautentikasi tidak melihat apa-apa โ dan pengembang menambahkan USING (true) agar aplikasi bekerja, yang mengizinkan semua orang membaca semuanya. Fix: tulis policy yang dibatasi oleh auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); dan policy INSERT/UPDATE/DELETE yang sepadan.
CREATE POLICY "select_own"
ON public.[name]
FOR SELECT
USING (auth.uid() = user_id);Bentuk 3: Policy membandingkan kolom dengan dirinya sendiri
A copy-paste artefact. The developer writes <code>USING (user_id = user_id)</code> โ which is always true โ instead of <code>USING (auth.uid() = user_id)</code>. Type-checks pass; the policy permits every row. <strong>Fix:</strong> always compare a column to a function call (<code>auth.uid()</code>, <code>auth.jwt()->>'org_id'</code>, etc.), never to itself or to a constant.
Bentuk 4: Policy pada SELECT tetapi tidak pada INSERT/UPDATE
Pengembang mengunci baca tetapi lupa tulis. Policy RLS bersifat per-command. FOR SELECT hanya melindungi baca; klien anonim masih dapat INSERT jika tidak ada policy yang menolaknya. Fix: buat policy per command, atau gunakan FOR ALL dengan klausa USING dan WITH CHECK eksplisit.
Cara kerja scanner RLS Supabase FixVibe
Check baas.supabase-rls berjalan dalam tiga tahap, masing-masing dengan tingkat kepercayaan eksplisit:
- Tahap 1 โ fingerprint. Scanner meng-crawl aplikasi yang ter-deploy, mem-parse bundle JavaScript-nya, dan mengekstrak URL proyek Supabase serta anon key dari konfigurasi runtime. Tanpa tebakan DNS, tanpa brute force โ ia membaca apa yang dibaca browser.
- Tahap 2 โ penemuan skema. Satu
GET /rest/v1/dengan anon key mengembalikan skema OpenAPI untuk setiap tabel yang dapat dilihat role anon. Scanner mencatat nama tabel tetapi tidak membaca data baris pada tahap ini. - Tahap 3 โ sondir baca dan tulis. Untuk setiap tabel yang ditemukan, scanner mengeluarkan satu
SELECTanonim denganlimit=1. Jika baris dikembalikan, RLS bersifat permisif. Scanner berhenti di situ โ ia tidak mengenumerasi baris, tidak melakukan paginasi, tidak memodifikasi data. Sondir INSERT diperketat di balik verifikasi kepemilikan domain dan opt-in eksplisit; sondir tersebut tidak pernah berjalan terhadap target yang belum diverifikasi.
Setiap temuan disertai URL request persis, status response, bentuk response (header-only), dan nama tabel. Prompt perbaikan AI di bawah temuan adalah blok SQL salin-tempel yang Anda jalankan di editor SQL Supabase.
Apa yang harus dilakukan ketika scanner menemukan sesuatu
Setiap temuan RLS adalah keadaan darurat runtime. Endpoint PostgREST publik di-scan oleh penyerang dalam hitungan menit. Urutan remediasi bersifat mekanis:
- Audit setiap tabel. Jalankan
SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';di editor SQL Supabase. Setiap baris denganrowsecurity = falseadalah masalah. - Aktifkan RLS pada setiap tabel publik. Default ke
ENABLE ROW LEVEL SECURITYdanFORCE ROW LEVEL SECURITYpada setiap tabel yang dibuat โ jadikan ini template migrasi. - Tulis policy command-per-command. Jangan gunakan
FOR ALL USING (true). Tulis policy eksplisit untuk SELECT, INSERT, UPDATE, DELETE โ masing-masing dibatasi padaauth.uid()atau kolom org-id dariauth.jwt(). - Verifikasi dengan akun kedua. Daftar sebagai user yang berbeda, coba baca catatan user lain melalui REST API secara langsung. Jika response adalah
200, policy rusak. - Scan ulang. Setelah menerapkan fix, jalankan kembali scan FixVibe terhadap URL yang sama. Temuan
baas.supabase-rlsseharusnya hilang.
-- Audit every table for missing RLS. Run in the Supabase SQL editor.
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY rowsecurity, tablename;Bagaimana ini dibandingkan dengan scanner lain
Sebagian besar tool DAST generik (Burp Suite, OWASP ZAP, Nessus) tidak tahu apa itu PostgREST. Mereka akan meng-crawl aplikasi Anda, mengabaikan path /rest/v1/, dan melaporkan halaman HTML yang mereka pahami. Snyk dan Semgrep adalah tool analisis statis โ mereka menemukan file migrasi di repo Anda dengan panggilan RLS yang hilang, tetapi tidak dapat membuktikan database yang ter-deploy salah konfigurasi. FixVibe duduk di celah itu: pasif, sadar-BaaS, fokus pada apa yang dapat dibuktikan penyerang tidak terautentikasi dari URL publik.
Pertanyaan yang sering diajukan
Apakah scanner membaca atau memodifikasi data saya?
Tidak. Scan pasif mengeluarkan paling banyak satu SELECT ... limit=1 per tabel yang ditemukan untuk mengonfirmasi apakah RLS mengizinkan baca anonim. Scanner mencatat bentuk response, bukan isi baris. Sondir INSERT, UPDATE, dan DELETE diperketat di balik verifikasi kepemilikan domain dan tidak pernah berjalan terhadap target yang belum diverifikasi.
Apakah ini bekerja jika proyek Supabase saya dijeda atau berada di domain kustom?
Proyek yang dijeda mengembalikan 503 pada setiap request โ scanner melaporkan proyek sebagai tidak terjangkau. Domain kustom bekerja selama aplikasi yang ter-deploy masih memuat SDK klien Supabase di browser; scanner mengekstrak URL proyek dari bundle bagaimanapun juga.
Bagaimana jika anon key saya dirotasi atau publishable key saya berubah?
Jalankan kembali scan. Scanner mengekstrak ulang key dari bundle saat ini pada setiap run. Rotasi hanya menginvalidasi laporan sebelumnya, bukan keadaan policy database.
Apakah scanner memeriksa model publishable-key Supabase yang baru (<code>sb_publishable_*</code>)?
Ya. Detektor mengenali JWT anon lama dan key sb_publishable_* yang lebih baru, serta memperlakukannya secara identik โ keduanya dimaksudkan untuk publik dan keduanya menyisakan RLS sebagai satu-satunya garis pertahanan.
Langkah berikutnya
Jalankan scan FixVibe gratis terhadap URL produksi Anda โ check baas.supabase-rls diaktifkan di setiap paket termasuk tier gratis. Untuk bacaan lebih dalam tentang apa lagi yang dapat bocor dari proyek Supabase, lihat Supabase service role key terekspos di JavaScript dan Checklist keamanan bucket storage Supabase. Untuk tinjauan menyeluruh lintas semua penyedia BaaS, baca Scanner miskonfigurasi BaaS.
