FixVibe

// docs / baas security / supabase rls scanner

Supabase RLS scanner: ค้นหาตารางที่ขาดหรือมี row-level security ที่เสียหาย

Row-level security (RLS) คือสิ่งเดียวที่ยืนอยู่ระหว่างข้อมูลของลูกค้าและอินเทอร์เน็ตเมื่อคุณปล่อยแอปที่ใช้ Supabase เครื่องมือ AI Coding สร้างโค้ดในรูปแบบ RLS ที่ compile ได้, deploy ได้ และทำให้ข้อมูลรั่วไหลโดยเงียบๆ — ตารางที่สร้างขึ้นโดยไม่ได้เปิด RLS, policy ที่อ่านได้แต่ไม่จำกัด, predicate ที่เปรียบเทียบคอลัมน์กับตัวมันเอง บทความนี้แสดงว่า Supabase RLS scanner สามารถพิสูจน์อะไรได้บ้างจากภายนอก, รูปแบบ RLS เสียหายสี่แบบที่ปรากฏในแอป vibe-coded และวิธีสแกน deployment ของคุณเองในเวลาไม่ถึงหนึ่งนาที

การสแกน RLS จากภายนอกสามารถพิสูจน์อะไรได้

การสแกน RLS แบบ passive รันกับ PostgREST endpoint ที่ Supabase เปิดที่ https://[project].supabase.co/rest/v1/ โดยใช้เพียงแค่ anon key ที่เผยแพร่ได้ — key เดียวกับที่เบราว์เซอร์ของคุณใช้ — และตรวจสอบ metadata รายการตาราง, การอ่านแบบ anonymous และการเขียนแบบ anonymous มันไม่เคย authenticate ในฐานะผู้ใช้และไม่เคยใช้สิทธิ์ service-role อะไรก็ตามที่มันทำได้ ผู้โจมตีที่ไม่ผ่านการ authenticate บนอินเทอร์เน็ตก็ทำได้

จากภายนอกฐานข้อมูล สแกนเนอร์สามารถยืนยันสิ่งต่อไปนี้ด้วยความมั่นใจสูง:

  • RLS ปิดอยู่บนตาราง PostgREST คืน rows สำหรับ SELECT แบบ anonymous เมื่อ RLS ปิดอยู่หรือเมื่อ policy อนุญาต ทั้งสองกรณีถือเป็น finding
  • role ที่เป็น anonymous สามารถ list ตารางได้ GET /rest/v1/ ด้วย anon key คืน OpenAPI schema สำหรับทุกตารางที่ role anon มีสิทธิ์ใดๆ อยู่ แอปที่สร้างจาก AI มักให้สิทธิ์ USAGE บน schema และ SELECT บนทุกตาราง ทำให้แผนผัง schema ทั้งหมดถูกเปิดเผยแม้ว่า RLS จะปฏิเสธการอ่านจริงก็ตาม
  • role ที่เป็น anonymous สามารถ insert ได้ POST ทดลองพร้อมการคาดเดารูปแบบคอลัมน์จะสำเร็จถ้า RLS ไม่มี INSERT policy ปฏิเสธ — แม้ว่า SELECT จะถูกล็อกไว้ก็ตาม
  • service-role key อยู่ใน bundle ของเบราว์เซอร์ ใกล้เคียงกับ RLS: ถ้าสแกนเนอร์พบ SUPABASE_SERVICE_ROLE_KEY หรือ JWT ใดๆ ที่มี role: service_role ใน JavaScript bundle, RLS ก็ไม่มีผล — ผู้ที่ถือ key นั้นข้ามทุก policy

การสแกนจากภายนอกพิสูจน์อะไรไม่ได้

