← 무료 위험 확인
Supabase · RLS

Supabase RLS 보안 체크리스트: 출시 전 8가지

AI 앱 출시 전 인증·데이터·키 보안을 점검하는 장면
작성·검토: LaunchGuard 편집팀 · 최초 발행 2026-07-23 · 최종 수정 2026-07-29 · 11분 읽기

Supabase에서 Row Level Security(RLS)는 “누가 어떤 행을 읽고 바꿀 수 있는지”를 데이터베이스가 직접 결정하는 장치입니다. 로그인 UI가 멀쩡해도 RLS가 빠지거나 정책이 넓으면 REST API로 다른 사용자 데이터가 열릴 수 있습니다.

먼저 결론 — 출시 통과 기준은 “RLS 토글이 켜짐”이 아닙니다. 공개 API에 노출된 모든 테이블에 RLS가 있고, 비로그인·소유자·다른 사용자 요청의 허용/차단 결과가 제품 권한표와 일치해야 합니다. 아래 SQL은 구조 확인용이며 실제 데이터 값이나 운영 비밀키를 공유하지 마세요.

출시 판정표부터 만드세요

요청소유자 A다른 사용자 B비로그인
내 행 SELECT허용차단 또는 0행차단 또는 0행
내 user_id로 INSERT허용자기 user_id만 허용차단
소유자 변경 UPDATE차단차단차단
내 행 DELETE제품 규칙대로차단차단

공개 프로필처럼 누구나 읽는 기능은 예외일 수 있습니다. 그 경우에도 읽기만 공개하고 이메일, 결제 상태, 내부 메모 같은 비공개 열을 같은 테이블에서 그대로 노출하지 않는지 확인하세요.

1. 공개 스키마의 모든 테이블에 RLS가 켜져 있는가

새 테이블을 만든 뒤 RLS 활성화를 놓치는 경우가 가장 흔합니다. 사용자 데이터가 있는 테이블뿐 아니라 조인 테이블, 초대, 결제 상태, 관리자 메모도 확인하세요.

Supabase SQL Editor에서 다음 읽기 전용 쿼리로 public 스키마의 일반 테이블과 RLS 상태를 먼저 목록화할 수 있습니다.

select
  n.nspname as schema_name,
  c.relname as table_name,
  c.relrowsecurity as rls_enabled,
  c.relforcerowsecurity as rls_forced
from pg_class c
join pg_namespace n on n.oid = c.relnamespace
where n.nspname = 'public'
  and c.relkind = 'r'
order by c.relname;

rls_enabled = false인 표가 발견되면 데이터 성격과 API 노출 여부를 확인하고, 필요한 테이블에 alter table public.<table_name> enable row level security;를 적용하세요. Table Editor에서 만든 표는 기본 활성화되지만 SQL Editor나 migration으로 만든 표는 별도 확인이 필요합니다.

2. service_role 키가 프론트엔드에 없는가

service_role은 RLS를 우회할 수 있으므로 서버 전용입니다. 브라우저 번들, HTML, 공개 환경변수, 오류 로그에 들어갔다면 정책이 완벽해도 무력화됩니다. 노출 시 코드를 고치는 것만으로 끝내지 말고 키를 회전하세요.

3. SELECT 정책이 소유자만 허용하는가

개인 데이터는 보통 user_id = auth.uid() 조건이 필요합니다. 조직형 서비스는 현재 사용자가 해당 조직의 멤버인지 membership 테이블로 확인해야 합니다. 단순히 auth.uid() is not null이면 모든 로그인 사용자가 모든 행을 읽을 수 있습니다.

create policy "users read own rows"
on public.todos
for select
to authenticated
using ((select auth.uid()) = user_id);

비로그인 요청에서 auth.uid()null입니다. 의도를 분명히 하려면 인증 필요 조건을 명시하고, 권한 정책의 TO authenticated 범위가 빠지지 않았는지 확인하세요.

4. INSERT·UPDATE에 WITH CHECK가 있는가

USING은 기존 행을 볼 수 있는지, WITH CHECK는 새 값이 허용되는지를 판단합니다. UPDATE에서 WITH CHECK를 빼면 소유자 필드를 다른 사람 ID로 바꾸는 식의 권한 문제가 생길 수 있습니다.

create policy "users update own rows"
on public.todos
for update
to authenticated
using ((select auth.uid()) = user_id)
with check ((select auth.uid()) = user_id);

UPDATE에는 대상 행을 볼 수 있는 SELECT 정책도 필요합니다. 정책이 있는데 업데이트가 항상 실패한다면 UPDATE 정책만 보지 말고 SELECT 정책과 열 권한을 함께 확인하세요.

5. DELETE 정책이 과도하게 넓지 않은가

읽기 정책을 복사해 삭제에도 붙이면 공유 데이터까지 지울 수 있습니다. 소유자 삭제, 조직 관리자 삭제, 소프트 삭제 중 제품 규칙에 맞는 조건을 별도로 정의하세요.

