Lovable 코드베이스 감사: 보안 문제, RLS 갭, 노출된 API 키
Lovable 생성 코드의 보안 문제: API 키 노출, RLS 갭, 그리고 프로덕션 준비 부족
지난 달 한 클라이언트가 우리에게 Lovable로 생성된 앱을 가져왔는데, 이미 프로덕션에서 실제 고객들을 대상으로 운영 중이었습니다. 몇 가지 기능을 추가하고 "코드를 좀 정리"하고 싶다고 했습니다. 초기 코드베이스 감사 과정에서 발견한 것은... 매우 놀라웠습니다. 클라이언트 측 코드에 노출된 API 키. 사용자 데이터를 보유한 테이블의 Row Level Security 정책 부재. 서버 측 검증 없는 직접 Supabase 호출. 그리고 이것은 일회성 문제가 아니었습니다. 우리는 이제 6개의 서로 다른 Lovable 프로젝트를 감사했는데, 패턴이 매우 일관성 있게 나타났습니다.
명확히 하자면: Lovable은 프로토타입 제작용 인상적인 도구입니다. 자연어 프롬프트에서 작동하는 UI를 생성하는 속도는 정말 놀랍습니다. 그러나 "작동하는 프로토타입"과 "프로덕션 준비 완료 애플리케이션" 사이에는 거대한 간격이 있으며, 그 간격은 대부분의 비기술 창업자들이 찾지 못하는 보안 취약점으로 가득 차 있습니다.
이 글은 여러 Lovable 코드베이스 감사에서 발견한 것들에 대한 기술적 분석입니다. Lovable로 뭔가 만들었고 실제 사용자의 돈이나 데이터를 다루고 있다면, 반드시 읽어야 합니다.
목차
- Lovable이 실제로 생성하는 것
- API 키 문제
- Row Level Security: 조용한 킬러
- 인증 및 인가 갭
- 클라이언트 측 비즈니스 로직 노출
- Supabase Edge Functions: 누락된 것
- 발견한 일반적인 취약점 패턴
- 자신의 Lovable 프로젝트를 감사하는 방법
- 재구축 대 수정: 언제 어떤 것을 할 것인가
- FAQ

