ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Supabase][보안] service_role key를 브라우저에 넣으면 안 되는 이유와 점검 위치
    기타개발지식/풀스택개발 2026. 6. 21. 09:16

    IT 리서치 노트

    [Supabase][보안] service_role key를 브라우저에 넣으면 안 되는 이유와 점검 위치

    Supabase 프로젝트에서 가장 위험한 실수 중 하나는 `service_role` 키를 브라우저 코드나 공개 배포 파일에 넣는 것이다. 이 키는 사용자 권한이 아니라 관리자 급 권한 흐름에 가깝고, RLS 우회를 포함한 강한 권한을 가질 수 있다. 실무에서는 admin 작업을 빨리 끝내려고 프론트에서 직접 호출하다가 키가 번들, 브라우저 devtools, 네트워크 탭에 그대로 남는다. 이 글은 어디서 키가 새는지, 무엇을 서버로 옮겨야 하는지, 이미 노출됐을 때 무엇부터 바꿔야 하는지 정리한다.

    1. 개요

    Supabase 프로젝트에서 가장 위험한 실수 중 하나는 `service_role` 키를 브라우저 코드나 공개 배포 파일에 넣는 것이다. 이 키는 사용자 권한이 아니라 관리자 급 권한 흐름에 가깝고, RLS 우회를 포함한 강한 권한을 가질 수 있다.

    실무에서는 admin 작업을 빨리 끝내려고 프론트에서 직접 호출하다가 키가 번들, 브라우저 devtools, 네트워크 탭에 그대로 남는다. 이 글은 어디서 키가 새는지, 무엇을 서버로 옮겨야 하는지, 이미 노출됐을 때 무엇부터 바꿔야 하는지 정리한다.

    • 이 글은 현재 공식 문서와 공개 도움말을 다시 확인한 뒤 정리했다.
    • 설정 경로, 실패 지점, 검증 결과를 분리해서 읽으면 바로 실행에 옮기기 쉽다.
    • 실제 운영에서는 권한, 비용, 배포 로그를 함께 봐야 같은 실수를 반복하지 않는다.

    2. 어디서 막히는가

    문제가 길게 보이더라도 실제 막힘은 몇 가지 패턴으로 모인다. 아래 증상 중 하나라도 보이면 설정값, 요청값, 실행 결과를 분리해서 기록하는 쪽이 빠르다.

    • 프론트엔드 번들에서 `service_role` 또는 admin client 생성 코드가 보인다.
    • 브라우저 network 탭의 `apikey` 헤더에 관리자 키가 실린다.
    • RLS를 믿고 있었는데 관리자 키로 호출해 정책 우회가 가능해진다.
    • 이미 배포된 사이트와 캐시, 로그에서 키를 완전히 회수하기 어렵다.

    여기서 가장 흔한 실수는 증상만 보고 권한을 넓히거나 비용 플랜을 올리거나, 별도 로그 없이 다시 시도하는 것이다. 그러면 당장은 지나가도 다음 배포나 다음 운영 시간대에 같은 문제가 다시 나온다.

    따라서 먼저 실제 값과 공식 기준이 어떻게 다른지 좁히고, 그 다음에 클릭, 입력, 실행, 저장 순서를 하나씩 재현해야 한다. 이 순서가 있어야 팀원끼리 같은 결과를 볼 수 있다.

    문제정의 단계에서는 오류 문구 한 줄만 보는 대신, 어떤 메뉴에서 확인했고 어떤 필드가 비어 있었는지, 어떤 응답 상태가 먼저 나타났는지까지 적어두는 편이 좋다. 그래야 해결 단계에서 값을 바꾼 뒤에도 같은 기준으로 성공과 실패를 다시 비교할 수 있다.

    특히 비용 문제나 권한 문제는 겉으로는 비슷하게 보여도 원인이 다르다. 청구 화면, 콘솔 설정, 배포 로그, 브라우저 요청, CLI 출력 가운데 무엇이 기준 화면인지 먼저 정하고 그 화면을 중심으로 확인해야 엉뚱한 메뉴를 오래 헤매지 않는다.

    3. 실제로 해결하는 순서

    아래 순서는 메뉴를 열고 값을 확인하고 결과를 다시 보는 실무용 순서다. 한 번에 모두 바꾸지 말고 한 단계씩 적용한 뒤 출력과 화면을 확인하는 편이 안전하다.

    1. 먼저 공개 JS 번들과 환경변수 노출 여부를 grep으로 확인한다.
    2. 관리자 작업은 서버, Edge Function, 안전한 worker로 옮긴다.
    3. 이미 노출된 키는 즉시 교체하고 배포 캐시와 로그를 함께 점검한다.
    4. 브라우저에는 anon key와 사용자 세션 토큰만 남도록 호출 경계를 다시 나눈다.
    안전한 예제 코드
    // server/admin.ts
    import { createClient } from "@supabase/supabase-js";
    
    export const admin = createClient(
      process.env.SUPABASE_URL || "https://example.supabase.co",
      process.env.SUPABASE_SERVICE_ROLE_KEY || "YOUR_SERVICE_ROLE_KEY"
    );
    
    // 브라우저 코드에서는 anon key만 사용한다.

    service_role 키는 서버 전용 환경변수에만 둔다. 실제 프로젝트 URL과 키는 예제에 넣지 않는다.

    예제 코드는 흐름만 남겼다. 실제 토큰, 계정, 내부 서버 주소, 비공개 저장소 이름은 placeholder로 바꾸고 서버 환경변수나 보안 저장소로 분리한다.

    중요한 점은 설정 변경 직후 바로 다음 단계로 넘어가지 않는 것이다. 각 단계마다 어떤 메뉴를 클릭했고, 어떤 값을 입력했고, 어떤 출력이 돌아왔는지 짧게라도 기록해야 한다. 그래야 실패했을 때 마지막으로 바뀐 값이 무엇인지 빠르게 되짚을 수 있다.

    브라우저에서 service_role을 빼냈는데도 여전히 RLS가 예상과 다르게 동작한다면, 실제 Authorization 헤더와 role을 어떤 순서로 봐야 하는지는 RLS는 켰는데 데이터가 보이는 경우 service_role과 Authorization 헤더부터 확인하는 글에서 이어서 정리했다.

    또한 해결 절차는 한 번 성공했다고 끝나지 않는다. 같은 절차를 다른 환경이나 다른 브랜치, 다른 계정에서도 다시 실행해 보고 결과가 같은지 확인해야 한다. 운영 환경과 로컬 환경의 URL, 권한, 캐시, 플랜, 리전 차이가 숨어 있으면 여기서 드러나는 경우가 많다.

    4. Supabase 문서 화면에서 위험 지점 확인하기

    service_role key 문제는 단순히 키 이름이 위험해 보인다는 이야기가 아니다. 아래 화면에서는 API key 권한, secret key 취급, RLS 우회 가능성, Auth admin API의 영향 범위를 같이 본다.

    먼저 API keys 문서에서 키 종류를 확인한다. 브라우저에 넣을 수 있는 키와 서버에서만 보관해야 하는 키를 분리하는 것이 출발점이다.

    API keys 문서에서는 publishable key와 secret key의 역할 차이를 먼저 확인한다.
    API keys 문서에서는 publishable key와 secret key의 역할 차이를 먼저 확인한다.

    보안 고려사항에서는 secret 계열 키를 어디에 둘지 판단한다. 브라우저 번들, public repository, 모바일 앱 리소스에 들어가면 회수와 회전 작업이 필요하다.

    Security considerations 화면은 service_role 또는 secret key를 클라이언트에 넣으면 안 되는 이유를 확인하는 위치다.
    Security considerations 화면은 service_role 또는 secret key를 클라이언트에 넣으면 안 되는 이유를 확인하는 위치다.

    RLS 문서는 key 문제를 데이터 접근 권한과 연결해서 볼 때 필요하다. RLS를 켰는지 여부와 service_role의 우회 범위를 같이 확인해야 한다.

    Row Level Security 화면에서는 테이블 접근을 정책으로 제한하는 기본 구조를 확인한다.
    Row Level Security 화면에서는 테이블 접근을 정책으로 제한하는 기본 구조를 확인한다.

    secret key가 허용하는 접근 범위를 보면 브라우저 노출의 심각도가 분명해진다. 단순 읽기 키인지, 관리자성 접근인지에 따라 대응 순서가 달라진다.

    secret key 접근 범위 설명은 키 유출 뒤 어떤 권한을 즉시 차단해야 하는지 판단할 때 사용한다.
    secret key 접근 범위 설명은 키 유출 뒤 어떤 권한을 즉시 차단해야 하는지 판단할 때 사용한다.

    유출 대응 설명도 같은 문서에서 확인한다. 이미 브라우저나 저장소에 노출된 키가 있다면 키 회전, 접근 로그 점검, 배포 산출물 교체를 같은 순서로 처리해야 한다.

    secret key 또는 service_role key가 노출됐을 때는 회전과 영향 범위 점검을 즉시 진행해야 한다.
    secret key 또는 service_role key가 노출됐을 때는 회전과 영향 범위 점검을 즉시 진행해야 한다.

    이 순서로 확인하면 문서의 기능 설명, 설정 범위, 제한 조건, 실제 운영 판단 기준을 한 번에 대조할 수 있다. 화면에서 먼저 볼 항목을 정한 뒤 코드나 콘솔 설정을 수정해야 같은 문제가 반복되지 않는다.

    5. 주의할 점

    설정을 맞췄더라도 운영에서는 다시 틀어지는 지점이 있다. 아래 항목은 배포 직전과 배포 직후에 꼭 다시 점검할 부분이다.

    • 이미 노출된 service_role 키는 삭제만으로 끝나지 않고 교체가 필요하다.
    • 캐시된 JS 파일과 오래 열린 브라우저 탭이 키를 계속 들고 있을 수 있다.
    • RLS 정책이 잘 짜여 있어도 관리자 키가 우회 경로를 만든다.

    특히 권한, 비용, 캐시, 재시도 정책은 한 번 맞춘 뒤에도 환경이 바뀌면 다시 확인해야 한다. 문서가 바뀌었는지, 콘솔 기본값이 달라졌는지, 팀의 배포 방식이 바뀌었는지도 같이 본다.

    6. 결론

    사용자 세션으로 충분한 작업이면 anon key와 RLS로 끝낸다.

    • 사용자 세션으로 충분한 작업이면 anon key와 RLS로 끝낸다.
    • 사용자 생성, 권한 변경, 비공개 데이터 일괄 처리는 서버로 보낸다.
    • 브라우저에서 admin이 필요해 보이면 설계가 잘못됐는지 먼저 의심한다.

    핵심은 추상적인 '좋은 설정'을 찾는 것이 아니라, 지금 내 서비스에서 어떤 값과 어떤 출력이 정답인지 빠르게 확인하는 것이다. 이 글의 순서대로 문서, 설정, 실행 결과를 묶어 보면 같은 문제를 다시 만났을 때 훨씬 빨리 끝낼 수 있다.

    7. 참고 링크

    1. https://supabase.com/docs/guides/auth/signing-keys
    2. https://supabase.com/docs/reference/javascript/auth-admin-oauth-regenerateclientsecret
    3. https://supabase.com/docs/reference/javascript/auth-admin-updateuserbyid
Designed by Tistory.