Supabase 보안 체크리스트
Supabase는 편리한 BaaS지만, 클라이언트에서 DB에 직접 접근하는 구조이기 때문에 설정을 잘못하면 테이블 전체가 공개되는 등 심각한 사고로 이어질 수 있습니다. 운영 환경에 올리기 전 아래 항목을 반드시 점검하세요.
1. 모든 테이블에 RLS(Row Level Security) 활성화
Supabase의 가장 중요한 보안 원칙입니다. RLS를 켜지 않은 테이블은 anon 키만 있으면 누구나 전체 데이터를 읽고 쓸 수 있습니다.
-- 모든 테이블에 RLS 활성화
ALTER TABLE public.posts ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- FORCE를 걸면 테이블 소유자 쿼리에도 RLS가 적용되어 더 안전합니다
ALTER TABLE public.posts FORCE ROW LEVEL SECURITY;
점검 쿼리
-- RLS가 꺼진 public 테이블 찾기
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public' AND rowsecurity = false;
Dashboard 확인
Database → Tables → (테이블 선택) → RLS 상태 가 초록색(Enabled)인지 확인. Supabase Studio의 Security Advisor 탭에서 일괄 점검 가능합니다.
2. RLS 정책을 실제로 작성하기
RLS만 켜고 정책(Policy)을 만들지 않으면 모든 요청이 차단됩니다. 반대로 USING (true) 같은 무의미한 정책을 걸면 켠 의미가 없습니다.
-- 예: posts는 본인이 작성한 것만 수정 가능
CREATE POLICY "users can update own posts"
ON public.posts FOR UPDATE
USING ((select auth.uid()) = author_id)
WITH CHECK ((select auth.uid()) = author_id);
-- 읽기는 공개
CREATE POLICY "posts are public"
ON public.posts FOR SELECT
USING (true);
:::tip 성능 팁
auth.uid() 를 그대로 쓰면 매 행마다 함수가 재평가됩니다. (select auth.uid()) 로 감싸면 쿼리당 1회만 평가되어 대량 쿼리에서 훨씬 빠릅니다.
:::
3. 서비스 롤 키(service_role)는 절대 클라이언트로 내보내지 않기
| 키 종류 | 용도 | 노출 가능? |
|---|---|---|
anon (public) | 브라우저/앱 클라이언트 | 가능 (RLS 전제) |
service_role | 서버·관리자 작업 | 절대 불가 (RLS 우회) |
service_role 키는 RLS를 완전히 무시하므로 유출되면 전 테이블이 읽기/쓰기/삭제 가능해집니다.
.env.local, 서버 사이드 환경변수에만 두기- Next.js라면
SUPABASE_SERVICE_ROLE_KEY는NEXT_PUBLIC_접두어를 절대 붙이지 않기 - Git 커밋 전
git secret scan또는 GitHub Push Protection 활성화 - 이미 커밋되었다면 즉시 Dashboard에서 키 로테이션
# GitHub에 비밀키가 푸시된 과거 이력 확인
gh secret scanning alerts list
4. SQL Injection 방지: rpc() / 파라미터 바인딩 사용
클라이언트에서 문자열 조립으로 SQL을 만들지 말고, 반드시 SDK의 파라미터화된 API나 RPC 함수로 호출하세요.
// 나쁨: 문자열 조립 → SQL Injection 위험
await supabase.rpc('exec_raw', { sql: `SELECT * FROM posts WHERE id=${userInput}` });
// 좋음: 파라미터 전달
await supabase.from('posts').select('*').eq('id', userInput);
// 좋음: SECURITY DEFINER 함수 + 명시적 파라미터
await supabase.rpc('get_post_by_id', { p_id: userInput });
5. Storage 버킷의 공개 여부 명확히 구분
- Public 버킷: 누구나 URL만 알면 접근 가능. 프로필 사진 등 공개 자산에만 사용.
- Private 버킷: 기본값. 접근 시 RLS 정책과 서명 URL(
createSignedUrl) 필요.
-- Storage도 RLS 정책 필요
CREATE POLICY "users can upload to own folder"
ON storage.objects FOR INSERT
WITH CHECK (
bucket_id = 'avatars'
AND (storage.foldername(name))[1] = (select auth.uid())::text
);
6. 인증(Auth) 설정 강화
Dashboard → Authentication → Policies / Providers 에서 확인:
- 이메일 확인(Email Confirmation) 켜기 — 미확인 계정으로 리소스 접근 방지
- 강력한 비밀번호 정책 설정 (최소 8자, 복잡도)
- Rate Limiting 설정 — 로그인·회원가입 브루트포스 차단
- Redirect URL Allowlist 에 우리 도메인만 등록 — 피싱 리다이렉트 방지
- JWT 만료 시간 을 짧게(예: 1시간) + Refresh Token 로테이션 활성화
- Leaked Password Protection 활성화 (HaveIBeenPwned 연동)
- 민감한 서비스라면 MFA(TOTP) 강제
7. Realtime 채널 인증
Realtime 구독 채널도 RLS 영향권입니다. replication 테이블 레벨에서 구독을 허용할 컬럼을 제한하세요.
-- 특정 테이블만 realtime publish에 포함
ALTER PUBLICATION supabase_realtime ADD TABLE public.messages;
-- 민감 컬럼은 제외
ALTER PUBLICATION supabase_realtime SET (publish = 'insert, update, delete');
8. Database 접근 제한
- Network Restrictions (Pro 이상): Dashboard → Settings → Database → Network Restrictions 에서 허용 IP 대역 지정
- Direct Connection 을 쓸 때는 Pooler(6543 포트) 또는 Session(5432) 중 서버 환경에 맞는 것 선택
- Read Replica 를 분석용 쿼리에 분리해 운영 부하 격리
- DB 비밀번호는 16자 이상 + 정기 로테이션
9. Dashboard / Org 자체 보안
- Organization 멤버는 최소 권한 원칙 (Viewer/Developer/Administrator/Owner 구분)
- 2FA(TOTP) 전원 필수
- 퇴사자 Organization 접근 권한 즉시 제거
- Audit Log(Team plan+) 주기적 리뷰
10. Edge Function 보안
// supabase/functions/hello/index.ts
Deno.serve(async (req) => {
// 1. JWT 검증
const authHeader = req.headers.get('Authorization');
if (!authHeader) return new Response('Unauthorized', { status: 401 });
// 2. 입력 검증 (zod 등)
const body = schema.parse(await req.json());
// 3. service_role은 서버 내부에서만, 절대 응답에 포함 금지
const client = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY!);
// ...
});
- CORS 는 허용 도메인만 명시 (
*지양) - 비밀값은 Edge Function Secrets로 관리 (
supabase secrets set) - 외부 API 호출 시 타임아웃·리트라이 제한으로 SSRF·비용 폭증 방지
11. Security Advisor & Performance Advisor 정기 점검
Supabase Dashboard의 Advisors 탭은 RLS 미적용 테이블, SECURITY DEFINER 뷰, 공개된 스키마 함수 등 보안 취약점을 자동 스캔해 줍니다. 배포 전마다 확인하는 루틴을 만드세요.
12. 백업과 복구
- 자동 백업 주기 확인 (Free: 7일 / Pro: 14일 / 추가 PITR 가능)
- 중요한 변경 전 Point-in-Time Recovery(PITR) 가능한 플랜인지 확인
- 백업에서 실제 복원이 되는지 연습(test restore) 해두기
13. 로깅 & 모니터링
- Logs Explorer 에서 비정상 쿼리 패턴, 실패한 로그인 모니터링
- pg_stat_statements 확장으로 느리거나 이상한 쿼리 추적
- 알림은 Slack/Email Webhook으로 연결
체크리스트 요약
| # | 항목 | 상태 |
|---|---|---|
| 1 | 모든 public 테이블에 RLS 활성화 | ☐ |
| 2 | 실제 동작하는 RLS 정책 작성 | ☐ |
| 3 | service_role 키 서버 전용 (커밋 이력 스캔) | ☐ |
| 4 | 모든 쿼리 파라미터 바인딩 사용 | ☐ |
| 5 | Storage 버킷 public/private 구분 + RLS | ☐ |
| 6 | 이메일 확인·MFA·Redirect Allowlist·Leaked PW 보호 | ☐ |
| 7 | Realtime publication 범위 제한 | ☐ |
| 8 | Network Restrictions + DB 패스워드 로테이션 | ☐ |
| 9 | Org 멤버 최소 권한 + 2FA | ☐ |
| 10 | Edge Function JWT/CORS/Secrets 관리 | ☐ |
| 11 | Security Advisor 0건 | ☐ |
| 12 | 백업/PITR 확인 및 복원 테스트 | ☐ |
| 13 | 로그·모니터링 알림 구성 | ☐ |