Supabase RLS 보안 체크리스트: 출시 전 8가지
Supabase에서 Row Level Security(RLS)는 “누가 어떤 행을 읽고 바꿀 수 있는지”를 데이터베이스가 직접 결정하는 장치입니다. 로그인 UI가 멀쩡해도 RLS가 빠지거나 정책이 넓으면 REST API로 다른 사용자 데이터가 열릴 수 있습니다.
1. 공개 스키마의 모든 테이블에 RLS가 켜져 있는가
새 테이블을 만든 뒤 RLS 활성화를 놓치는 경우가 가장 흔합니다. 사용자 데이터가 있는 테이블뿐 아니라 조인 테이블, 초대, 결제 상태, 관리자 메모도 확인하세요.
2. service_role 키가 프론트엔드에 없는가
service_role은 RLS를 우회할 수 있으므로 서버 전용입니다. 브라우저 번들, HTML, 공개 환경변수, 오류 로그에 들어갔다면 정책이 완벽해도 무력화됩니다. 노출 시 코드를 고치는 것만으로 끝내지 말고 키를 회전하세요.
3. SELECT 정책이 소유자만 허용하는가
개인 데이터는 보통 user_id = auth.uid() 조건이 필요합니다. 조직형 서비스는 현재 사용자가 해당 조직의 멤버인지 membership 테이블로 확인해야 합니다. 단순히 auth.uid() is not null이면 모든 로그인 사용자가 모든 행을 읽을 수 있습니다.
4. INSERT·UPDATE에 WITH CHECK가 있는가
USING은 기존 행을 볼 수 있는지, WITH CHECK는 새 값이 허용되는지를 판단합니다. UPDATE에서 WITH CHECK를 빼면 소유자 필드를 다른 사람 ID로 바꾸는 식의 권한 문제가 생길 수 있습니다.
5. DELETE 정책이 과도하게 넓지 않은가
읽기 정책을 복사해 삭제에도 붙이면 공유 데이터까지 지울 수 있습니다. 소유자 삭제, 조직 관리자 삭제, 소프트 삭제 중 제품 규칙에 맞는 조건을 별도로 정의하세요.
6. Storage 버킷 정책도 별도로 확인했는가
DB 테이블의 RLS와 Storage 객체 정책은 별개입니다. 비공개 파일은 public 버킷에 두지 말고, 업로드 경로의 사용자 ID와 auth.uid()를 비교하며 MIME·크기 제한도 서버에서 검증하세요.
7. 두 개의 테스트 계정으로 교차 검증했는가
계정 A가 만든 행의 ID를 계정 B 요청에 넣어 SELECT·UPDATE·DELETE를 각각 시도하세요. 화면에서 링크가 안 보이는지만 확인해서는 부족합니다. HTTP 응답과 실제 데이터 변경 여부를 봐야 합니다.
8. 관리자·서버 함수가 RLS를 우회하지 않는가
Security Definer 함수와 서버의 service_role 사용은 필요한 범위로 제한해야 합니다. 사용자 입력으로 임의 사용자 ID를 받아 조회하는 서버 함수는 RLS 바깥에서 IDOR를 만들 수 있습니다.
공식 참고 자료
- Supabase Row Level Security 공식 문서 — exposed schema의 RLS, USING·WITH CHECK와 service role 기준
- OWASP Broken Access Control — 객체 소유권과 서버 측 권한 검사 원칙
Storage를 쓴다면 함께 확인하세요
RLS는 로그인 기능을 대신하지 않습니다. OAuth redirect, JWT 검증, MFA와 세션 정책은 Supabase Auth 보안 체크리스트에서 확인하세요. 프로필 이미지·문서 업로드가 있다면 Supabase Storage 버킷·RLS·파일 업로드 체크리스트에서 public·private 버킷, 사용자별 경로와 signed URL까지 이어서 점검하세요.