제공 서비스
웹 보안 점검 · 무료 단건 외부 공격표면 관리 · 지속 자동 AI 취약점 진단 · 신규 모의해킹 · 침투 테스트
요금제 스토어 블로그 파트너 마이페이지 무료 보안 점검
CRM보안 2026.04.01 · 조회 209

Supabase CRM 보안 설정 가이드: RLS부터 API 키 관리까지

Supabase로 만든 CRM에서 가장 많이 하는 실수 — RLS 미설정, anon key 남용, 서비스키 노출. 올바른 설정법을 정리했다.

S
보안 전문 기업 SENTRIX 레드팀
해킹대회 수상 · 보안 실무 10년 · 실전 진단 경험을 바탕으로 작성합니다.
이 글의 취약점, 내 사이트엔 없을까요? URL 하나로 30초 무료 점검.

Supabase CRM, 왜 뚫리는가

Supabase는 바이브코딩 CRM의 백엔드로 가장 많이 쓰인다. 인증, 데이터베이스, 실시간 동기화, 파일 저장까지 한 번에 해결해주기 때문이다. 문제는 기본 설정 그대로 쓰면 모든 데이터가 공개된다는 것이다.

핵심은 이 구조를 이해하는 데 있다. Supabase는 PostgreSQL 위에 PostgREST라는 자동 REST API 계층을 얹어놓은 형태다. 테이블을 하나 만들면 그 즉시 https://프로젝트.supabase.co/rest/v1/테이블명 경로로 조회·삽입·수정·삭제 API가 자동 생성된다. 개발자가 백엔드 코드를 한 줄도 쓰지 않아도 API가 열린다. 편리한 만큼, 접근 제어를 켜지 않으면 그 API는 전 세계 누구에게나 열린 창구가 된다.

2025년 Lovable 사건에서 170개 이상의 Supabase 앱이 RLS 미설정으로 데이터가 노출됐다. 대부분 고객 관리, 주문 관리 등 CRM 성격의 앱이었다. 이름, 전화번호, 이메일, 주소 같은 개인정보가 인증 없이 브라우저 주소창만으로 조회되는 상태였다. 공격자 입장에서는 SQL Injection 같은 기술도 필요 없다. 공개된 anon key를 그대로 복사해 API를 호출하기만 하면 된다.

공격자는 실제로 이렇게 악용한다

Supabase 앱의 anon key와 프로젝트 URL은 브라우저에 배포되는 JavaScript 안에 반드시 포함된다(클라이언트가 DB에 접속하려면 필요하기 때문). 공격자는 배포된 사이트의 개발자 도구 → Network 탭이나 소스 코드에서 이 두 값을 몇 초 만에 찾는다. 그다음 자동화 봇으로 /rest/v1/ 하위의 테이블 이름을 사전 공격(customers, users, orders, profiles 같은 뻔한 이름)으로 훑는다. RLS가 꺼진 테이블이 하나라도 걸리면 전체 행을 페이지네이션으로 긁어간다. 실제로 신규 Supabase 프로젝트는 배포 후 수십 분 내에 이런 스캔 트래픽을 받는다.

필수 보안 설정 4단계

1단계: RLS 활성화

모든 테이블에 RLS(Row Level Security, 행 수준 보안)를 켜야 한다. Supabase 대시보드에서 테이블 → Authentication → Enable RLS. RLS는 PostgreSQL의 기능으로, "어떤 사용자가 어떤 에 접근할 수 있는지"를 SQL 규칙으로 정의한다.

여기서 반드시 기억할 함정 하나. RLS를 켜기만 하고 정책(Policy)을 하나도 만들지 않으면 그 테이블은 아무도 못 읽는 상태(전면 차단)가 된다. 반대로 RLS를 끈 상태면 전면 공개다. 즉 안전한 상태는 "RLS ON + 정확한 정책"의 조합뿐이다.

-- customers 테이블 RLS 활성화
ALTER TABLE customers ENABLE ROW LEVEL SECURITY;

-- 인증된 사용자만 자기 데이터 조회
CREATE POLICY "Users can view own customers"
  ON customers FOR SELECT
  USING (auth.uid() = user_id);

-- 삽입 시에도 본인 user_id만 허용 (타인 명의 데이터 주입 차단)
CREATE POLICY "Users can insert own customers"
  ON customers FOR INSERT
  WITH CHECK (auth.uid() = user_id);

