Supabase RLS Tester가 말해주는 것: 권한 정책도 테스트 케이스가 필요하다
RLS 정책이 “있는 것”과 “내가 의도한 사용자에게만 행을 보여주는 것”을 어떻게 구분할 수 있을까?
Supabase RLS Tester가 말해주는 것: 권한 정책도 테스트 케이스가 필요하다
- 카테고리: web_app_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: RLS 정책이 “있는 것”과 “내가 의도한 사용자에게만 행을 보여주는 것”을 어떻게 구분할 수 있을까?
- 핵심 출처:
- Feature Preview: RLS Tester - 게시일 미표기, 2026-06-10 확인
- Row Level Security - 게시일 미표기, 2026-06-10 확인
- Postgres Roles - 게시일 미표기, 2026-06-10 확인
- PostgreSQL Row Security Policies - PostgreSQL 18 current docs, 2026-06-10 확인
- PostgreSQL CREATE POLICY - PostgreSQL 18 current docs, 2026-06-10 확인
1. 왜 지금 봐야 하나
Supabase가 RLS Tester를 feature preview로 공개했다. 핵심 기능은 단순하다. 특정 사용자인 것처럼 SQL SELECT 쿼리를 실행해 보고, 어떤 Row Level Security 정책이 평가되는지 확인하는 UI다. Supabase는 지금까지 RLS 정책의 정확성을 검증하는 일이 빈틈으로 남아 있었고, 여러 GitHub 논의에서 같은 문제가 반복됐다고 설명한다.
이 소식은 “대시보드에 새 디버깅 화면이 생겼다”로 끝나지 않는다. 웹앱 개발자에게 더 중요한 메시지는 이것이다.
권한 정책은 설정값이 아니라 테스트해야 하는 제품 동작이다.
Supabase 같은 BaaS를 쓰면 브라우저에서 supabase-js로 데이터 API를 직접 호출하는 구조가 자연스럽다. 이때 서버 코드가 없는 대신, 데이터베이스의 GRANT, 역할, RLS 정책이 사실상 백엔드 권한 레이어가 된다. 문제는 권한 버그가 일반 버그처럼 크게 실패하지 않는다는 점이다.
- 너무 막힌 정책은 “데이터가 안 나와요”로 보인다.
- 너무 열린 정책은 조용히 다른 사용자의 행을 노출한다.
auth.uid()가null인 경우는 에러가 아니라 “조건이 false가 됨”으로 끝난다.- 여러 permissive policy가
OR로 합쳐지면, 의도보다 넓은 접근이 생길 수 있다.
그래서 RLS Tester의 등장은 도구 하나보다 관점 전환에 가깝다. “정책을 작성했다”에서 “사용자·역할·쿼리별 기대 결과를 확인했다”로 넘어가야 한다.
2. 핵심 개념
RLS를 이해할 때는 세 층을 나눠야 한다.
1) GRANT: 이 역할이 테이블에 접근할 자격이 있는가
Postgres의 기본 권한은 GRANT와 REVOKE다. 어떤 역할이 테이블에 SELECT, INSERT, UPDATE, DELETE를 할 수 있는지 먼저 결정한다. Supabase 문서도 새 테이블을 Data API에 노출하려면 역할별 권한 부여와 RLS를 별도로 생각하라고 설명한다.
중요한 점은 GRANT가 “문을 열어주는 층”이라는 것이다. 문이 닫혀 있으면 RLS까지 도달하지 않는다. 반대로 문이 열려 있어도 RLS가 행 단위로 다시 걸러야 한다.
2) 역할: 요청은 어떤 Postgres role로 실행되는가
Supabase에서 API 요청은 보통 anon 또는 authenticated 역할로 매핑된다. 로그인하지 않은 요청은 anon, 로그인한 요청은 authenticated다. 여기서 헷갈리기 쉬운 부분이 있다. Supabase Auth의 anonymous user는 Postgres의 anon 역할과 같지 않다. 익명 로그인 사용자는 여전히 authenticated 역할을 쓸 수 있고, JWT claim으로 구분해야 한다.
또 service_role은 RLS를 우회할 수 있는 강한 역할이다. 서버 배치나 관리자 작업에는 필요할 수 있지만, 프론트엔드에 노출되면 RLS 설계 자체가 무력화된다.
3) RLS policy: 같은 테이블 안에서 어떤 행이 보이는가
PostgreSQL 문서는 RLS를 테이블 단위 정책으로 설명한다. ALTER TABLE ... ENABLE ROW LEVEL SECURITY를 켜면, 일반 접근은 정책이 허용하는 행만 볼 수 있다. 정책이 없으면 default deny가 적용되어 행이 보이지 않는다.
정책은 명령과 역할에 따라 달라질 수 있다.
create policy "users can read own todos"
on todos
for select
to authenticated
using (auth.uid() is not null and auth.uid() = user_id);
여기서 USING은 기존 행이 사용자에게 보이는지를 판단한다. WITH CHECK는 새로 만들거나 바꾸려는 행이 허용되는지를 판단한다. PostgreSQL CREATE POLICY 문서는 USING이 false 또는 null이면 해당 행은 보이지 않고, WITH CHECK가 false 또는 null이면 insert/update가 거부된다고 설명한다.
3. 최신 이슈와 연결
RLS Tester preview에서 눈에 띄는 제약은 “현재는 SELECT만 지원한다”는 점이다. Supabase는 INSERT, UPDATE, DELETE 테스트가 실제 트리거, Edge Function, 외부 HTTP 호출 같은 부작용을 만들 수 있기 때문에 우선 제외했다고 설명한다.
이 제약이 오히려 핵심을 잘 보여준다. 권한 테스트는 두 종류로 나뉜다.
- 읽기 권한 테스트: 어떤 사용자가 어떤 행을 볼 수 있는가.
- 쓰기 권한 테스트: 어떤 사용자가 어떤 행을 만들거나 바꿀 수 있는가.
읽기 테스트는 상대적으로 안전하다. 물론 민감 데이터 조회라면 테스트 계정과 샘플 데이터가 필요하지만, DB 상태를 바꾸지는 않는다. 반면 쓰기 테스트는 “정책이 맞는지 보려고 실행한 쿼리”가 실제 알림, 결제 상태 변경, 웹훅, 감사 로그를 발생시킬 수 있다.
그래서 RLS Tester의 SELECT 우선 접근은 현실적인 선택이다. 권한 검증도 테스트 피라미드처럼 봐야 한다.
- 먼저 읽기 정책을 다양한 사용자로 빠르게 검증한다.
- 쓰기 정책은 로컬/브랜치/샌드박스 DB에서 검증한다.
- 운영 DB에서는 mutation 테스트를 직접 때리지 않는다.
또 Supabase preview는 클라이언트 라이브러리 코드 일부를 AI Assistant가 SQL로 추론해 테스트하는 기능도 언급한다. 단, 문서는 “AI가 변환한 것이므로 반드시 검증하라”고 못박는다. AI 시대의 권한 검증에서 중요한 태도다. AI는 테스트 입력을 만드는 데 도움을 줄 수 있지만, 권한 판단의 최종 책임을 대신하지 않는다.
4. 개발자 관점 해석
작은 웹앱 팀에서 RLS는 자주 “나중에 한 번 점검할 보안 설정”으로 밀린다. 하지만 실제 제품에서는 세 가지 이유로 빨리 테스트 체계가 필요하다.
1) 정책은 시간이 지나며 넓어진다
처음에는 profiles에서 “본인 row만 읽기”로 시작한다. 이후 관리자 화면, 팀 공유, 초대 링크, 공개 프로필, 고객 지원 계정이 추가된다. 이때 새 policy를 하나씩 붙이다 보면 permissive policy가 OR로 합쳐진다는 사실을 잊기 쉽다. “관리자는 모두 읽기” 정책이 “팀원은 팀 row 읽기” 정책과 합쳐지며, 의도하지 않은 조합이 생긴다.
PostgreSQL 문서는 permissive policy는 기본적으로 OR, restrictive policy는 AND로 결합된다고 설명한다. 그러므로 “정책이 여러 개 있다”는 말은 곧 “불리언 식이 합성된다”는 뜻이다. UI에서 정책 목록을 보는 것만으로는 최종 결과를 알기 어렵다.
2) 권한 버그는 실패 모양이 다르다
일반 API 버그는 500 에러, 타입 에러, 화면 깨짐으로 드러난다. RLS 버그는 더 조용하다.
select()결과가 빈 배열이면 정책이 막은 것인지 데이터가 없는 것인지 헷갈린다.auth.uid()가 null이라서 false가 된 경우도 “정상적으로 아무 것도 안 보임”처럼 보인다.- 너무 열린 정책은 QA 계정 하나로는 발견되지 않는다.
그래서 RLS 테스트는 “쿼리가 성공했는가”가 아니라 “이 사용자에게 보여야 할 행 수와 대표 row가 맞는가”를 확인해야 한다.
3) AI 코딩 도구는 스키마를 만들지만, 권한 의도는 모른다
AI 코딩 도구에게 “메모 앱 만들어줘”라고 하면 notes 테이블, CRUD 화면, supabase-js 호출은 빠르게 만든다. 하지만 “개인 메모인지, 팀 공유 메모인지, 초대받은 사용자는 읽기만 가능한지, 삭제 권한은 소유자만인지”는 제품 의도다. 이 의도를 테스트 케이스로 쓰지 않으면 AI가 만든 스키마와 정책은 그럴듯하지만 위험할 수 있다.
RLS Tester는 AI가 만든 SQL을 불신하라는 의미가 아니다. AI가 만든 권한 정책을 사용자 시나리오로 검증하는 루프가 필요하다는 뜻이다.
5. 내 프로젝트에 적용할 체크포인트
아래 질문에 “예”라고 답할 수 없다면 RLS는 아직 운영 가능한 상태가 아닐 수 있다.
- 노출 schema의 모든 테이블에 RLS가 켜져 있는가?
- Data API로 접근해야 하는 테이블에만 필요한
GRANT가 있는가? anon,authenticated,service_role의 의미를 팀원이 구분해서 설명할 수 있는가?auth.uid() = user_id정책에auth.uid() is not null같은 명시적 조건을 넣었는가?- 정책 이름이 제품 규칙을 설명하는가? 예:
team_members_can_read_team_tasks - SELECT 정책과 INSERT/UPDATE 정책을 구분했는가?
USING과WITH CHECK의 차이를 코드 리뷰에서 확인하는가?- 관리자·팀원·소유자·초대 사용자·로그아웃 사용자별 기대 결과를 샘플 데이터로 적어두었는가?
- permissive policy가 여러 개 있을 때 최종 결과가
OR로 넓어지는지 확인했는가? - 테스트 계정의 JWT claim, 조직 id, team id가 실제 운영 구조와 비슷한가?
특히 팀 기반 제품이라면 owner_id = auth.uid() 하나로 끝나지 않는다. team_members 같은 다른 테이블을 참조하는 정책이 생기고, 이때 성능·경합·정보 누출 문제가 생길 수 있다. PostgreSQL 문서도 정책식에서 다른 행이나 테이블을 조회할 수 있지만, race condition과 정보 누출에 주의하라고 설명한다.
6. 오늘 10분 액션
오늘은 도구를 완벽히 붙이는 대신, 권한 테스트 표를 하나 만든다.
0~2분: 가장 민감한 테이블 하나 고르기
예: profiles, notes, orders, messages, team_tasks 중 하나를 고른다.
2~5분: 사용자 4종류를 적기
아래처럼 최소 4개 행을 만든다.
| 시나리오 | 역할/상태 | 기대 결과 |
|---|---|---|
| 로그아웃 사용자 | anon | 공개 row만 보이거나 0개 |
| 소유자 | authenticated, auth.uid() = owner_id | 자기 row 표시 |
| 다른 로그인 사용자 | authenticated, 다른 uid | 민감 row 0개 |
| 팀원 또는 관리자 | authenticated, team/member claim 있음 | 허용된 team row만 표시 |
5~8분: SELECT 쿼리 하나를 기대 행 수와 함께 적기
select id, owner_id, team_id, visibility
from notes
order by created_at desc
limit 20;
각 사용자별로 “몇 개가 보여야 하는지”와 “절대 보이면 안 되는 row id”를 적는다.
8~10분: 정책 이름과 테스트 이름을 맞추기
정책 이름이 Enable read access for authenticated users처럼 넓다면 바꿀 후보를 적는다. 좋은 이름은 테스트 문장과 거의 같다.
정책: team_members_can_read_team_notes
테스트: 팀 멤버 A는 team_id가 같은 note만 읽을 수 있다
Supabase RLS Tester preview를 사용할 수 있다면 위 SELECT부터 실행해 본다. 아직 preview를 쓰지 못한다면 SQL 파일이나 README에 이 표를 남기는 것만으로도 다음 코드 리뷰 품질이 달라진다.
7. 더 볼 자료
- Feature Preview: RLS Tester
- Row Level Security
- Postgres Roles
- PostgreSQL Row Security Policies
- PostgreSQL CREATE POLICY
중복 회피 메모
최근 웹앱 글은 Supabase Auth 이메일 운영, Next.js AI 디버깅, Expo 운영 전환, Android 배포 신원 검증, Next.js 인증 경계, Supabase 신규 테이블 GRANT/RLS 기본값을 다뤘다. 이번 글은 Supabase를 다시 다루지만, 발신 이메일이나 신규 테이블 노출 기본값이 아니라 RLS 정책을 사용자 시나리오별 테스트 케이스로 검증하는 방법에 집중했다. 특히 RLS Tester preview의 SELECT 전용 제약을 “권한 테스트의 안전한 범위와 mutation 테스트의 부작용”이라는 실무 판단으로 해석했다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)