ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Supabase][보안] RLS는 켰는데 데이터가 보이는 경우 service_role과 Authorization 헤더부터 확인하는 법
    기타개발지식/풀스택개발 2026. 6. 27. 09:15

    IT 리서치 노트

    [Supabase][보안] RLS는 켰는데 데이터가 보이는 경우 service_role과 Authorization 헤더부터 확인하는 법

    Supabase에서 RLS를 켰는데도 데이터가 계속 보이면 많은 팀이 바로 정책 SQL부터 다시 본다. 하지만 2026년 6월 27일 기준 Supabase 공식 문서를 다시 읽어 보면, 이 문제는 policy 문장보다 service_role 사용 여부와 Authorization 헤더, grants, 사용자 세션 섞임에서 더 자주 시작된다. 이 글은 데이터가 보이는 상황을 기준으로 무엇부터 확인해야 시간을 덜 쓰는지 정리한 것이다.

    1. 개요

    결론부터 말하면 Supabase에서 RLS 적용 여부는 createClient에 넣은 키 이름보다 실제 요청의 Authorization 헤더와 현재 role이 더 중요하다. service_role Authorization 헤더면 정책을 우회할 수 있고, 반대로 service key로 시작했더라도 사용자 세션이 섞이면 다시 사용자 정책을 따를 수 있다. 그래서 데이터가 보이는 증상은 policy SQL보다 header, session, grants, 호출 위치를 먼저 봐야 빨리 좁혀진다.

    이미 service_role key를 브라우저에 넣으면 안 되는 글이 권한 노출 자체를 다뤘다면, 이번 글은 '정책을 켰는데 왜 아직 보이지'라는 다음 증상에 대한 점검 순서다. 서버 권한을 넓게 두는 습관이 왜 위험한지는 권한을 workflow별로 줄이는 글과 같이 보면 감각이 더 맞는다.

    2. 어디서 실제로 막히는가

    현장에서 자주 만나는 증상은 세 가지다. 첫째, RLS 정책은 분명 있는데 일부 요청에서만 데이터가 다 보인다. 둘째, 서버에서는 정상인데 브라우저에서는 보이면 안 되는 row가 보인다. 셋째, 같은 admin client를 여러 곳에서 재사용하다가 사용자 세션이 덮어써지거나, 반대로 사용자용 client에 service key가 섞여 버린다.

    이때 가장 흔한 오해는 'service key를 넣었으니 언제나 관리자 권한으로 동작하겠지' 또는 'RLS를 켰으니 policy만 맞으면 되겠지'다. 공식 troubleshooting 문서는 RLS가 Authorization 헤더 기준으로 결정된다고 분명히 적고 있고, API 보안 문서는 grants와 RLS를 별도 단계로 설명한다. 즉 RLS는 행 단위 제어이고, 그 전에 어떤 role이 객체에 도달하는지가 먼저다.

    또 Next.js SSR, edge function, cron job, 백엔드 API가 모두 같은 프로젝트에 붙어 있으면 한 번의 createClient 패턴이 모든 경로를 대표하지 못한다. 어떤 요청은 브라우저 세션을 타고, 어떤 요청은 서버 관리자 키를 타고, 어떤 요청은 로그인된 사용자 토큰이 덮어쓴다. 이 구분이 없으면 동일한 테이블 쿼리인데도 환경마다 결과가 달라진다.

    • 증상: RLS를 켰는데 특정 경로에서만 모든 row가 보인다.
    • 실패: createClient에 넣은 키와 실제 Authorization 헤더가 같다고 가정한다.
    • 막힘: 브라우저 경로와 서버 관리자 경로를 같은 client 인스턴스로 섞어 쓴다.
    • 누락: grants와 role을 보지 않고 policy SQL만 반복 수정한다.
    증상 먼저 볼 곳 판단 기준
    데이터가 모두 보인다 Authorization 헤더 service_role이 들어가는지 확인한다
    환경별 결과가 다르다 호출 위치와 세션 흐름 브라우저/SSR/배치 경로를 분리한다
    정책 수정이 계속 헛돈다 grants와 role 객체 접근 role이 이미 과한지 본다

    3. 실무에서 적용하는 순서

    실무 점검 순서는 네 단계가 가장 짧다. 먼저 실제 요청의 Authorization 헤더가 무엇인지 로그나 프록시에서 본다. 다음으로 호출 위치를 브라우저, SSR, server action, background job으로 나눈다. 세 번째로 grants와 사용 중인 Postgres role을 확인한다. 마지막으로 그 뒤에야 RLS policy SQL을 점검한다.

    1. 요청의 Authorization 헤더를 먼저 확인한다.
    2. 브라우저 경로와 서버 관리자 경로를 분리한다.
    3. role과 grants가 객체 접근을 어떻게 열어 두는지 본다.
    4. 마지막으로 RLS policy SQL을 검토한다.

    이 순서를 팀 문서에 남겨 두면 재발 때도 빠르다. 특히 service_role을 꼭 써야 하는 관리자 작업은 별도 admin client로 분리하고, 사용자 세션을 타는 경로에서는 persistSession과 토큰 덮어쓰기 흐름을 명확히 남기는 편이 좋다. 브라우저 코드는 publishable 또는 anon 경로만, 서버 배치와 내부 관리자 API만 service_role을 가지게 두면 의도하지 않은 우회가 줄어든다.

    점검 메모 예시
    request_path=server_action
    authorization=AUTH_HEADER_VALUE
    client_kind=admin|browser
    session_present=true|false
    db_role=anon|authenticated|service_role
    grants_checked=true
    policy_checked_last=true

    이렇게 보면 policy를 손보기 전에 이미 원인이 드러나는 경우가 많다. 특히 service_role과 사용자 세션이 한 코드 경로에서 교차하면 RLS가 켜졌는지 여부보다 요청마다 어떤 Authorization 헤더가 만들어졌는지가 더 결정적이다.

    4. 공식 문서와 예시 화면으로 확인하기

    첫 화면은 Supabase RLS 문서의 bypass 구간이다. 여기서는 service key가 RLS를 우회할 수 있고, 브라우저에 노출하면 안 된다는 기본 전제를 먼저 확인해야 한다.

    Supabase 문서는 service key가 RLS를 우회할 수 있으므로 고객 브라우저에 노출하면 안 된다고 설명한다.
    Supabase 문서는 service key가 RLS를 우회할 수 있으므로 고객 브라우저에 노출하면 안 된다고 설명한다.

    즉 정책을 잘 써도 호출 경로가 service_role 기준이면 행 정책은 그대로 지나갈 수 있다. RLS 문제를 정책 SQL만으로 읽으면 계속 같은 자리에서 맴돌게 된다.

    두 번째 자료는 Supabase troubleshooting 문서다. 여기서는 RLS 적용 여부가 apikey 문자열이 아니라 Authorization 헤더 기준으로 결정된다는 점을 아주 직접적으로 적고 있다.

    Troubleshooting 문서는 service role Authorization 헤더가 들어가면 RLS를 우회하고, 사용자 세션이 끼면 다시 사용자 정책을 따른다고 짚는다.
    Troubleshooting 문서는 service role Authorization 헤더가 들어가면 RLS를 우회하고, 사용자 세션이 끼면 다시 사용자 정책을 따른다고 짚는다.

    실무에서 가장 자주 헷갈리는 지점이 바로 여기다. createClient에 service key를 넣어도 이후 요청에 사용자 세션이 섞이면 기대한 권한과 실제 권한이 달라질 수 있다.

    세 번째 화면은 Securing your API 문서다. Supabase는 grants와 RLS를 한 세트로 설명한다. 즉 어떤 role이 객체에 닿을 수 있는지와, 닿은 뒤 어떤 row를 볼 수 있는지는 다른 단계다.

    Securing your API 문서는 grants와 RLS가 서로 다른 제어면이며 둘을 함께 읽어야 한다고 설명한다.
    Securing your API 문서는 grants와 RLS가 서로 다른 제어면이며 둘을 함께 읽어야 한다고 설명한다.

    그래서 데이터가 보이는 문제는 RLS 정책 한 줄보다 grants, role, Authorization 헤더, 호출 위치를 같이 보는 편이 빠르다. 정책이 맞아도 role이 service_role이면 이미 출발점이 다르다.

    클라이언트 초기화 코드를 role별로 나눠 보면 사고가 줄어든다. 브라우저는 publishable 또는 anon 경로만 쓰고, 서버 배치나 관리자 작업만 service_role을 갖게 해야 한다.

    브라우저용 client와 서버용 admin client를 분리한 예시다.
    브라우저용 client와 서버용 admin client를 분리한 예시다.

    이처럼 경로를 분리해 두면 로그에서도 어떤 요청이 user policy를 타는지, 어떤 요청이 관리자 우회 경로인지 빨리 보인다. 이미 service_role key를 브라우저에 넣으면 안 되는 글을 읽었다면, 이번 글은 그다음 단계인 실제 권한 판별 순서다.

    권한을 빨리 좁히려면 요청이 어떤 헤더와 세션으로 나가는지 한 번에 보여 주는 표가 필요하다. 이 표는 같은 테이블 조회라도 호출 경로에 따라 어떤 정책이 적용되는지 비교하는 용도다.

    Authorization 헤더, user session, 호출 위치를 같이 보면 RLS 우회 여부를 더 빨리 판별할 수 있다.
    Authorization 헤더, user session, 호출 위치를 같이 보면 RLS 우회 여부를 더 빨리 판별할 수 있다.

    특히 SSR, edge function, background job이 섞인 코드베이스에서는 이 경로 정리가 없으면 로그 한 줄만 보고 판단하기 어렵다. 서버 권한을 어디까지 줄일지 보는 감각은 workflow별 권한 분리 글과도 닿아 있다.

    마지막 자료는 현장에서 바로 쓸 수 있는 점검 순서다. RLS SQL부터 열지 말고, 현재 role과 Authorization 헤더와 grants를 먼저 보면 같은 버그를 더 짧게 재현할 수 있다.

    RLS가 켜졌는데 데이터가 보일 때 먼저 확인할 순서를 정리한 체크리스트다.
    RLS가 켜졌는데 데이터가 보일 때 먼저 확인할 순서를 정리한 체크리스트다.

    이 순서대로 보면 policy를 계속 수정하다가 원인은 header나 role이었던 상황을 줄일 수 있다. 특히 service_role이 필요한 관리자 배치와 사용자 요청 경로를 한 client로 섞어 쓰는 경우에 효과가 크다.

    5. 주의사항과 리스크

    첫 번째 리스크는 브라우저 코드에 service key를 넣고도 'RLS가 있으니 괜찮다'고 생각하는 것이다. 두 번째 리스크는 관리자 경로와 사용자 경로를 같은 client 객체로 재사용해 세션이 섞이는 것이다. 세 번째 리스크는 grants가 너무 넓은데도 policy SQL만 계속 손봐서 원인을 놓치는 것이다.

    운영 전에 확인할 때는 최소한 브라우저 요청, 로그인된 사용자 요청, 배치 관리자 요청 세 종류를 따로 찍어 보는 편이 좋다. 같은 SELECT라도 Authorization 헤더와 role이 다르면 결과가 달라지는 것이 정상이다. 이 구분을 로그와 코드 구조에 같이 남겨 두어야 추적이 쉬워진다.

    • RLS 문제는 policy SQL보다 Authorization 헤더와 role에서 먼저 갈린다.
    • service_role 경로와 사용자 경로를 코드 구조에서 분리해 둔다.
    • grants와 RLS를 같은 권한 계층으로 오해하지 않는다.

    6. 결론

    Supabase에서 RLS를 켰는데도 데이터가 보이는 문제는 대개 policy 문장 자체보다 호출 경로와 Authorization 헤더에서 시작된다. service_role 사용 위치를 먼저 자르고, 브라우저와 서버 경로를 분리하고, grants와 role을 확인한 뒤 policy를 보면 같은 증상을 훨씬 빨리 좁힐 수 있다.

    • Authorization 헤더가 무엇인지 먼저 확인한다.
    • service_role은 관리자 경로로만 한정한다.
    • grants와 RLS를 함께 읽되 순서를 섞지 않는다.

    같은 service_role 이슈라도 반대 증상인 service_role client인데 RLS error가 날 때 SSR 쿠키와 사용자 세션 덮어쓰기를 먼저 보는 글을 같이 보면, 우회가 너무 넓은 상황과 우회가 사라진 상황을 Authorization 헤더 기준으로 한 번에 정리할 수 있다.

    7. 참고 링크

    1. https://supabase.com/docs/guides/database/postgres/row-level-security
    2. https://supabase.com/docs/guides/troubleshooting/why-is-my-service-role-key-client-getting-rls-errors-or-not-returning-data-7_1K9z
    3. https://supabase.com/docs/guides/api/securing-your-api
Designed by Tistory.