본문 바로가기

DB

Supabase 보안 대변화 완전 가이드 — 4월 28일부터 테이블 자동 노출 비활성화

반응형

Lovable, Claude Code, Cursor로 만든 테이블이 5월 30일부터 Data API에서 자동으로 사라집니다. 미리 대응하지 않으면 프로덕션이 조용히 터집니다.

[핵심 요약]
→ 변경: public 스키마 테이블의 Data API/GraphQL 자동 노출 비활성화
→ 시작: 2026년 4월 28일 (옵트인 가능)
→ 신규 프로젝트 기본값: 2026년 5월 30일
→ 기존 프로젝트 적용: 2026년 10월 30일
→ 영향: supabase-js, REST API, GraphQL API 전부
→ 증상: PostgREST 403 에러 → "no permissions exist for anon/authenticated"
→ 해결: 명시적 GRANT 추가 (테이블 생성 시 필수)
→ RLS는 그대로: Grant와 RLS는 별개 레이어

뭐가 바뀌는 건가

[기존 동작 (2026년 4월 27일 이전)]

CREATE TABLE posts (id uuid, content text);
-- → anon, authenticated 역할이 자동으로 SELECT, INSERT, UPDATE, DELETE 가능
-- → supabase-js로 바로 조회 가능
-- → 보안: RLS로만 제어

[새로운 동작 (4월 28일~)]

CREATE TABLE posts (id uuid, content text);
-- → 어떤 역할도 접근 불가 (Grant 없음)
-- → supabase-js 조회 시 403 에러
-- → 해결: 명시적 GRANT 추가 필요
[Grant vs RLS — 헷갈리기 쉬운 두 레이어]

GRANT (접근 가능 여부):
→ "이 역할이 이 테이블에 접근할 수 있나?"
→ 없으면: Postgres가 쿼리 자체를 거부 (RLS 실행 전)
→ 에러: "permission denied for table posts"

RLS (접근 가능한 행):
→ "이 역할이 어떤 행을 볼 수 있나?"
→ GRANT 통과 후에 실행
→ 에러: 데이터 없이 빈 결과 반환

실행 순서:
사용자 요청 → GRANT 체크 → (통과 시) RLS 체크 → 결과 반환
                  ↓ 실패                ↓ 실패
              즉시 거부            빈 결과 또는 거부

실전 1 — 영향받는 경우 확인

-- 내 프로젝트가 영향받는지 확인
-- Supabase 대시보드 → SQL Editor에서 실행

SELECT
  schemaname,
  tablename,
  -- anon 역할의 SELECT 권한 확인
  has_table_privilege('anon', schemaname||'.'||tablename, 'SELECT') AS anon_select,
  has_table_privilege('anon', schemaname||'.'||tablename, 'INSERT') AS anon_insert,
  has_table_privilege('authenticated', schemaname||'.'||tablename, 'SELECT') AS auth_select
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;
-- 더 상세한 확인: 권한 없는 테이블 목록
SELECT
  t.tablename,
  CASE
    WHEN has_table_privilege('anon', 'public.'||t.tablename, 'SELECT')
    THEN '✅ anon SELECT 있음'
    ELSE '❌ anon SELECT 없음 → 10월 이후 접근 불가'
  END AS anon_status,
  CASE
    WHEN has_table_privilege('authenticated', 'public.'||t.tablename, 'SELECT')
    THEN '✅ authenticated SELECT 있음'
    ELSE '❌ authenticated SELECT 없음'
  END AS auth_status
FROM pg_tables t
WHERE t.schemaname = 'public'
ORDER BY t.tablename;

 

[영향받는 케이스 체크리스트]

✅ 영향받음 (지금 확인 필요):
→ AI 코딩 툴(Lovable, Bolt, Claude Code, Cursor)로 테이블 생성
→ SQL Editor나 마이그레이션 파일로 테이블 생성
→ 외부 Postgres 클라이언트(DBeaver, pgAdmin)로 테이블 생성
→ Supabase CLI supabase migration apply로 테이블 생성

✅ 영향 적음 (이미 Grant 있음):
→ Supabase 대시보드 Table Editor로 테이블 생성
   (대시보드는 자동으로 Grant 추가했음)
→ 명시적으로 GRANT 구문을 마이그레이션에 포함한 경우

실전 2 — 새 테이블 생성 패턴 (필수 3단계)

-- ❌ 기존 패턴 (5월 30일 이후 접근 불가)
CREATE TABLE posts (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id uuid REFERENCES auth.users(id),
  content text NOT NULL,
  created_at timestamptz DEFAULT now()
);

ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "users can see own posts"
ON posts FOR SELECT
USING (auth.uid() = user_id);

-- 위 코드로 만든 테이블: RLS는 있지만 Grant 없음
-- → supabase-js로 조회 시 403 에러


-- ✅ 새로운 패턴 (Grant + RLS 세트)
-- Step 1: 테이블 생성
CREATE TABLE posts (
  id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id uuid REFERENCES auth.users(id),
  content text NOT NULL,
  created_at timestamptz DEFAULT now()
);

