Vibe-coded 앱을 배포하기 전 확인해야 할 12가지 사항
요약
AI 도구로 생성된 'Vibe-coded' 앱들이 보안에 매우 취약하다는 연구 결과를 바탕으로, 배포 전 반드시 점검해야 할 12가지 보안 체크리스트를 제시합니다. 특히 .env 파일 노출, .git 디렉토리 노출, 클라이언트 번들 내 민감 정보 포함 문제를 중점적으로 다룹니다.
핵심 포인트
- Vibe-coded 앱의 98%가 최소 하나 이상의 보안 문제를 보유함
- .env 및 .git 디렉토리의 HTTP 접근 가능 여부를 반드시 확인해야 함
- 클라이언트 번들 내 service_role 키 등 민감한 비밀 정보 노출 주의
- curl과 브라우저 개발자 도구만으로도 기본적인 보안 검토 가능
앱이 제대로 '작동'하게 만드는 것은 더 이상 어려운 일이 아닙니다. 당신이 원하는 것을 설명하면, Lovable이나 Bolt 또는 v0가 이를 구축하고, 40분 후에는 실제 사람들이 클릭할 수 있는 실제 URL에 무언가가 나타납니다.
어려운 부분은 옮겨갔습니다. 이제는 "작동한다"와 "인터넷과의 접촉에서 살아남는다" 사이의 모든 것이 문제입니다.
그 간극은 단순히 느낌(vibe)의 문제가 아닙니다. 측정 가능한 문제입니다. Symbiotic Security는 2026년 6월에 65,643개의 URL을 크롤링하고 Supabase 기반의 vibe-coded 앱 1,072개를 완전히 스캔했습니다. 그 결과 98%가 최소 하나 이상의 보안 문제를 가지고 있었고, 16%는 심각한 문제를 가지고 있었습니다. Deng 등이 수행한 별도의 학술 연구에 따르면, vibe-coded 앱은 전통적인 코드베이스(codebase)가 생성하는 패턴과는 다른 '반복적인' 취약점 패턴을 보여줍니다. 즉, 이것들은 무작위적인 실수가 아니라 구조적인 문제입니다. 그리고 SecurityWeek가 보고한 Xint.io의 분석에 따르면, 비밀 정보 노출(secrets exposure), 권한 부여 오류(broken authorization), 서비스 거부(denial of service)에 집중된 434개의 악용 가능한 결함이 발견되었습니다.
동일한 몇 가지 실패 모드(failure modes)가 계속해서 반복됩니다. 이는 좋은 소식인데, 약 15분이면 이를 확인할 수 있다는 의미이기 때문입니다.
아래는 제가 실제로 검토하는 목록입니다. 여기에 있는 모든 것은 curl과 브라우저 개발자 도구(devtools)를 사용하여 자신의 도메인에 실행해 볼 수 있습니다. 별도의 도구는 필요하지 않습니다.
1. .env 파일이 HTTP를 통해 접근 가능한가요?
가장 흔하게 발견되는 치명적인 문제입니다. 빌드 출력 디렉토리(build output directory)와 프로젝트 루트(project root)가 동일하게 설정될 때 발생합니다.
curl -sI https://yourapp.com/.env | head -1
curl -sI https://yourapp.com/.env.local | head -1
curl -sI https://yourapp.com/.env.production | head -1
404 이외의 응답이 나온다면 비상 상황입니다. 다른 무엇을 하기 전에 해당 파일의 모든 키를 교체(rotate)하십시오. 봇들이 이러한 경로를 끊임없이 공격하므로, 이미 정보가 긁어갔다고 가정해야 합니다.
2. .git 디렉토리가 노출되어 있나요?
.env보다 더 심각합니다. 당신이 삭제했다고 '생각했던' 키들을 포함하여 전체 히스토리를 넘겨주기 때문입니다.
curl -sI https://yourapp.com/.git/HEAD | head -1
curl -s https://yourapp.com/.git/config
만약 HEAD가 200을 반환한다면, 낯선 이가 전체 저장소(repository)를 재구성할 수 있다는 뜻입니다.
3. 클라이언트 번들(client bundle)에 어떤 키들이 들어있나요?
개발자 도구(devtools)를 열고, Sources 탭으로 이동하여 모든 파일을 검색하세요. sk-, service_role, SECRET, PRIVATE_KEY, 그리고 eyJ로 시작하는 긴 문자열(JWT)을 찾아야 합니다.
사람들을 헷갈리게 만드는 미묘한 차이가 있습니다: 브라우저에 Supabase anon 키가 있는 것은 설계상 괜찮습니다. 하지만 service_role 키는 안 됩니다. 이는 행 수준 보안 (Row-Level Security, RLS)을 완전히 우회하기 때문입니다. AI 어시스턴트들은 프롬프트 관점에서 둘 다 "Supabase 키"라고 인식하기 때문에 이 둘을 끊임없이 혼동합니다.
4. 행 수준 보안 (RLS)이 실제로 켜져 있나요?
브라우저에 anon 키가 있는 것이 안전하려면 모든 테이블에 RLS가 활성화되어 있어야 합니다. 기본값이 '꺼짐(off)'이라는 점이 함정입니다. 테이블을 하나씩 확인하며 정책(policies)이 존재하는지 확인하세요. "정책은 나중에 추가할게요"라는 생각이 바로 16%의 사고를 일으키는 원인입니다.
5. 보안 헤더 (Security headers)
curl -sI https://yourapp.com | grep -iE 'content-security-policy|strict-transport-security|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'
대부분의 Vibe-coded 배포 환경은 이 중 어느 것도 반환하지 않습니다. 즉각적으로 가장 중요한 두 가지는 클릭재킹 (clickjacking)을 방지하기 위한 Content-Security-Policy (또는 최소한 frame-ancestors)와, 다운그레이드 공격 (downgrade attack)이 TLS를 제거하지 못하도록 하는 Strict-Transport-Security입니다.
6. 공개된 소스 맵 (Source maps)
curl -sI https://yourapp.com/_next/static/chunks/main.js.map | head -1
프로덕션 환경의 소스 맵은 공격자에게 주석, 내부 함수 이름, 데드 코드 경로 (dead code paths) 등을 포함한 읽기 쉬운 원본 소스를 그대로 넘겨주는 것과 같습니다. 빌드 설정에서 소스 맵을 끄거나, 인증된 사용자만 접근할 수 있도록 제한하세요.
7. 디버그 잔재 및 고립된 경로 (Debug leftovers and orphan routes)
자신의 번들에서 console.log를 grep으로 검색하여 페이지 로드 시 민감한 정보가 출력되는지 확인하세요. 그런 다음, 아무도 배포할 의도가 없었던 경로들을 테스트해 보세요: /api/debug, /api/test, /admin, /api/seed. AI가 생성한 스캐폴딩 (scaffolding)은 인증되지 않은 이러한 경로들을 남겨두는 경우가 많습니다.
8. 인증 및 API 엔드포인트의 속도 제한 (Rate limiting)
명시적으로 요청하지 않는 한 거의 존재하지 않습니다. 속도 제한이 없다면, 귀하의 로그인 엔드포인트는 무차별 대입 공격 (credential-stuffing)의 표적이 되며, LLM 기반 API 경로는 타인이 무료로 추론 예산 (inference budget)을 사용하는 통로가 됩니다. 호스팅 업체가 에지 (edge)에서 속도 제한을 제공하는지 확인하세요. 종종 단순히 활성화하지 않은 설정 플래그 (config flag)인 경우가 많습니다.
9. 정보가 유출되는 에러 메시지
의도적으로 실패를 유도해 보세요: POST 엔드포인트에 잘못된 형식의 JSON을 보내거나, 경로 파라미터 (path parameter)에 잘못된 ID를 입력합니다. 만약 스택 트레이스 (stack trace), 파일 경로, 또는 테이블과 컬럼 이름을 명시하는 ORM 에러가 반환된다면, 이는 귀하의 시스템을 탐색하는 누구에게나 무료 정찰 정보를 제공하는 것과 같습니다.
10. 수정되지 않은 보일러플레이트 (Boilerplate)
curl -s https://yourapp.com | grep -iE '<title>|og:image|og:description'
만약 여전히 "Create Next App"이라고 표시되거나 Open Graph 이미지가 없다면, 귀하가 이 코드를 아무도 검토하지 않았음을 광고하고 있는 셈입니다. 이것이 직접적인 취약점은 아니지만, 제품의 다른 모든 요소에 대한 판단 기준을 바꿉니다. 여기에는 귀하의 서비스가 공격할 가치가 있는지 결정하는 보안 연구원의 판단도 포함됩니다.
11. 동의 전 실행되는 트래커 (Trackers)
네트워크 (Network) 탭을 열고, 강력 새로고침 (hard-reload)을 한 뒤, 아무것도 클릭하기 전에 페이지에서 무엇이 나가는지 관찰하세요. 만약 Google Analytics, Meta Pixel 또는 세션 레코더 (session recorder)가 로드 시점에 실행된다면, EU에서는 동의 (consent) 문제가 발생하며, 일반적으로는 "거기에 있는 줄 몰랐다"는 식의 관리 문제가 발생합니다. AI가 생성한 템플릿에는 분석 스니펫 (analytics snippets)이 내장되어 배포되기 때문입니다.
관련되어 놓치기 쉬운 사항: 런타임에 Google CDN에서 로드되는 Google Fonts는 방문자의 IP 주소를 제3국으로 전송합니다. 독일 법원은 2022년에 정확히 이 문제에 대해 판결을 내렸으며, 이는 경고장 발송의 물결을 일으켰습니다. 폰트를 셀프 호스팅 (self-host)하세요. 어차피 그게 더 빠릅니다.
12. 임프린트 (Imprint) 및 개인정보 처리방침
독일이나 오스트리아에 사용자가 있다면 임프린트 (Imprint)는 있으면 좋은 것이 아니라 법적 요구 사항이며, 개인정보 처리방침 (privacy policy)은 실제로 무엇을 수집하는지 기술해야 합니다. 이는 비용이 들지 않으면서도 가장 많이 누락되는 점검 사항입니다. 지루하고 아무도 프롬프트로 요청하지 않았기 때문입니다.
15분 버전
DOMAIN="https://yourapp.com"
for path in /.env /.env.local /.env.production /.git/HEAD /.git/config \
...
첫 번째 블록의 모든 200은 발견 사항입니다. 두 번째 블록에서 누락된 모든 헤더는 취약점입니다. 본인의 도메인에 대해서만 실행하세요. 이것은 셀프 감사 (self-audit)이지, 타인의 사이트를 지적하기 위한 스캐너가 아닙니다.
결과에 따른 조치 사항
세 가지 범주로 분류하고, 자신이 어디에 속하는지 솔직하게 판단하세요.
노출된 비밀 값 (secrets), 열려 있는 .git, 또는 번들 내의 service_role 키가 발견되었다면 중단 (stop) 하세요. 키를 교체(rotate)하고, 수정하고, 재배포하세요. 깨끗해질 때까지 아무것도 발표하지 마세요.
누락된 헤더, 소스 맵 (source maps), 디버그 경로 (debug routes) 및 보일러플레이트 메타데이터 (boilerplate metadata)가 발견되었다면 반복 (iterate) 하세요. 이는 실제적인 문제이며 오후 시간 내에 수정 가능합니다. 소규모 사용자 대상의 소프트 런칭 (soft launch)을 미룰 이유는 아닙니다.
모든 것이 깨끗하다면 진행 (go) 하세요. 단, 이것은 특정 시점의 스냅샷이라는 점을 유의해야 합니다. 다음에 생성될 AI 기능이 이 중 어떤 것이든 다시 도입할 수 있으므로, 의미 있는 배포를 할 때마다 다시 실행하세요.
공지: 저는 AI로 구축된 제품을 출시하는 팀들을 위해 바로 이러한 종류의 리뷰를 수행하는 decivo에서 근무하고 있습니다. 저희는 이 체크리스트의 외부 관점(outside-in) 부분을 Vibe Code Rescue라는 무료 스캔 도구로 구현했습니다. URL을 붙여넣으면 외부 점검을 실행하고 Go / Iterate / Stop 판정을 내려줍니다. 가입도 필요 없고, 코드 접근도 필요 없으며, 아무것도 저장되지 않습니다. 직접 하고 싶다면 위의 수동 체크리스트가 동일한 내용을 다루고 있으며, 그것 또한 진심으로 괜찮습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기