익명 키 하나로 회원 데이터가 털린다 — Supabase RLS의 진실
코드 한 줄 안 쓴 AI 앱이 3일 만에 475만 건을 노출했습니다. Supabase anon 키는 원래 공개되는 것 — 데이터를 지키는 진짜 자물쇠는 RLS입니다. 왜 새는지와 30초 점검법.

2026년 1월, 이름도 없던 한 AI 앱이 출시 3일 만에 475만 건의 사용자 레코드를 인터넷에 그대로 노출했습니다. API 토큰 150만 개, 이메일 3만 5천 건이 로그인 한 번 없이 조회됐죠. 만든 사람은 “코드를 한 줄도 직접 쓰지 않았다”고 했습니다.
특별히 실력이 없어서가 아닙니다. 오늘도 Lovable·Cursor·Bolt로 만들어지는 수많은 앱이 정확히 같은 구멍을 안고 배포됩니다. 원인은 딱 하나, Supabase의 RLS(행 수준 보안)를 켜지 않은 것이에요.
anon 키가 브라우저에 있는 건 정상입니다
Supabase로 프론트엔드를 만들면 이런 코드가 브라우저로 그대로 내려갑니다. 누구나 개발자 도구로 볼 수 있죠.
const supabase = createClient(
'https://xxxx.supabase.co',
'eyJhbGciOiJIUzI1...' // anon key — 공개돼도 정상
)
여기까지는 설계 의도대로입니다. anon 키는 “익명 사용자”라는 신분증일 뿐, 그 자체로는 아무 권한도 주지 않아요. 권한을 결정하는 건 데이터베이스 쪽 규칙입니다.
데이터를 지키는 유일한 자물쇠, RLS
RLS는 테이블의 각 행에 “누가 이 행을 읽고 쓸 수 있는가”를 정하는 규칙입니다. RLS를 켜지 않으면, 익명 사용자가 보낸 요청이 테이블 전체를 그대로 반환합니다. 켜면, 정책에 맞는 행만 통과하죠.
실제로는 이렇게 털립니다
RLS가 꺼진 테이블은 별도의 해킹 도구도 필요 없습니다. 브라우저에 노출된 주소와 anon 키만 있으면, 터미널 한 줄로 회원 테이블이 쏟아집니다.
curl 'https://xxxx.supabase.co/rest/v1/users?select=*' \
-H 'apikey: eyJhbGciOiJIUzI1...'
# RLS OFF → users 테이블 전체가 JSON으로 반환됨
지금 30초 만에 점검하세요
Supabase 대시보드에서 각 테이블의 RLS 상태를 확인하고, 꺼져 있다면 켜세요. 켜기만 하면 기본은 전면 차단이 되고, 이후 필요한 접근만 정책으로 열어줍니다.
-- 1) RLS 켜기 (기본 전면 차단)
alter table public.users enable row level security;
-- 2) 본인 데이터만 읽도록 정책 추가
create policy "본인만 읽기" on public.users
for select using ( auth.uid() = id );
using (true)) 열어두면 켜지 않은 것과 똑같아요. 실제로 익명 요청이 데이터를 반환하는지 직접 확인해야 합니다.
설정만 보지 말고, 실제로 뚫리는지 확인하세요
“RLS를 켰다”와 “정말 안 뚫린다”는 다릅니다. 코드보안은 그 anon 키로 실제 읽기 요청을 보내(소유권 인증 후, 읽기 전용으로) 데이터가 반환되는지를 직접 검증합니다. 자세한 실수 유형은 바이브코딩 보안 구멍 5가지에서 이어집니다.