เปิดเผยตรงๆ เกี่ยวกับข้อจำกัดของสแกนเนอร์ การสแกน RLS จากภายนอกไม่สามารถอ่านตาราง pg_policies ของคุณ, ไฟล์ migration หรือ predicate ที่แน่นอนของ policy ใดๆ ได้ มันอนุมานจากพฤติกรรม black-box ซึ่งหมายความว่าบางครั้งอาจรายงาน finding ที่กลายเป็นข้อมูลสาธารณะโดยตั้งใจ (ตาราง newsletter การตลาด, catalog สินค้าสาธารณะ) รายงาน FixVibe จะระบุสิ่งเหล่านี้ว่าเป็น ความมั่นใจระดับกลาง เมื่อสแกนเนอร์ไม่สามารถแยกเจตนาได้ — ดูชื่อตารางและตัดสินใจ

รูปแบบ RLS เสียหายสี่แบบที่เครื่องมือ AI สร้างขึ้น

เมื่อคุณใช้ Cursor, Claude Code, Lovable หรือ Bolt กับ Supabase รูปแบบ RLS เสียหายสี่แบบเดียวกันจะปรากฏในแอปนับพัน แต่ละแบบผ่าน type-check, compile ได้ และ deploy ได้:

แบบที่ 1: RLS ไม่เคยถูกเปิดใช้งาน

โหมดความล้มเหลวที่พบบ่อยที่สุด migration สร้างตารางแต่ developer (หรือเครื่องมือ AI) ลืม ALTER TABLE ... ENABLE ROW LEVEL SECURITY PostgREST ให้ตารางทั้งหมดแก่ใครก็ตามที่มี anon key อย่างสบายใจ แก้ไข: ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY; ALTER TABLE public.[name] FORCE ROW LEVEL SECURITY; FORCE ไม่ใช่ทางเลือก — หากไม่มี เจ้าของตาราง (และ role ใดๆ ที่เป็นเจ้าของตาราง) จะข้าม RLS

sql
ALTER TABLE public.[name] ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.[name] FORCE  ROW LEVEL SECURITY;

แบบที่ 2: เปิด RLS แล้วแต่ไม่มี policy

ความล้มเหลวที่แอบแฝงกว่า RLS เปิดอยู่แต่ไม่มี policy เขียนไว้ ค่าเริ่มต้นใน PostgreSQL คือ ปฏิเสธ ดังนั้นผู้ใช้ที่ผ่านการ authenticate จะไม่เห็นอะไรเลย — และ developer เพิ่ม USING (true) เพื่อให้แอปทำงาน ซึ่งอนุญาตให้ทุกคนอ่านทุกอย่าง แก้ไข: เขียน policy ที่จำกัดด้วย auth.uid(): CREATE POLICY "select_own" ON public.[name] FOR SELECT USING (auth.uid() = user_id); และ policy INSERT/UPDATE/DELETE ที่สอดคล้องกัน

sql
CREATE POLICY "select_own"
  ON public.[name]
  FOR SELECT
  USING (auth.uid() = user_id);

แบบที่ 3: policy เปรียบเทียบคอลัมน์กับตัวมันเอง

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.

แบบที่ 4: policy บน SELECT แต่ไม่มีบน INSERT/UPDATE

developer ล็อกการอ่านแต่ลืมการเขียน RLS policy เป็นแบบต่อคำสั่ง FOR SELECT ปกป้องเฉพาะการอ่าน; client ที่เป็น anonymous ยังคงสามารถ INSERT ได้หากไม่มี policy ปฏิเสธ แก้ไข: เขียน policy ต่อคำสั่ง หรือใช้ FOR ALL พร้อมเงื่อนไข USING และ WITH CHECK ที่ชัดเจน

วิธีที่ FixVibe Supabase RLS scanner ทำงาน