흔한 실수는 FOR SELECT 정책만 만들고 INSERT/UPDATE/DELETE 정책을 빠뜨리는 것이다. SELECT는 막았는데 DELETE는 안 막아서, 공격자가 데이터를 훔치지는 못해도 전부 지워버리는 사고가 생긴다. 명령(SELECT/INSERT/UPDATE/DELETE)마다 정책이 필요하다. 그리고 조회 조건에는 USING, 쓰기 조건에는 WITH CHECK를 쓴다는 점도 구분해야 한다.

또 하나 자주 나오는 취약 패턴은 이렇게 열어두는 것이다.

-- 위험: 로그인만 하면 남의 데이터까지 전부 조회됨
CREATE POLICY "authenticated can read"
  ON customers FOR SELECT
  USING (auth.role() = 'authenticated');

이 정책은 "로그인한 사람은 누구나 전체 고객 테이블을 읽을 수 있다"는 뜻이다. 멀티 테넌트 CRM에서 이러면 A사장이 B사장의 고객 명단을 그대로 볼 수 있다. 반드시 auth.uid() = user_id처럼 소유자 기준으로 행을 좁혀야 한다.

2단계: anon key vs service_role key 분리

anon key: 클라이언트(브라우저)에서 사용한다. 공개되는 것이 정상이며, RLS 정책의 적용을 받는다. 즉 anon key로 접근해도 RLS가 제대로 걸려 있으면 허가된 행만 나온다. anon key 노출 자체는 사고가 아니다, RLS가 없을 때만 사고가 된다.

service_role key: 서버에서만 사용한다. RLS를 완전히 우회한다. 이 키를 가진 요청은 모든 정책을 무시하고 모든 행에 접근한다. 절대 클라이언트에 노출하면 안 된다. Vercel/Netlify 환경변수나 서버 사이드 코드(Edge Function, API Route)에만 넣어야 한다.

AI 코딩 도구가 코드를 생성할 때 service_role key를 프론트엔드 환경변수에 넣는 경우가 있다. Next.js에서 특히 위험한데, NEXT_PUBLIC_ 접두사가 붙은 환경변수는 브라우저 번들에 그대로 박힌다.

# 위험: service_role 키가 브라우저로 노출됨 (RLS 전면 무력화)
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...

# 안전: 공개용 anon 키만 클라이언트에
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGci...
# service_role 키는 접두사 없이 서버 전용으로
SUPABASE_SERVICE_ROLE_KEY=eyJhbGci...

service_role key가 유출되면 RLS를 아무리 잘 짜놨어도 전부 무용지물이 된다. 공격자는 그 키로 모든 테이블을 읽고, 수정하고, 삭제할 수 있다. 두 키는 JWT라 디코딩하면 "role": "anon" 또는 "role": "service_role"이 그대로 보인다, 어떤 키가 어디 있는지 헷갈리면 jwt.io 같은 디코더로 payload를 열어 role을 확인하면 된다.

3단계: API 접근 제어

Supabase REST API는 기본적으로 public 스키마의 모든 테이블에 CRUD가 가능하다. CRM에는 고객 테이블 외에도 내부 로그, 설정, 결제 메타데이터 등 API로 노출될 필요가 없는 테이블이 많다. 필요 없는 테이블은 API 노출을 끄거나, RLS로 완전히 차단해야 한다.

실무에서 자주 쓰는 접근은 민감한 테이블을 public 스키마 밖으로 빼는 것이다. PostgREST는 기본적으로 노출 스키마(보통 public)에 있는 테이블만 API로 연다. 서버에서만 다뤄야 할 데이터는 별도 스키마(예: private)에 두면 REST API 표면에서 아예 사라진다.

-- 내부 전용 테이블을 API에서 제외
CREATE SCHEMA IF NOT EXISTS private;
ALTER TABLE internal_audit_logs SET SCHEMA private;
-- private 스키마는 PostgREST 노출 목록에 없으므로 REST로 접근 불가

또한 뷰(View)를 통한 우회에도 주의해야 한다. RLS는 테이블 단위로 걸리지만, 그 테이블을 참조하는 뷰를 만들어 노출하면 뷰가 원본 테이블의 RLS를 SECURITY DEFINER 방식으로 우회할 수 있다. 뷰를 API로 노출한다면 반드시 뷰 소유자 권한과 정책을 함께 점검한다.

4단계: 실시간 구독 보안

Supabase Realtime을 쓰는 CRM에서, 구독 필터 없이 전체 테이블 변경사항을 수신하면 다른 사용자의 데이터까지 받을 수 있다. Realtime은 데이터베이스의 변경(INSERT/UPDATE/DELETE)을 WebSocket으로 클라이언트에 밀어주는 기능인데, 여기에도 별도의 접근 제어가 필요하다.

