RLS를 끈 Supabase는 공개 데이터베이스다
2025년에 Lovable로 만든 170개 이상의 앱에서 사용자 데이터가 통째로 노출된 사건이 있었다. 공통점은 하나 — Supabase RLS(Row Level Security)가 꺼져 있었다.
Supabase anon key는 클라이언트에 노출되는 게 의도된 설계다. 대신 RLS로 "이 key로는 이 데이터만 볼 수 있다"는 규칙을 정의해야 한다. RLS가 없으면 anon key 하나로 테이블 전체를 읽고, 쓰고, 삭제할 수 있다.
가장 흔한 실수 3가지
실수 1: RLS를 아예 활성화 안 함
Supabase 대시보드에서 테이블을 만들면 기본값이 RLS 비활성화다. AI가 생성한 코드도 대부분 RLS 설정을 포함하지 않는다.
-- RLS 활성화 (이것만 해도 일단 잠긴다)
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
RLS를 활성화하면 아무 정책도 없는 상태에서는 아무것도 읽을 수 없다. 이후 필요한 접근만 허용하는 정책을 추가한다.
실수 2: 너무 느슨한 정책
-- 위험: 로그인한 모든 사용자가 모든 행 열람 가능
CREATE POLICY "allow_all_authenticated"
ON users FOR SELECT
TO authenticated
USING (true);
-- 안전: 본인 데이터만
CREATE POLICY "own_data_only"
ON users FOR SELECT
USING (auth.uid() = id);
실수 3: service_role key를 클라이언트에 노출
service_role key는 RLS를 완전히 무시한다. 절대 클라이언트 코드에 넣으면 안 된다. anon key만 클라이언트에 사용하고, service_role은 서버 사이드 전용으로 관리한다.
# .env.local (클라이언트용 — Next.js 등)
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ... # anon key만
# .env (서버용)
SUPABASE_SERVICE_ROLE_KEY=eyJ... # NEXT_PUBLIC_ 붙이지 않기
RLS 제대로 설정된 것인지 검증하는 법
설정 후에는 반드시 다른 사용자 계정으로 로그인해서 남의 데이터가 보이는지 테스트해야 한다. Supabase 대시보드의 "Auth > User" 메뉴에서 테스트 계정을 만들고 SQL Editor에서 직접 쿼리해보는 게 가장 확실하다.
-- 다른 유저 ID로 테스트
SET LOCAL role TO authenticated;
SET LOCAL request.jwt.claims TO '{"sub": "other-user-uuid"}';
SELECT * FROM orders; -- 빈 결과가 나와야 정상