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