-- Step 2: Grant 추가 (이게 새로 필요해진 것)
GRANT SELECT, INSERT, UPDATE, DELETE
ON posts
TO authenticated;

-- anon 역할에도 필요한 경우 (로그인 없는 읽기 허용 시):
-- GRANT SELECT ON posts TO anon;

-- Step 3: RLS 활성화 + 정책 설정
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "users can view own posts"
ON posts FOR SELECT
USING (auth.uid() = user_id);

CREATE POLICY "users can insert own posts"
ON posts FOR INSERT
WITH CHECK (auth.uid() = user_id);

CREATE POLICY "users can update own posts"
ON posts FOR UPDATE
USING (auth.uid() = user_id);

CREATE POLICY "users can delete own posts"
ON posts FOR DELETE
USING (auth.uid() = user_id);
[Grant 설정 가이드]

인증 사용자만 접근:
GRANT SELECT, INSERT, UPDATE, DELETE ON 테이블명 TO authenticated;

비로그인 사용자도 읽기 허용:
GRANT SELECT ON 테이블명 TO anon;
GRANT SELECT, INSERT, UPDATE, DELETE ON 테이블명 TO authenticated;

쓰기 없이 읽기만:
GRANT SELECT ON 테이블명 TO authenticated;

관리자만:
-- Grant 없음 (service_role은 RLS/Grant 우회)
-- service_role 키로만 접근 (서버사이드 전용)

실전 3 — 기존 테이블 대응 (마이그레이션)

-- 현재 Grant 없는 테이블에 일괄 추가
-- 조심: 테이블별로 필요한 권한 다름, 일괄 적용 전 검토 필수

-- 예시: 특정 테이블들에 Grant 추가
GRANT SELECT, INSERT, UPDATE, DELETE ON posts TO authenticated;
GRANT SELECT, INSERT, UPDATE, DELETE ON comments TO authenticated;
GRANT SELECT ON public_profiles TO anon;
GRANT SELECT ON public_profiles TO authenticated;

-- 마이그레이션 파일로 관리 (Supabase CLI)
-- supabase/migrations/20260506_add_grants.sql
# Supabase CLI로 마이그레이션 생성
supabase migration new add_table_grants

# 생성된 파일에 Grant 추가 후
supabase db push

# 또는 로컬 테스트 후
supabase migration up
-- Security Advisor 확인
-- Supabase 대시보드 → Advisors → Security
-- → "Tables without grants" 항목 확인
-- → 10월 이전 전부 해결해야 함

-- 또는 SQL로 확인
SELECT * FROM pg_tables
WHERE schemaname = 'public'
AND tablename NOT IN (
  SELECT DISTINCT table_name
  FROM information_schema.role_table_grants
  WHERE grantee IN ('anon', 'authenticated')
);

실전 4 — AI 코딩 툴 사용자 대응

[AI 툴로 테이블 만들 때 주의사항]

Lovable, Bolt, v0 등 AI 앱 빌더:
→ 이 툴들이 생성한 마이그레이션에 Grant 없을 수 있음
→ 생성된 SQL 파일 확인: GRANT 구문 있는지 체크
→ 없으면 수동으로 추가

Claude Code, Cursor, Codex:
→ CLAUDE.md / .cursorrules에 Grant 지시 추가
→ 또는 Supabase Agent Skill 설치 (자동 처리)
<!-- CLAUDE.md에 추가할 내용 -->

## Supabase 테이블 생성 규칙

테이블 생성 시 반드시 3단계로:

1. CREATE TABLE
2. GRANT (필수! 없으면 Data API 접근 불가)
   - authenticated 사용자 접근: GRANT SELECT, INSERT, UPDATE, DELETE ON 테이블명 TO authenticated;
   - 비로그인 읽기: GRANT SELECT ON 테이블명 TO anon;
3. ALTER TABLE ... ENABLE ROW LEVEL SECURITY + 정책

Grant 없이 테이블만 만들면 supabase-js에서 403 에러 발생.
<!-- .cursorrules에 추가할 내용 -->

## Supabase Rules

When creating Supabase tables, always include in this exact order:
1. CREATE TABLE with proper types and constraints
2. GRANT statement (REQUIRED - without this, Data API returns 403)
   - For authenticated users: GRANT SELECT, INSERT, UPDATE, DELETE ON tablename TO authenticated;
   - For public read: GRANT SELECT ON tablename TO anon;
3. ALTER TABLE tablename ENABLE ROW LEVEL SECURITY;
4. CREATE POLICY statements

Never skip the GRANT step - tables without grants will break after May 30, 2026.

실전 5 — 에러 발생 시 빠른 수정

[증상별 원인 파악]

에러: "permission denied for table posts"
원인: GRANT 없음
해결: GRANT SELECT ON posts TO authenticated;

에러: "no permissions exist for anon or authenticated roles"
원인: GRANT 없음 (대시보드 표시)
해결: 위와 동일

에러: PostgREST 403 {"code":"42501","message":"permission denied"}
원인: GRANT 없음
해결: 적절한 GRANT 추가