Check baas.supabase-rls ทำงานในสามขั้นตอน แต่ละขั้นมีระดับความมั่นใจที่ชัดเจน:

  1. ขั้นที่ 1 — fingerprint สแกนเนอร์ crawl แอปที่ deploy แล้ว, parse JavaScript bundle และดึง URL ของ Supabase project และ anon key จากการตั้งค่า runtime ไม่มีการเดา DNS ไม่มี brute force — มันอ่านสิ่งที่เบราว์เซอร์อ่าน
  2. ขั้นที่ 2 — การค้นพบ schema GET /rest/v1/ เพียงครั้งเดียวด้วย anon key คืน OpenAPI schema สำหรับทุกตารางที่ anon role เห็นได้ สแกนเนอร์บันทึกชื่อตารางแต่ไม่อ่านข้อมูล row ในขั้นนี้
  3. ขั้นที่ 3 — probe การอ่านและเขียน สำหรับแต่ละตารางที่พบ สแกนเนอร์ส่ง SELECT แบบ anonymous หนึ่งครั้งด้วย limit=1 หากคืน rows มา RLS เป็นแบบ permissive สแกนเนอร์หยุดที่นั่น — มันไม่ enumerate rows, ไม่ paginate, ไม่แก้ไขข้อมูล INSERT probe ถูกจำกัดให้อยู่หลังการยืนยันสิทธิ์ความเป็นเจ้าของโดเมนและการ opt-in อย่างชัดเจน; มันไม่เคยทำกับเป้าหมายที่ยังไม่ได้ยืนยัน

ทุก finding มาพร้อมกับ URL request ที่ชัดเจน, response status, รูปแบบ response (เฉพาะ header) และชื่อตาราง AI fix prompt ที่ด้านล่างของ finding เป็น SQL block ที่ copy-paste ได้ คุณรันใน Supabase SQL editor

จะทำอย่างไรเมื่อสแกนเนอร์พบบางสิ่ง

ทุก RLS finding คือเหตุฉุกเฉิน runtime PostgREST endpoint สาธารณะถูกสแกนโดยผู้โจมตีภายในไม่กี่นาที ลำดับการแก้ไขเป็นไปอย่างเป็นกลไก:

  1. Audit ทุกตาราง รัน SELECT schemaname, tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public'; ใน Supabase SQL editor row ใดๆ ที่มี rowsecurity = false คือปัญหา
  2. เปิด RLS บนทุกตารางสาธารณะ เป็นค่าเริ่มต้น ENABLE ROW LEVEL SECURITY และ FORCE ROW LEVEL SECURITY บนทุกตารางที่สร้าง — ทำให้เป็น template ของ migration
  3. เขียน policy แยกคำสั่งทีละคำสั่ง อย่าใช้ FOR ALL USING (true) เขียน policy ที่ชัดเจนสำหรับ SELECT, INSERT, UPDATE, DELETE — แต่ละตัวจำกัดด้วย auth.uid() หรือคอลัมน์ org-id จาก auth.jwt()
  4. ตรวจสอบด้วยบัญชีที่สอง สมัครเป็นผู้ใช้คนละคน พยายามอ่านข้อมูลของผู้ใช้คนอื่นผ่าน REST API โดยตรง ถ้า response เป็น 200, policy เสียหาย
  5. สแกนใหม่ หลังจากใช้การแก้ไข รัน FixVibe scan อีกครั้งกับ URL เดิม Finding baas.supabase-rls ควรหายไป
sql
-- 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;

เปรียบเทียบกับสแกนเนอร์อื่นๆ อย่างไร

เครื่องมือ DAST ทั่วไปส่วนใหญ่ (Burp Suite, OWASP ZAP, Nessus) ไม่รู้จัก PostgREST พวกมันจะ crawl แอปของคุณ, เพิกเฉยต่อเส้นทาง /rest/v1/ และรายงานเฉพาะหน้า HTML ที่พวกมันเข้าใจ Snyk และ Semgrep เป็นเครื่องมือวิเคราะห์แบบ static — พวกมันค้นหาไฟล์ migration ใน repo ของคุณที่ขาดการเรียก RLS แต่ไม่สามารถพิสูจน์ว่าฐานข้อมูลที่ deploy แล้วถูกตั้งค่าผิด FixVibe อยู่ในช่องว่างนี้: passive, รู้จัก BaaS, มุ่งเน้นที่สิ่งที่ผู้โจมตีที่ไม่ผ่านการ authenticate สามารถพิสูจน์จาก URL สาธารณะ

คำถามที่พบบ่อย

สแกนเนอร์จะอ่านหรือแก้ไขข้อมูลของฉันหรือไม่?