중요한 점: Realtime은 기본적으로 RLS를 우선 존중하지 않았던 시기가 있었고, 지금도 채널 인가(authorization)를 명시적으로 켜야 안전하다. Postgres Changes를 브로드캐스트할 때 RLS 정책이 적용되도록 설정하고, 클라이언트 구독에는 반드시 필터를 건다.

// 위험: 전체 테이블 변경을 무필터로 수신 → 타인 데이터 유입
supabase.channel('all').on('postgres_changes',
  { event: '*', schema: 'public', table: 'customers' },
  handler).subscribe()

// 안전: 본인 소유 행으로 필터 + RLS 인가 활성화
supabase.channel('mine').on('postgres_changes',
  { event: '*', schema: 'public', table: 'customers',
    filter: `user_id=eq.${userId}` },
  handler).subscribe()

내 손으로 직접 확인하는 법

지금 운영 중인 Supabase CRM이 뚫려 있는지 5분이면 스스로 확인할 수 있다. 별도 도구 없이 브라우저와 개발자 도구만 있으면 된다.

  • anon key와 URL 찾기: 배포된 사이트에서 F12 → Network 탭 → 새로고침 → supabase.co로 가는 요청을 보면 요청 헤더의 apikey 값과 프로젝트 URL이 그대로 보인다.
  • 무인증 조회 테스트: 아래 명령을 실행해본다. 데이터가 배열로 쏟아지면 그 테이블은 RLS가 없거나 잘못된 상태다. [](빈 배열)이나 권한 오류가 나오면 정상이다.
# anon key로 로그인 없이 customers 테이블을 긁어본다
curl "https://프로젝트.supabase.co/rest/v1/customers?select=*" \
  -H "apikey: 여기에_anon_key" \
  -H "Authorization: Bearer 여기에_anon_key"

# 결과가 고객 데이터 배열이면 → 즉시 RLS 조치 필요
# 결과가 [] 또는 permission denied → 정상

대시보드에서도 확인할 수 있다. Table Editor에서 각 테이블 옆에 RLS가 꺼져 있으면 "Unrestricted"라는 붉은 경고 배지가 붙는다. Advisors → Security 탭에는 RLS 미적용 테이블, 노출된 뷰, 함수 권한 문제가 자동으로 목록화된다. 이 두 곳을 먼저 보는 것이 가장 빠르다.

그래서 뭘 먼저 해야 하나 (우선순위)

  1. 지금 당장: 개인정보가 든 테이블(customers, orders, profiles 등)에 위 curl 테스트를 돌려 노출 여부 확인. 노출됐다면 즉시 RLS ON + 소유자 기준 정책 추가.
  2. 오늘 안에: 프론트엔드 환경변수에 service_role key가 들어가 있지 않은지 확인. 있었다면 키를 즉시 재발급(rotate)한다, 이미 브라우저에 배포된 키는 회수할 수 없으므로 교체가 유일한 해결책이다.
  3. 이번 주: Advisors → Security 경고를 전부 해소하고, Realtime 채널 필터·인가를 점검한다.
  4. 지속: 새 테이블을 만들 때마다 RLS ON을 기본 습관으로. 기능을 추가할 때마다 새 API 표면이 열리므로 정기 점검이 필요하다.

내 Supabase CRM 점검하기

위 절차를 매번 수동으로 돌리기 번거롭다면, CodeScan은 Supabase 설정 오류를 자동 탐지한다. RLS 미설정, service_role key 노출, 무인증 API 접근 가능 여부, 개인정보 노출 상태를 URL 하나로 30초 만에 확인할 수 있다. 보안 전문 기업 SENTRIX의 레드팀이 실제 침투 관점에서 설계한 점검 항목이다.

→ 무료 CRM 보안 진단 받기

Supabase CRM RLS API키 바이브코딩

내 사이트도 점검해보세요

URL 하나면 21개 항목을 30초에 무료 점검합니다. 회원가입·카드 없이.

추천 상품
웹서비스 런칭 전 보안 세팅
런칭 전에 반드시 해야 하는 보안 설정을 원격으로 직접 해드립니다. HTTPS, 환경변수 분리, 보안 헤더…
220,000원 150,000원

AI 코딩 보안 체크리스트 받기

AI 코딩 보안 체크리스트(PDF)를 무료로 받아보세요.