supabase-js 에러: "relation public.posts does not exist" (GraphQL)
원인: GRANT 없음 또는 pg_graphql 설정 문제
해결: GRANT 추가 후 재시도
// 에러 핸들링 코드 (현재 앱에 추가 권장)
const { data, error } = await supabase
  .from('posts')
  .select('*')

if (error) {
  if (error.code === '42501') {
    // permission denied → GRANT 없음
    console.error('DB 권한 없음: Supabase 대시보드에서 Grant 확인 필요')
  } else if (error.message.includes('no permissions exist')) {
    // 새로운 에러 메시지 형태
    console.error('테이블 Grant 없음:', error.hint)
    // error.hint: "Grant the required privileges to the current role with: GRANT SELECT ON public.posts TO anon;"
  }
}

타임라인 완전 정리

날짜 내용

2026년 4월 28일 옵트인 가능 — 프로젝트 생성 시 체크박스로 선택 가능
2026년 5월 30일 신규 프로젝트 기본값 변경 — 모든 새 프로젝트에 적용
2026년 10월 30일 기존 프로젝트 전체 적용 — 기존 프로젝트도 강제 적용
[추가 변경사항 (연관 보안 강화)]

5월 18일: pg_graphql 기본 비활성화
→ GraphQL API 사용 프로젝트: 명시적 활성화 필요

5월 26일: OAuth 토큰 엔드포인트 201 → 200 응답 변경
→ /v1/oauth/token 응답 코드 체크하는 코드 수정 필요

10월 30일: 기존 프로젝트 Grant 강제 적용
→ 지금부터 Security Advisor 확인하며 준비

왜 이 변화가 좋은가

[보안 관점에서 올바른 방향]

기존 문제:
→ 테이블 만들면 자동 노출 → 개발자가 실수로 민감 데이터 노출 가능
→ RLS 없이 만든 테이블 → 전체 데이터 공개
→ AI 코딩 툴이 Grant/RLS 고려 없이 테이블 생성 → 보안 구멍

새로운 방식:
→ 명시적 선택이 없으면 노출 안 됨 (Deny by default)
→ 개발자가 의도적으로 Grant를 추가해야 접근 가능
→ 5개국 정부 보안 가이드의 "최소 권한 원칙"과 일치

Supabase가 말하는 이유:
"처음에는 사람이 스키마 변경 검토 후 RLS 활성화를 도왔다.
지금은 수십만 프로젝트. AI 코딩 툴이 Grant/RLS 없이 테이블을 만든다.
이 기본값 변경은 실수로부터 개발자를 보호하기 위한 것이다."

마무리

✅ 지금 당장 해야 할 것

기존 프로젝트 (10월 30일 전에):
□ Supabase 대시보드 → Advisors → Security 확인
□ "Tables without grants" 항목 전부 해결
□ 각 테이블에 적절한 GRANT 추가
□ supabase-js 에러 핸들링 코드 확인

새 프로젝트 (5월 30일 이후):
□ 테이블 생성 시 항상 Grant + RLS 세트로
□ AI 코딩 툴 사용 시 CLAUDE.md / .cursorrules 업데이트
□ Supabase Agent Skill 설치 (자동 처리)

마이그레이션 파일 있다면:
□ 기존 마이그레이션 GRANT 구문 포함 여부 확인
□ 없으면 새 마이그레이션 파일로 일괄 추가

❌ 이것만은 피할 것
→ "10월 30일까지 시간 있으니 나중에" → 미루다 프로덕션 터짐
→ GRANT SELECT, INSERT, UPDATE, DELETE 무조건 anon에 → 보안 구멍
→ Grant만 추가하고 RLS 빠뜨리기 → 모든 행 접근 가능

관련 글:

 

 

 

https://cell-devlog.tistory.com/40

 

AI 네이티브 앱 아키텍처 설계 — 처음부터 AI를 고려한 풀스택 구조 (with Supabase)

"AI 기능 추가해야 해"라는 말을 들으면 많은 개발자가 기존 앱에 LLM API 호출을 끼워 넣어요.# 이렇게 하면 안 돼요@app.post("/chat")def chat(message: str): response = openai.chat.completions.create(...) # 그냥 때려

cell-devlog.tistory.com

 

https://cell-devlog.tistory.com/166

 

Supabase + Claude Code MCP 완전 가이드 — AI 에이전트가 Postgres를 올바르게 다루게 만드는 법

AI 에이전트가 Supabase를 알고는 있습니다. 그런데 제대로 쓰는 건 다른 얘기입니다. RLS 없이 테이블 만들고, 없는 CLI 명령 날조하고, security_invoker 빠뜨린 뷰를 만듭니다. MCP + Agent Skills로 이걸 고

cell-devlog.tistory.com

https://cell-devlog.tistory.com/146

 

Lovable 완전 가이드 — 코드 한 줄 없이 풀스택 SaaS MVP를 30분에 만드는 법

"개발자 없이 앱 만들기"가 드디어 현실이 됐습니다. Lovable은 출시 2개월 만에 ARR $20M을 찍었습니다. 유럽 스타트업 역사상 최단 기록입니다.[핵심 요약]→ 정체: 자연어 프롬프트 → 풀스택 앱 자

cell-devlog.tistory.com

 

반응형