Claude나 Cursor로 바이브 코딩한 앱을 유료 클라이언트에게 건네려고 한다면, 잠깐. AI 생성 코드는 예측 가능한 보안 허점을 가지고 배포된다 — 노출된 API 키, 누락된 입력 검증, 모든 사용자가 다른 사용자의 데이터를 볼 수 있게 하는 기본 데이터베이스 권한 등. 이런 것들은 엣지 케이스가 아니다. 거의 모든 AI 생성 코드베이스 초안에서 발생한다.
이 가이드는 런칭 전 보안 체크리스트다. 실제 사용자가 앱을 만지기 전에 단계별로 따라가자. 가장 흔한 바이브 코딩 스택(Next.js + Supabase + Vercel)을 기준으로 작성되었지만, 어떤 도구를 사용하든 원칙은 동일하게 적용된다.
AI 생성 코드가 보안 문제를 가진 이유
AI 모델은 "작동하는가?"에 최적화되어 있지, "안전한가?"에는 최적화되어 있지 않다. Claude에게 "사용자 계정이 있는 작업 관리자를 만들어줘"라고 말하면, 사용자를 생성하고 작업을 저장하고 표시하는 코드를 생성할 것이다. 자동으로 하지 않을 가능성이 높은 것: 사용자 A가 사용자 B의 작업을 볼 수 없도록 보장하기, 입력 필드가 악의적인 스크립트를 받을 수 없도록 검증하기, 브라우저 개발자 도구에서 API 키 숨기기, 또는 누군가가 엔드포인트를 해머링하는 것을 방지하기 위해 속도 제한 추가하기.
이것들은 AI의 실패가 아니라 프롬프트의 간극이다. AI는 당신이 요청한 것을 만든다. 기능에 집중했기 때문에 아마 보안을 요청하지 않았을 것이다. 이제 다시 돌아가서 보안을 추가할 시간이다.
1단계: 환경 변수 감사
이것은 바이브 코딩 앱에서 가장 흔하고 가장 위험한 실수다. 프로젝트의 모든 파일에서 하드코딩된 API 키, 데이터베이스 URL 또는 비밀을 확인하자.
찾을 것: sk-, eyJ, sbp_, supabase, postgres:// 또는 길게 보이는 무작위 문자열로 시작하는 문자열을 코드베이스에서 검색하자. 이 파일들을 구체적으로 확인하자: /app 또는 /pages 디렉토리의 모든 파일, 모든 컴포넌트 파일, next.config.js, 그리고 모든 유틸리티 파일.
해결책: 모든 비밀을 환경 변수로 이동시키자. Next.js에서 NEXT_PUBLIC_으로 시작하는 변수만 브라우저에 노출된다. 데이터베이스 URL, 서비스 역할 키, API 비밀은 절대 이 접두사를 가져서는 안 된다.
# .env.local (이 파일을 절대 커밋하지 말 것)
SUPABASE_SERVICE_ROLE_KEY=your-secret-key
DATABASE_URL=postgres://...
# 이것들은 브라우저에 노출해도 됨:
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
검증: .gitignore 파일에 .env.local이 포함되어 있는지 확인하자. 이미 Git에 비밀을 커밋했다면, 삭제 후에도 히스토리에 남아있다 — 노출된 모든 키를 즉시 순환(재생성)하자.
2단계: Supabase에서 행 수준 보안 활성화
Supabase를 사용 중이라면, 이것이 단일 가장 중요한 단계다. 기본적으로 Supabase 테이블에는 접근 제한이 없다 — 익명 키를 가진 누구든 모든 테이블의 모든 행을 읽고 쓸 수 있다. 이것은 사용자 A가 간단한 API 호출로 사용자 B의 데이터를 볼 수 있다는 의미다.
해결책: 모든 테이블에서 행 수준 보안(RLS)을 활성화한 후 접근을 제한하는 정책을 생성하자.
Supabase 대시보드 → 테이블 편집기 → 각 테이블 선택 → "RLS 비활성화"를 클릭해 활성화하자. 그 다음 정책을 추가하자:
사용자가 자신의 데이터만 봐야 하는 일반적인 앱의 경우, SELECT 정책을 생성하자: auth.uid() = user_id. INSERT, UPDATE, DELETE에도 유사한 정책을 생성하자.
테스트: 사용자 A로 로그인한 후, API를 통해 사용자 B의 데이터에 접근하려고 시도해보자. 볼 수 있다면, 정책이 잘못된 것이다. Supabase는 SQL 편집기에서 정책을 직접 테스트할 수 있다.
일반적인 AI 실수: Claude는 종종 anon 키 대신 service_role 키를 사용하는 Supabase 쿼리를 생성한다 (이것은 RLS를 우회한다). 클라이언트 측 코드가 익명 키만 사용하는지 확인하자. 서비스 역할 키는 서버 측 코드(API 라우트, 서버 액션)에만 존재해야 하고 브라우저에 절대 노출되어서는 안 된다.
3단계: 인증을 제대로 추가하자
AI 생성 인증 코드는 종종 작동하지만 지름길을 택한다. 이 특정 문제들을 확인하자:
세션 관리: 세션이 만료되는지 확인하자. 인증 설정에 합리적인 세션 타임아웃이 포함되어 있는지 확인하자 (Supabase 기본값이 대부분 좋지만, 검증하자). 로그아웃이 실제로 세션을 무효화하는지, 단순히 로컬 쿠키만 지우는지 확인하자.
비밀번호 요구사항: 이메일/비밀번호 인증이 있다면, 최소 비밀번호 길이(8자 이상)를 시행하자. Supabase는 프로젝트 설정 → 인증 → 비밀번호 요구사항에서 이것을 처리한다.
보호된 라우트: 사용자 특정 데이터를 표시하는 모든 페이지는 인증 미들웨어가 필요하다. Next.js App Router에서 유효한 세션을 확인하고 인증되지 않은 사용자를 로그인 페이지로 리다이렉트하는 미들웨어를 생성하자. 클라이언트 측 검사만 신뢰하지 말자 — 사용자는 API를 직접 치면 이를 우회할 수 있다.
이메일 확인: Supabase 인증 설정에서 이메일 확인을 활성화하자. 이것은 사람들이 가짜 이메일 주소로 계정을 만드는 것을 방지하고 계정 유효성의 기본 계층을 추가한다.
이게 도움이 되었나? 우리는 매주 AI 도구, 워크플로우, 실용 가이드에 대한 깊이 있는 콘텐츠 한 개를 발행한다. 먼저 받는 독자들과 함께하자 →
4단계: 모든 입력 검증
AI 생성 폼은 보통 기본 검증(필수 필드, 이메일 형식)을 가지고 있지만 악의적인 입력으로부터 보호하는 경우는 드물다.
추가할 것:
모든 API 엔드포인트에서 서버 측 검증. 클라이언트 측 검증만 신뢰하지 말자 — API에 직접 요청을 보내면 우회될 수 있다. Zod (TypeScript용)와 같은 검증 라이브러리를 사용해 앱이 받아들이는 모든 데이터에 대한 스키마를 정의하자.
브라우저에 표시되는 사용자 생성 콘텐츠의 HTML을 새니타이즈하자. 앱에 댓글, 설명 또는 브라우저에서 렌더링되는 텍스트 필드가 있으면, DOMPurify 같은 라이브러리를 사용해 위험한 스크립트를 제거하자. 이것이 없으면, 누군가 다른 사용자의 세션을 도용하는 JavaScript를 주입할 수 있다 (교차 사이트 스크립팅 / XSS).
파일 업로드 크기와 유형을 제한하자. 앱이 파일 업로드를 받으면, AI 생성 업로드 핸들러에는 종종 제한이 없어서, 누군가 2GB 파일이나 실행 파일을 업로드할 수 있다. 크기 제한(대부분 앱에 5MB가 합리적)을 추가하고 파일 유형을 실제로 필요한 것(이미지, PDF 등)으로 제한하자.
5단계: API 라우트 보안
앱의 모든 API 라우트나 서버 액션에서 이 문제들을 확인하자:
모든 엔드포인트에서 인증. 사용자 데이터를 반환하거나 수정하는 모든 API 라우트는 먼저 사용자의 세션을 확인해야 한다. AI는 종종 누구로부터든 요청을 받는 API 라우트를 생성한다.
인증 이상의 인가. 사용자가 로그인했음을 확인한 후에도, 그들이 요청하는 특정 리소스에 접근할 수 있는지 확인하자. "사용자 A가 로그인했다"는 "사용자 A가 사용자 B의 프로필을 편집할 수 있다"를 의미하지 않는다. 모든 데이터 접근에서 소유권을 확인하자.
속도 제한. 속도 제한이 없으면, 누군가 초당 수천 개의 요청을 API로 보낼 수 있는데, 이것은 데이터를 스크래핑하거나 서버를 압도하기 위한 것이다. rate-limiter-flexible 같은 라이브러리를 사용해 기본 속도 제한을 추가하거나 Vercel의 Edge Functions 내장 속도 제한을 사용하자.
HTTP 메서드. API 라우트가 응답해야 할 HTTP 메서드만 응답하는지 확인하자. POST 요청을 처리하는 라우트가 명시적으로 설계하지 않는 한 DELETE 요청에도 응답해서는 안 된다.
6단계: 배포 설정 확인
배포 플랫폼에는 AI가 구성하지 않는 보안 설정이 있다.
확인할 Vercel 설정: "배포 보호" 활성화 (미리보기 배포를 보기 위해 인증 필요 — 클라이언트가 진행 중인 작업을 노출하는 미리보기 URL을 실수로 공유하는 것을 방지). 예상치 못한 청구를 방지하기 위해 앱이 트래픽 스파이크를 받으면 지출 제한을 설정하자. CORS 헤더에서 허용된 도메인을 구성하자.
커스텀 도메인과 SSL: 클라이언트에 배포하는 경우, HTTPS로 커스텀 도메인을 설정하자. Vercel과 Netlify는 SSL을 자동으로 처리한다. 클라이언트 앱을 .vercel.app 서브도메인에서 배포하지 말자 — 전문성이 없어 보이고 클라이언트가 쉽게 이전할 수 없다.
헤더: next.config.js 또는 vercel.json에 보안 헤더를 추가하자: X-Content-Type-Options: nosniff, X-Frame-Options: DENY (클릭재킹을 위해 사이트가 iframe에 포함되는 것을 방지), Strict-Transport-Security (HTTPS를 강제). 이것들은 일회성 추가지만 공격의 전체 계층을 방지한다.
7단계: 최종 보안 스캔 실행
클라이언트에게 무언가를 건네기 전에, 이 무료 검사들을 실행하자:
npm audit: 프로젝트 디렉토리에서 npm audit를 실행하자. 의존성에서 알려진 취약점을 표시한다. 중요(Critical) 및 높음(High) 심각도 문제를 해결하자. 자동 수정이 가능한 경우 npm audit fix를 실행하자.
Lighthouse: 배포된 사이트를 Chrome에서 열고, DevTools를 열고, Lighthouse 감사를 실행하자. "모범 사례" 점수를 확인하자 — HTTPS 누락, 취약한 라이브러리, 불안전한 헤더 같은 일반적인 보안 문제를 잡아낸다.
수동 테스트: 한 사용자로 로그인한 후, URL이나 API 호출을 수정해 다른 사용자의 데이터에 접근하려고 시도하자. 빈 폼, 크기를 초과한 입력, 특수 문자(인코딩된 XSS 페이로드 같은)를 제출해보자. 이 중 하나라도 작동하면, 해결해야 할 문제가 있다.
런칭 전 체크리스트
이것을 인쇄해 라이브되기 전에 모든 항목을 확인하자:
- 모든 비밀이 환경 변수에 있음 (하드코딩된 키 없음)
.env.local이.gitignore에 있음 (그리고 Git 히스토리에 비밀 없음)- 모든 테이블에서 Supabase RLS 활성화됨
- RLS 정책이 테스트됨 (사용자 A가 사용자 B의 데이터를 볼 수 없음)
- 클라이언트 측 코드가 익명 키만 사용 (서비스 역할 키는 서버 측만)
- 모든 API 라우트에서 인증
- 데이터 접근에서 인가 검사 (소유권 검증)
- 모든 폼에서 입력 검증 (클라이언트 측 아닌 서버 측)
- 파일 업로드 제한 (크기와 유형) (해당하는 경우)
- API 엔드포인트에서 속도 제한
- 보안 헤더 구성됨
npm audit실행되고 중요 문제 해결됨- SSL로 구성된 커스텀 도메인
- 미리보기 배포 보호됨
- 수동 교차 사용자 데이터 접근 테스트 통과됨
핵심
바이브 코딩된 앱을 보안하는 것은 보안 전문가가 되는 것이 아니다. 그것은 AI가 남기는 예측 가능한 간극을 잡는 체크리스트를 실행하는 것이다. 위의 단계들은 2~4시간이 걸리고 가장 일반적인 취약점을 방지한다. 클라이언트 대면 앱의 경우, 이것은 선택사항이 아니다 — 그것은 전문적인 작업과 책임 사이의 차이다.
바이브 코딩 앱을 처음 만드는 경우, 바이브 코딩 완전 가이드로 시작하자. Claude나 Cursor에 사용하는 프롬프트를 개선하려면, 무료 프롬프트 옵티마이저를 시도해보자. 최고의 코딩 도구 분석은 Claude Code vs Codex를 참고하자.
이것이 우리가 매주 하는 것이다. AI 도구, 워크플로우, 정직한 의견에 대한 하나의 깊이 있는 콘텐츠 — 과장 없음, 불필요한 내용 없음. 우리와 함께하자 →
공개: 이 문서의 일부 링크는 제휴 링크다. 우리는 개인적으로 테스트하고 정기적으로 사용하는 도구만 추천한다. 전체 공개 정책은 여기를 참고하자.