// docs / baas security / supabase service role exposure
JavaScript에 노출된 Supabase 서비스 역할 키: 의미와 찾는 방법
Supabase 서비스 역할 키는 데이터베이스의 마스터 키입니다. 그것을 가진 사람은 누구나 행 수준 보안을 우회하고, 모든 테이블의 모든 열을 읽을 수 있으며, 원하는 무엇이든 쓰거나 삭제할 수 있습니다. 서버 측 코드에만 존재하도록 설계되었으며 — 결코 브라우저에 있어서는 안 됩니다. AI 코딩 도구가 그것을 JavaScript 번들에 출시하면 데이터베이스는 사실상 공개됩니다. 이 기사는 유출된 키를 식별하는 JWT 모양, 유출을 생성하는 세 가지 AI 도구 패턴, 탐지 후 첫 한 시간 내에 할 일, 그리고 사용자보다 먼저 자동으로 스캔하는 방법을 설명합니다.
서비스 역할 키란 무엇인가
Supabase는 모든 프로젝트에 대해 두 개의 별개 키를 발급합니다: anon 키(더 새로운 프로젝트에서는 공개 가능 키라고도 함)와 service_role 키. 둘 다 프로젝트의 JWT 비밀로 서명된 JSON Web Token입니다. 차이는 JWT 페이로드에 구운 role 클레임에 있습니다 — 공개 키의 경우 anon, 마스터 키의 경우 service_role. PostgREST, Supabase Storage, Supabase Auth 모두 service_role 클레임을 보면 모든 것을 우회 모드로 전환합니다.
jwt.io에서 Supabase 키를 디코딩하고 페이로드를 보세요. 서비스 역할 JWT의 모양은 명백합니다:
서비스 역할 JWT의 디코딩된 페이로드(아래에 구문 강조 블록으로 표시).
{
"iss": "supabase",
"ref": "[project-ref]",
"role": "service_role",
"iat": 1700000000,
"exp": 2000000000
}더 새로운 Supabase 프로젝트는 JWT 대신 접두사 sb_secret_을 가진 비밀 스타일 키를 발급합니다. 동작은 동일합니다 — 공개 번들에 sb_secret_을 운반하는 모든 것은 똑같이 파괴적입니다.
AI 코딩 도구가 서비스 역할 키를 유출하는 방법
수천 개의 바이브 코딩 앱에서 동일한 세 가지 패턴을 봤습니다. 각각은 개발자가 AI 도구에 도움을 요청하는 것으로 시작하여 서비스 키가 번들에 인라인되는 것으로 끝납니다.
패턴 1: NEXT_PUBLIC_ 접두사가 있는 단일 .env 파일
개발자가 AI 도구에 "Supabase 설정"을 요청하고 두 키 모두 포함된 단일 .env를 수락합니다. AI 도구는 — 대부분의 환경 변수가 NEXT_PUBLIC_*를 통해 노출되는 코퍼스에서 훈련되어 — 둘 다에 NEXT_PUBLIC_을 접두사로 붙입니다. Next.js는 빌드 시 그 접두사에 일치하는 모든 것을 클라이언트 번들에 인라인합니다. Vercel로 출시하면 서비스 키가 main.[hash].js에 있습니다.
패턴 2: createClient 호출의 잘못된 키
개발자가 AI가 생성한 config.ts 파일에 두 키를 모두 붙여넣고, AI가 실수로 브라우저 측 createClient() 호출에 process.env.SUPABASE_SERVICE_ROLE_KEY를 채웁니다. 빌드가 변수를 끌어들이고 JWT가 번들에 떨어집니다.
패턴 3: 시드 스크립트에 하드코딩된 서비스 역할 키
개발자가 AI 도구에 데이터베이스를 시드하는 스크립트를 작성하도록 요청합니다. AI는 (환경에서 읽는 대신) 서비스 역할 키를 파일에 직접 하드코딩하고, 파일을 리포지토리에 커밋하며, 공개 GitHub 리포지토리 또는 배포된 앱의 /scripts/seed.js 경로가 이제 키를 제공합니다.
FixVibe 번들 스캔이 유출을 탐지하는 방법
FixVibe의 번들 비밀 검사는 배포된 앱이 참조하는 모든 JavaScript 파일 — 진입 청크, 지연 로드 청크, 웹 워커, 서비스 워커 — 을 다운로드하고 JWT 모양(eyJ[base64-header].eyJ[base64-payload].[signature])에 일치하는 것을 디코딩하는 탐지기를 통해 실행합니다. 디코딩된 페이로드에 "role": "service_role"이 포함되어 있으면 스캔은 이를 파일 경로와 키가 나타나는 정확한 줄과 함께 중대한 발견으로 보고합니다. 동일한 검사는 접두사로 더 새로운 sb_secret_* 패턴도 일치시킵니다.
스캔은 발견된 키로 결코 인증하지 않습니다. 모양을 식별하고 유출을 보고합니다 — 키를 사용하여 악용 가능성을 증명하는 것은 데이터베이스에 대한 무단 접근이 될 것입니다. 증거는 JWT 페이로드 자체에 있습니다.
탐지됨 — 첫 한 시간 내에 할 일
유출된 서비스 역할 키는 런타임 비상 사태입니다. 키가 스크래핑되었다고 가정하세요 — 공격자는 실시간으로 공개 번들을 모니터링합니다. 키를 교체하고 최근 활동을 감사할 때까지 데이터베이스를 손상된 것으로 취급하세요.
- 즉시 키 교체. Supabase 대시보드에서 Project Settings → API → Service role key → Reset으로 이동하세요. 이전 키는 몇 초 안에 무효화됩니다. 키를 사용하는 모든 서비스 측 코드는 교체가 적용되기 전에 업데이트되고 재배포되어야 합니다.
- 최근 데이터베이스 활동 감사. 대시보드에서 Database → Logs를 엽니다. 지난 7일로 필터링하세요. PII가 있는 테이블에 대한 비정상적인
SELECT *쿼리, 큰UPDATE또는DELETE문, 알려진 인프라 밖의 IP에서의 요청을 찾으세요. Supabase는 모든 요청에x-real-ip헤더를 기록합니다. - 스토리지 객체 확인. Storage → Logs를 방문하여 최근 파일 다운로드를 검토하세요. 유출된 서비스 역할 키는 비공개 버킷에도 모든 것을 우회하는 접근을 제공합니다.
- 소스 컨트롤에서 키 제거. 교체 후에도 git 히스토리에 JWT를 남겨두면 공개 리포에서 발견 가능한 상태로 남습니다.
git filter-repo또는 BFG Repo-Cleaner를 사용하여 히스토리에서 정리한 다음 강제 푸시하세요(먼저 협업자에게 경고). - 수정 후 재스캔. 재배포된 앱에 대해 새로운 FixVibe 스캔을 실행하세요. 번들 비밀 발견이 사라져야 합니다. 어떤 청크에도
service_roleJWT나sb_secret_*문자열이 남아 있지 않은지 확인하세요.
처음부터 유출 방지
구조적 수정은 명명 규율과 도구 수준의 가드레일입니다:
- 서비스 키에
NEXT_PUBLIC_*,VITE_*, 또는 다른 번들 인라이닝 접두사를 결코 붙이지 마세요. 명명 규약이 경계입니다 — 모든 프레임워크가 이를 존중합니다. - 개발자 머신의
.env에서 서비스 키를 완전히 제거하세요. 배포 시 비밀 관리자(Doppler, Infisical, Vercel 암호화된 env vars)에서 읽고, 로컬에 결코 커밋하지 마세요. - <strong>Mark every Supabase client construction with explicit context.</strong> Files named <code>supabase/browser.ts</code> use the anon key; files named <code>supabase/server.ts</code> use the service-role key with <code>import 'server-only'</code> at the top. The <code>server-only</code> import causes a build error if a client component tries to consume the module.
- <strong>Add a pre-commit hook that greps for JWT-shaped strings.</strong> <code>git diff --staged | grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+'</code> catches both anon and service tokens before they leave your machine.
- 빌드 출력을 스캔하는 CI 게이트를 추가하세요.
next build후.next/static/chunks/출력에서service_role문자열을 grep하세요. 일치하는 것이 있으면 빌드를 실패시키세요.
# Pre-commit hook: refuse any staged JWT-shaped string.
git diff --staged \
| grep -E 'eyJ[A-Za-z0-9_-]+\.eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+' \
&& echo "JWT detected in staged changes — refusing commit" \
&& exit 1
# CI gate: fail the build if "service_role" shipped to the static bundle.
grep -RE 'service_role|sb_secret_' .next/static/chunks/ \
&& echo "Service-role credential leaked into bundle" \
&& exit 1자주 묻는 질문
공격자가 실제로 유출된 Supabase 서비스 역할 키를 얼마나 빨리 찾나요?
공개 번들 스캐너는 몇 분 이내에 새 배포를 트롤링합니다. 연구자들은 첫 배포로부터 한 시간 이내에 새 Supabase 프로젝트에 대한 작동하는 익스플로잇을 문서화했습니다. 모든 서비스 역할 노출을 60일 윈도우가 아닌 60분 윈도우로 취급하세요.
키 교체로 충분한가요, 아니면 데이터 유출을 가정해야 하나요?
교체는 유출된 키를 무효화하지만 이미 가져간 데이터를 되돌리지 못합니다. 테이블에 PII, 결제 데이터, 또는 규제 데이터가 포함되어 있으면 GDPR(72시간), CCPA, 또는 HIPAA에 따라 통지 의무가 있을 수 있습니다. 로그를 감사하고 감사 결과 의심스러운 접근이 보이면 법률 자문을 구하세요.
서비스 역할 키가 유출되면 RLS가 나를 보호하나요?
아니요. 행 수준 보안은 service_role 클레임에 의해 완전히 우회됩니다. 그것은 설계에 의한 것입니다 — 키는 정확히 관리 작업을 위해 백엔드 코드가 RLS를 건너뛸 수 있도록 존재합니다. 완화책은 공격자가 키를 읽을 수 있는 컨텍스트에 결코 도달하지 못하도록 하는 것입니다.
이것이 새로운 Supabase 공개 가능 / 비밀 키 모델(<code>sb_publishable_</code> / <code>sb_secret_</code>)에 적용되나요?
예 — 동일한 위험 클래스. sb_secret_* 키는 더 새로운 프로젝트의 서비스 역할 JWT를 대체하는 새로운 비밀 키 형식입니다. 번들에 sb_secret_*을 운반하는 모든 것은 유출된 서비스 역할 JWT만큼 파괴적입니다. FixVibe의 번들 비밀 탐지기는 두 모양을 모두 일치시킵니다.
anon / 공개 가능 키는 어떻나요 — 번들에 있어도 안전한가요?
예, 설계에 의해. anon 키는 브라우저에 존재하도록 의도되었으며 모든 Supabase 웹 클라이언트가 사용하는 것입니다. 그 안전성은 모든 공개 테이블에 RLS가 올바르게 구성되었는지에 전적으로 의존합니다. 확인할 사항은 Supabase RLS 스캐너 기사를 참조하세요.
다음 단계
프로덕션 URL에 대해 FixVibe 스캔을 실행하세요 — 번들 비밀 검사는 무료이며 가입이 필요 없고, 1분 이내에 service_role 노출을 보고합니다. RLS 계층이 제 역할을 하는지 확인하기 위해 이를 Supabase RLS 스캐너 기사와 결합하고, 파일 접근을 잠그기 위해 Supabase 스토리지 버킷 보안 체크리스트와 결합하세요. AI 도구가 이 유출 클래스를 그렇게 안정적으로 생성하는 이유에 대한 배경은 AI 코딩 도구가 보안 격차를 남기는 이유를 읽으세요.