6. Storage 버킷 정책도 별도로 확인했는가

DB 테이블의 RLS와 Storage 객체 정책은 별개입니다. 비공개 파일은 public 버킷에 두지 말고, 업로드 경로의 사용자 ID와 auth.uid()를 비교하며 MIME·크기 제한도 서버에서 검증하세요.

7. 두 개의 테스트 계정으로 교차 검증했는가

계정 A가 만든 행의 ID를 계정 B 요청에 넣어 SELECT·UPDATE·DELETE를 각각 시도하세요. 화면에서 링크가 안 보이는지만 확인해서는 부족합니다. HTTP 응답과 실제 데이터 변경 여부를 봐야 합니다.

  1. 테스트 전용 계정 A와 B를 만들고 운영 고객 데이터가 아닌 테스트 행을 사용합니다.
  2. A로 행을 만든 뒤 행 ID와 소유자 ID를 기록합니다.
  3. B 세션에서 같은 행 ID를 조회하고, 값 변경과 삭제를 각각 요청합니다.
  4. 세션을 제거한 비로그인 상태에서도 같은 요청을 반복합니다.
  5. 화면 메시지만 보지 말고 응답 상태, 반환 행 수, 실제 DB 변경 여부를 함께 기록합니다.

보안 테스트는 허가된 프로젝트와 테스트 데이터에서만 수행하세요. “0행 반환”과 “권한 오류”는 모두 정책상 정상일 수 있으므로 제품 API가 기대하는 동작을 사전에 정해야 합니다.

8. 관리자·서버 함수가 RLS를 우회하지 않는가

Security Definer 함수와 서버의 service_role 사용은 필요한 범위로 제한해야 합니다. 사용자 입력으로 임의 사용자 ID를 받아 조회하는 서버 함수는 RLS 바깥에서 IDOR를 만들 수 있습니다.

Postgres 15 이상에서 뷰가 호출자의 RLS를 따르게 하려면 security_invoker = true 설정을 검토하세요. 이전 버전이나 관리자용 뷰라면 anon·authenticated 권한을 회수하거나 API에 노출되지 않은 스키마로 옮기는 편이 안전합니다.

정책이 맞아도 느리다면 확인할 것

권한을 넓혀 성능 문제를 숨기면 안 됩니다. Supabase는 정책에서 비교하는 user_id 같은 열에 인덱스를 만들고, 행마다 결과가 달라지지 않는 인증 함수는 (select auth.uid())처럼 감싸는 방법을 안내합니다. 먼저 실행 계획과 실제 쿼리 패턴을 측정한 뒤 적용하세요.

중요 — 공개 URL 스캔은 Supabase 사용 여부와 키 노출 신호는 찾을 수 있지만, RLS가 실제로 다른 사용자 데이터를 막는지는 테스트 계정으로 요청을 재현해야 확인할 수 있습니다.

공식 참고 자료

Supabase RLS 자주 묻는 질문

Supabase에서 RLS만 켜면 데이터가 안전한가요?

아닙니다. RLS를 활성화한 뒤 각 작업에 맞는 정책을 만들고 anon, 로그인 사용자, 다른 사용자 계정으로 실제 요청을 재현해야 합니다. 서버의 service_role 키 노출 여부와 Storage 정책도 별도로 확인해야 합니다.

USING과 WITH CHECK는 무엇이 다른가요?

USING은 기존 행을 읽거나 수정 대상으로 선택할 수 있는지 판단하고, WITH CHECK는 INSERT 또는 UPDATE 뒤의 새 행이 정책 조건을 만족하는지 판단합니다.

RLS 테스트는 계정 하나로 충분한가요?

충분하지 않습니다. 비로그인 요청, 소유자 계정 A, 다른 사용자 계정 B를 분리하고 같은 행 ID로 SELECT, INSERT, UPDATE, DELETE 결과를 비교해야 교차 사용자 접근을 확인할 수 있습니다.

service_role 키를 브라우저에서 써도 되나요?

안 됩니다. service_role은 RLS를 우회할 수 있는 서버 전용 비밀입니다. 브라우저 번들, 공개 환경변수, HTML 또는 로그에 노출됐다면 즉시 키를 회전하고 서버 경로로 옮겨야 합니다.

Storage를 쓴다면 함께 확인하세요

RLS는 로그인 기능을 대신하지 않습니다. OAuth redirect, JWT 검증, MFA와 세션 정책은 Supabase Auth 보안 체크리스트에서 확인하세요. 프로필 이미지·문서 업로드가 있다면 Supabase Storage 버킷·RLS·파일 업로드 체크리스트에서 public·private 버킷, 사용자별 경로와 signed URL까지 이어서 점검하세요.

Supabase 앱을 출시하기 전 점검하세요

무료 예비 진단 뒤, 테스트 계정으로 RLS·IDOR·인증 흐름까지 상세 점검할 수 있습니다.

무료로 위험 확인