ไม่ การสแกนแบบ passive ส่ง SELECT ... limit=1 มากที่สุดหนึ่งครั้งต่อตารางที่พบ เพื่อยืนยันว่า RLS อนุญาตการอ่านแบบ anonymous หรือไม่ สแกนเนอร์บันทึกรูปแบบ response ไม่ใช่เนื้อหา row INSERT, UPDATE และ DELETE probe ถูกจำกัดให้อยู่หลังการยืนยันสิทธิ์ความเป็นเจ้าของโดเมนและไม่เคยทำกับเป้าหมายที่ยังไม่ได้ยืนยัน

ใช้งานได้หรือไม่ถ้าโปรเจกต์ Supabase ของฉันถูกพักหรืออยู่บนโดเมนกำหนดเอง?

โปรเจกต์ที่ถูกพักจะคืน 503 ในทุก request — สแกนเนอร์รายงานว่าโปรเจกต์เข้าถึงไม่ได้ โดเมนกำหนดเองทำงานได้ตราบใดที่แอปที่ deploy แล้วยังคงโหลด Supabase client SDK ในเบราว์เซอร์; สแกนเนอร์ดึง URL ของโปรเจกต์จาก bundle ไม่ว่ากรณีใด

ถ้า anon key ของฉันถูกหมุนหรือ publishable key เปลี่ยน?

สแกนใหม่ สแกนเนอร์ดึง key ใหม่จาก bundle ปัจจุบันทุกครั้งที่รัน การหมุน key จะทำให้รายงานก่อนหน้าเท่านั้นที่ใช้ไม่ได้ ไม่ใช่สถานะ policy ของฐานข้อมูล

สแกนเนอร์ตรวจสอบโมเดล publishable key ใหม่ของ Supabase (<code>sb_publishable_*</code>) หรือไม่?

ใช่ ตัวตรวจจับรู้จักทั้ง anon JWT แบบเก่าและ key sb_publishable_* ที่ใหม่กว่า และจัดการพวกมันเหมือนกัน — ทั้งสองตั้งใจให้เป็นสาธารณะ และทั้งสองทำให้ RLS เป็นแนวป้องกันเพียงอย่างเดียว

ขั้นตอนถัดไป

รัน FixVibe scan ฟรีกับ URL production ของคุณ — check baas.supabase-rls เปิดใช้ในทุกแพ็กเกจรวมถึงระดับฟรี สำหรับการอ่านเพิ่มเติมเกี่ยวกับสิ่งอื่นๆ ที่อาจรั่วออกมาจากโปรเจกต์ Supabase โปรดดู Supabase service role key ถูกเปิดเผยใน JavaScript และ Supabase storage bucket checklist ความปลอดภัย สำหรับมุมมองภาพรวมข้ามผู้ให้บริการ BaaS ทั้งหมด อ่าน BaaS misconfiguration scanner

// สแกนพื้นผิว baas ของคุณ

ค้นหาตารางที่เปิดอยู่ก่อนที่คนอื่นจะพบ

ใส่ URL production ของคุณ FixVibe จะระบุผู้ให้บริการ BaaS ที่แอปของคุณติดต่อด้วย, ทำ fingerprint endpoint สาธารณะของพวกเขา และรายงานสิ่งที่ client ที่ไม่ได้ผ่านการ authenticate สามารถอ่านหรือเขียนได้ ฟรี ไม่ต้องติดตั้ง ไม่ต้องใช้บัตร

  • ระดับฟรี — 3 scans ต่อเดือน ไม่ต้องใช้บัตรในการสมัคร
  • การทำ fingerprint BaaS แบบ passive — ไม่ต้องยืนยันโดเมน
  • Supabase, Firebase, Clerk, Auth0, Appwrite และอื่นๆ
  • AI fix prompt ในทุก finding — วางกลับเข้าไปใน Cursor / Claude Code
เริ่มสแกน BaaS ฟรี

ไม่ต้องสมัครสมาชิก

Supabase RLS scanner: ค้นหาตารางที่ขาดหรือมี row-level security ที่เสียหาย — Docs · FixVibe