Lovable이 실제로 생성하는 것
Lovable은 Supabase 백엔드에 연결된 React 애플리케이션(보통 Vite 사용)을 Tailwind CSS와 shadcn/ui 컴포넌트와 함께 생성합니다. 스택 자체는 견고합니다. 우리도 정기적으로 Next.js 개발 작업에서 이러한 도구들을 사용합니다. 문제는 기술 선택이 아닙니다. 문제는 이들이 어떻게 연결되어 있는지입니다.
다음은 일반적인 Lovable 프로젝트 구조입니다:
src/
├── components/
│ ├── ui/ # shadcn 컴포넌트
│ ├── Dashboard.tsx
│ ├── Settings.tsx
│ └── ...
├── integrations/
│ └── supabase/
│ ├── client.ts # ← 문제가 시작되는 곳
│ └── types.ts
├── pages/
├── hooks/
└── lib/
생성된 코드는 깔끔하고 읽기 쉽습니다. 이 점은 Lovable에게 높은 점수를 주겠습니다. 하지만 읽기 쉬운 것이 안전을 의미하지는 않습니다. 아키텍처는 데모에는 괜찮지만 프로덕션에는 위험한 결정들을 내립니다.
구체적으로 살펴봅시다.
API 키 문제
우리가 감사한 모든 Lovable 프로젝트는 Supabase 자격증명을 클라이언트 측 코드에 포함하고 있습니다. 이제 Supabase 옹호자들이 나타나기 전에 말하자면: 맞습니다, anon 키는 공개되도록 설계되었습니다. Supabase의 문서에서 명시적으로 이렇게 말합니다. anon 키는 클라이언트 측 코드에서 사용되도록 의도되었고, 보안은 Row Level Security 정책을 통해 적용되어야 합니다.
하지만 여기서 문제가 발생합니다.
감사한 6개 프로젝트 중 3개에서 우리는 Supabase service_role 키가 다음 중 하나의 방식으로 되어 있음을 발견했습니다:
- 유틸리티 파일에 직접 하드코딩됨
- GitHub 저장소에 커밋된
.env파일에 저장됨 - 인증 없이 접근 가능한 Supabase Edge Function에서 참조됨
service_role 키는 모든 RLS 정책을 우회합니다. 누군가 이 키를 얻으면, 전체 데이터베이스에 대한 완전한 읽기/쓰기 접근 권한을 가집니다. 모든 테이블. 모든 행. 모든 사용자의 데이터.
// Lovable 프로젝트에서 발견한 실제 패턴 (키는 마스킹됨)
import { createClient } from '@supabase/supabase-js'
// 이것은 lib/admin.ts라는 파일에 있었습니다
// 클라이언트 측 컴포넌트에서 임포트되고 사용됨
const supabaseAdmin = createClient(
'https://xxxxx.supabase.co',
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...' // service_role 키!
)
이것은 Lovable에서 직접 생성되지 않았습니다. 설립자가 관리자 기능이 필요할 때 추가한 것으로, Lovable에 "관리자 패널 추가"라고 요청했습니다. Lovable이 준수했고, 서버 측 대 클라이언트 측 보안 경계에 대한 개념이 없으므로, 편리한 곳에 키를 놓았습니다.
anon 키만 노출되더라도 (의도대로) RLS가 올바르게 설정되지 않으면 보안 모델이 붕괴됩니다. 이것이 우리를 큰 문제로 이끕니다.
Row Level Security: 조용한 킬러
Row Level Security(RLS)는 Supabase가 데이터베이스 수준에서 데이터를 보호하는 데 사용하는 기본 보안 메커니즘입니다. 올바르게 구성되면, 클라이언트에서 어떤 API 호출이 이루어지든 사용자가 자신의 데이터에만 접근할 수 있도록 보장합니다.
6개의 Lovable 프로젝트를 감사했을 때, 다음을 발견했습니다:
| 프로젝트 | 전체 테이블 | RLS 활성화된 테이블 | 올바른 RLS 정책 있는 테이블 | 노출된 민감 데이터 |
|---|---|---|---|---|
| SaaS 대시보드 | 14 | 6 | 3 | 사용자 PII, 청구 데이터 |
| 전자상거래 앱 | 22 | 10 | 4 | 주문 내역, 주소 |
| 건강 추적 앱 | 11 | 4 | 2 | 건강 기록, 약물 |
| 프로젝트 관리자 | 18 | 8 | 5 | 클라이언트 데이터, 문서 |
| 예약 플랫폼 | 16 | 7 | 3 | 연락처 정보, 일정 |
| CRM 도구 | 20 | 9 | 4 | 고객 데이터, 노트 |
다시 읽어보세요. 건강 정보와 약물 정보를 저장한 건강 추적 앱에서 11개 테이블 중 2개만 올바른 RLS 정책을 가지고 있었습니다. anon 키(공개이며, 기억하세요)를 가진 누구든지 모든 사용자의 건강 기록을 쿼리할 수 있었습니다.
우리가 가장 자주 보는 RLS 실패:
정책 완전 누락
Lovable은 종종 RLS를 활성화하지 않고 테이블을 만듭니다. Supabase에서 RLS는 새 테이블에서 기본적으로 비활성화되어 있으므로, anon 키를 가진 누구든지 모든 데이터를 읽을 수 있습니다.
-- 우리가 자주 발견하는 것: RLS가 활성화되지 않음
CREATE TABLE public.user_profiles (
id UUID REFERENCES auth.users,
full_name TEXT,
email TEXT,
phone TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ALTER TABLE ... ENABLE ROW LEVEL SECURITY; 없음
-- 정의된 정책 없음
과도하게 허용적인 정책
Lovable이 RLS를 추가할 때, 정책은 종종 너무 광범위합니다:
-- 일반적인 Lovable 생성 정책
CREATE POLICY "Users can view all profiles"
ON public.user_profiles
FOR SELECT
USING (true); -- 모든 인증된 사용자가 모든 프로필을 읽을 수 있음
수정은 다음과 같아야 합니다:
-- 어떻게 되어야 하는지
CREATE POLICY "Users can view own profile"
ON public.user_profiles
FOR SELECT
USING (auth.uid() = id);
DELETE 및 UPDATE 정책 누락
SELECT 정책이 올바르더라도, INSERT/UPDATE/DELETE 정책이 자주 누락되거나 잘못되어 있습니다. 우리는 인증된 모든 사용자가 다른 사용자의 프로필을 업데이트할 수 있는 경우를 발견했습니다.

인증 및 인가 갭
Lovable은 기본 인증을 합리적으로 잘 처리합니다. Supabase Auth를 이메일/비밀번호 또는 소셜 로그인으로 설정하고, 로그인/가입 흐름은 일반적으로 작동합니다. 하지만 인증(당신은 누구인가?)과 인가(당신이 할 수 있는 것은 무엇인가?)는 다른 것입니다.
인가 계층은 거의 항상 불완전하거나 존재하지 않습니다.
멀티테넌트 SaaS 앱을 생각해 봅시다. 사용자는 조직에 속합니다. 자신의 조직의 데이터만 봐야 합니다. 다른 역할(관리자, 멤버, 뷰어)을 가질 수 있습니다. Lovable은 이 중 어느 것도 생성하지 않습니다.
일반적으로 발견하는 것:
// Lovable 생성 데이터 가져오기
const { data: projects } = await supabase
.from('projects')
.select('*')
// organization_id 필터 없음
// 사용자의 역할이나 권한 확인 없음
수정에는 데이터베이스 수준(RLS)과 애플리케이션 수준 모두의 변경이 필요합니다. 역할을 가진 사용자를 조직에 매핑하는 memberships 테이블, 멤버십을 확인하는 RLS 정책, 적절하게 필터링하는 애플리케이션 코드가 필요합니다.
이것은 후기에 추가하기 어려운 종류의 아키텍처 작업입니다. 모든 쿼리, 모든 컴포넌트, 모든 페이지에 영향을 미칩니다. Lovable으로 멀티테넌트 SaaS를 구축하고 있다면, 이것이 가장 당신을 물 일 가능성이 높은 것입니다.
클라이언트 측 비즈니스 로직 노출
Lovable이 순수 클라이언트 측 React 앱을 생성하므로, 모든 비즈니스 로직은 브라우저에 있습니다. 이는 다음을 의미합니다:
- 가격 계산이 브라우저 DevTools에서 표시되고 조작 가능합니다
- 기능 플래그 다양한 구독 계층은 클라이언트 측에서 확인됩니다
- 할인 코드 및 검증 로직이 JavaScript 번들에 있습니다
- API 속도 제한이 존재하지 않습니다 (이를 적용할 서버가 없습니다)
우리는 가격 계층 확인이 완전히 클라이언트 측에 있는 Lovable 생성 SaaS를 발견했습니다:
// Lovable 프로젝트의 컴포넌트에서 발견
const canAccessFeature = (feature: string) => {
const plan = user?.subscription?.plan
if (plan === 'pro') return true
if (plan === 'basic' && BASIC_FEATURES.includes(feature)) return true
return false
}
사용자는 브라우저 콘솔에서 이 함수를 수정하거나 이 확인 없이 직접 Supabase API를 호출하고 기본 플랜에서 프로 기능에 접근할 수 있습니다. 데이터베이스는 플랜 기반 접근을 강제하는 정책이 없었습니다.
이것은 기본적인 아키텍처 문제입니다. 비즈니스 로직에는 서버 측 컴포넌트가 필요합니다. 그것이 Next.js API 경로, Supabase Edge Functions, 또는 별도의 백엔드 서비스든 - 사용자가 조작할 수 없는 곳에서 작업을 검증해야 하는 뭔가가 필요합니다.
이것은 정확히 우리가 자주 프로덕션 SaaS 애플리케이션용으로 Next.js 또는 Astro 같은 프레임워크를 권장하는 이유입니다. 기본적으로 서버 측 렌더링과 API 경로를 제공합니다.
Supabase Edge Functions: 누락된 것
일부 Lovable 프로젝트는 특정 작업용으로 Supabase Edge Functions를 사용합니다 - 일반적으로 Stripe 웹훅 처리 또는 이메일 전송. 하지만 구현에는 종종 문제가 있습니다:
입력 검증 없음: Edge Functions는 형태, 유형 또는 제약을 검증하지 않고 전송되는 모든 JSON을 수락하고 처리합니다.
CORS가 모든 출처를 허용하도록 구성됨:
// Lovable Edge Functions의 일반적인 패턴
const corsHeaders = {
'Access-Control-Allow-Origin': '*', // 모든 웹사이트가 이것을 호출할 수 있음
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
인증 확인 없음: 함수는 JWT 토큰을 검증하지 않으므로 인증되지 않은 사용자가 이를 호출할 수 있습니다.
Stripe 웹훅 서명 미검증: 2개 프로젝트에서 Stripe 웹훅 핸들러가 웹훅 서명을 검증하지 않았으므로, 누구든지 엔드포인트에 가짜 결제 이벤트를 전송할 수 있습니다.
// 발견한 것 -- 서명 검증 없음
Deno.serve(async (req) => {
const body = await req.json()
// Stripe에서 왔는지 검증하지 않고 직접 이벤트 처리
if (body.type === 'checkout.session.completed') {
// 사용자의 구독 업데이트
await supabaseAdmin.from('subscriptions').update({
status: 'active',
plan: 'pro'
}).eq('user_id', body.data.object.metadata.user_id)
}
})
이는 공격자가 웹훅 URL에 가짜 checkout.session.completed 이벤트를 가진 POST 요청을 전송하고 모든 사용자를 무료로 프로 플랜으로 업그레이드할 수 있음을 의미합니다.
발견한 일반적인 취약점 패턴
다음은 심각도와 빈도순으로 정렬된 가장 일반적인 문제 요약입니다:
| 취약점 | 심각도 | 빈도 (6개 중) | 악용 용이성 |
|---|---|---|---|
| 민감 테이블에서 RLS 누락 | 치명적 | 6/6 | 쉬움 -- 테이블만 쿼리 |
| 과도하게 허용적인 RLS 정책 | 높음 | 6/6 | anon 키로 쉬움 |
| Service role 키 노출 | 치명적 | 3/6 | 발견되면 사소함 |
| Stripe 웹훅 검증 없음 | 높음 | 4/6 | 중간 -- 엔드포인트 URL 필요 |
| 클라이언트 측 인가만 | 높음 | 6/6 | DevTools로 쉬움 |
| Edge Functions에서 입력 검증 없음 | 중간 | 5/6 | 중간 |
| Edge Functions에서 CORS 와일드카드 | 중간 | 5/6 | 쉬움 |
| localStorage에 민감 데이터 | 중간 | 4/6 | 물리적 접근이나 XSS |
| 속도 제한 없음 | 중간 | 6/6 | 사소함 |
| 안전하지 않은 직접 객체 참조 | 높음 | 5/6 | 쉬움 -- URL에서 ID 변경 |
자신의 Lovable 프로젝트를 감사하는 방법
Lovable로 실제 사용자 데이터를 다루는 무언가를 만들었다면, 다음은 이러한 문제를 직접 확인하는 방법입니다.
단계 1: Supabase RLS 상태 확인
Supabase 대시보드로 이동 → 테이블 편집기. 각 테이블을 클릭하고 RLS가 활성화되어 있는지 확인합니다. 그런 다음 인증 → 정책로 이동하고 각 정책을 검토합니다.
또는 SQL 편집기에서 이 쿼리를 실행합니다:
SELECT
schemaname,
tablename,
rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY tablename;
사용자 데이터를 포함하는 테이블에서 rowsecurity가 false라면, 그것은 치명적 문제입니다.
단계 2: 노출된 키 검색
코드베이스에서 다음을 검색합니다:
grep -r "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" src/
grep -r "service_role" src/
grep -r "SUPABASE_SERVICE" src/
첫 번째 패턴은 Supabase JWT 키의 시작과 일치합니다. src/ 디렉토리에서 service_role 키를 발견했다면, 그것은 즉각적인 치명적 취약점입니다.
단계 3: RLS 정책 테스트
두 번째 테스트 사용자 계정을 만듭니다. 사용자 1의 데이터에 접근해 봅니다. 브라우저의 네트워크 탭을 확인합니다 -- 당신이 받아서는 안 될 데이터를 받고 있습니까?
curl로 직접 테스트할 수도 있습니다:
curl 'https://YOUR_PROJECT.supabase.co/rest/v1/user_profiles?select=*' \
-H "apikey: YOUR_ANON_KEY" \
-H "Authorization: Bearer YOUR_ANON_KEY"
이것이 인증 없이 모든 사용자 프로필을 반환한다면, RLS가 깨져 있습니다.
단계 4: Edge Functions 확인
각 Edge Function을 다음에 대해 검토합니다:
- JWT 검증
- 입력 검증
- CORS 설정
- 웹훅 서명 검증 (Stripe/결제 핸들러용)
단계 5: 클라이언트 측 번들 검사
npm run build를 실행하고 출력을 검사합니다. 빌드된 JavaScript에서 API 키, 비즈니스 로직, 가격 데이터를 검색합니다. 번들의 모든 것은 사용자에게 표시됩니다.
재구축 대 수정: 언제 어떤 것을 할 것인가
이것은 모든 Lovable 설립자가 이러한 문제의 존재를 깨닫고 마주하는 질문입니다. 답변은 여러 요소에 따라 달라집니다:
수정하면 좋은 경우:
- 앱이 10개 미만의 테이블을 가지고 있습니다
- 복잡한 인가 모델이 없습니다 (멀티테넌시 없음, 역할 없음)
- 핵심 아키텍처 (데이터 모델, 페이지 구조)가 건전합니다
- 주로 RLS 정책을 추가하고 일부 로직을 서버 측으로 이동해야 합니다
재구축하면 좋은 경우:
- 역할과 권한이 있는 멀티테넌트 아키텍처가 필요합니다
- 비즈니스 로직이 복잡하고 모두 클라이언트 측입니다
- SEO나 성능을 위해 서버 측 렌더링이 필요합니다
- Supabase 스키마에 상당한 구조적 문제가 있습니다
- 적절한 인프라가 필요한 규모에 있습니다
수정의 경우, 일반적으로 모든 테이블에 RLS 정책을 추가하고, 민감 로직을 Edge Functions 또는 서버 측 계층으로 마이그레이션하고, 적절한 입력 검증을 구현하고, 속도 제한을 추가하는 것을 살펴보고 있습니다. 이것은 복잡도에 따라 경험 있는 개발자를 위해 2-4주 프로젝트입니다.
완전 재구축의 경우, 우리는 일반적으로 적절한 관심사의 분리로 헤드리스 아키텍처를 권장합니다. 선행 비용이 더 많이 들지만 확장하는 기초를 제공합니다. 무엇이 그것처럼 보이는지에 대한 감각을 위해 우리의 가격 페이지를 확인합니다.
어떤 경로가 맞는지 확실하지 않다면, 우리는 빠른 평가를 수행할 수 있습니다. 우리의 연락처 페이지에서 연락합니다.
FAQ
Lovable을 프로덕션 애플리케이션에 사용하기에 안전한가요? Lovable은 견고한 시작점을 생성할 수 있지만, 출력은 프로덕션 준비 전에 중요한 보안 강화가 필요합니다. 생성된 코드에는 적절한 RLS 정책, 서버 측 검증, 인가 로직이 부족합니다. 이것을 완성된 건물이 아니라 비계로 생각하세요. 실제 사용자가 데이터를 신뢰하기 전에 반드시 개발자가 코드를 검토하고 보안을 수행해야 합니다.
Lovable이 내 Supabase API 키를 노출하나요?
Supabase anon 키는 의도적으로 공개입니다 -- 이것은 설계에 의한 것이며, Supabase의 보안 모델은 RLS를 통해 이를 고려합니다. 문제는 Lovable (또는 당신이 프롬프트를 통해) service_role 키를 클라이언트 측 코드에 배치할 때입니다. anon 키가 공개인 것은 RLS 정책이 완벽할 때만 안전하며, Lovable 생성 프로젝트에서는 일반적으로 그렇지 않습니다.
Row Level Security란 무엇이고 왜 중요한가요? Row Level Security(RLS)는 사용자가 어떤 행을 읽고, 삽입하고, 업데이트하거나, 삭제할 수 있는지를 제어하는 데 Supabase가 사용하는 PostgreSQL 기능입니다. RLS 없이, 공개 anon 키를 가진 누구든지 전체 데이터베이스를 쿼리할 수 있습니다 -- 모든 사용자의 데이터, 모든 비공개 기록. 이것은 Supabase 기반 애플리케이션에서 가장 중요한 보안 메커니즘이며, Lovable 프로젝트에서 가장 자주 잘못 구성되는 것입니다.
개발자 없이 Lovable 보안 문제를 스스로 수정할 수 있나요? SQL과 Supabase의 RLS 정책 구문을 이해한다면, Supabase 대시보드를 사용하여 기본 RLS 정책을 직접 추가할 수 있습니다. 그러나 정책을 올바르게 설정하는 것 -- 특히 멀티테넌시, 공유 리소스, 또는 관리자 접근과 같은 복잡한 시나리오의 경우 -- 경험이 필요합니다. 잘못된 정책은 사용자를 자신의 데이터에서 차단하거나 모든 것을 노출할 수 있습니다. 간단한 개인 프로젝트 이상의 것에 대해서는 전문가 검토를 받으세요.
Lovable 앱의 데이터베이스가 안전한지 확인하려면 어떻게 해야 하나요?
가장 빠른 테스트: 브라우저의 DevTools를 열고, 네트워크 탭으로 가서, 앱이 만드는 Supabase API 호출을 봅니다. apikey 헤더 값을 복사합니다. 그런 다음 curl 또는 Postman을 사용하여 인증 토큰 없이 해당 키만 사용하여 테이블을 직접 쿼리합니다. 다른 사용자의 데이터를 얻거나 비공개여야 하는 테이블에서 어떤 데이터든 얻으면 -- RLS가 깨져 있습니다.
AI 생성 코드에서 일반적으로 가장 큰 보안 위험은 무엇인가요? AI 코드 생성기는 안전하게 하는 것이 아니라 작동하도록 최적화합니다. 당신의 위협 환경의 정신 모델이 없습니다. 가장 큰 위험: 노출된 비밀, 누락된 입력 검증, 과도하게 허용적인 접근 제어, 서버 측 보안 경계의 부재. 이것들은 Lovable에만 고유하지 않습니다 -- Cursor, v0, Bolt, 및 기타 AI 도구의 코드에도 유사한 문제가 존재합니다. 차이점은 Lovable이 프로덕션에 직접 배치되는 완전한 애플리케이션을 생성한다는 것입니다.
Lovable을 사용한 후 다른 백엔드로 전환해야 하나요? Supabase 자체는 괜찮습니다. 적절한 보안 기능이 있는 견고한 플랫폼입니다. 문제는 Lovable이 어떻게 이를 구성하는지입니다. Supabase를 포기할 필요가 없습니다 -- RLS 정책을 적절하게 구성하고, 민감 작업을 Edge Functions로 이동하고, Lovable이 건너뛴 인가 계층을 추가해야 합니다. 인프라는 괜찮습니다. 구성만 작업이 필요합니다.
Lovable 생성 앱의 보안 문제를 수정하는 데 얼마의 비용이 듭니까? 직설적인 수정의 경우 -- RLS 정책 추가, Edge Functions 보안, 노출된 키 제거, 기본 입력 검증 추가 -- 테이블 수와 인가 모델의 복잡도에 따라 대략 $3,000-$8,000을 보고 있습니다. 적절한 아키텍처로 완전 재구축은 범위에 따라 $15,000-$50,000+ 실행됩니다. 코어 데이터 모델이 건전하다면 수정 경로는 거의 항상 더 비용 효율적입니다.