저는 수요일 오후에 GeminiOmni의 첫 번째 공개 버전을 출시했습니다. 초기 버전의 Nano Banana 2 이미지 편집기라는 단 하나의 작동하는 도구와 "회원가입 없이 무료로 사용해보기" 버튼이 있는 간소화된 랜딩 페이지였습니다.
사이트는 베를린 시간으로 14:42에上线되었습니다. 15:05가 되자 Google Cloud 청구 대시보드에 경고가 울리기 시작했습니다.
이 글은 무엇이 잘못되었는지, 비용이 얼마나 들었는지, 그리고 같은 근무일이 끝나기 전에 제가 무엇을 변경했는지에 대한 전체 분석입니다. 유료 AI API를 사용하는 무언가를 구축 중이라면, 이것을 공짜 교훈으로 받아들이세요. 대부분의 사람들은 부끄러운 부분에 대해 글을 쓰지 않으니까요.
실제 상황
제 사고 기록(네, 보관합니다. 추천합니다)에 따른 타임라인은 다음과 같습니다.
- 14:42 배포 성공. 사이트가 geminiomni-ai.com에서 운영 시작. 소규모 Mastodon 인스턴스에 간단한 데모를 올리고 커피를 타러 갔습니다.
- 14:58 Google Cloud의 Slack 알림을 보고 돌아왔습니다: "현재 주기에 대한 청구 금액이 $5를 초과했습니다." 이는 당황스러운 일이었습니다. 주기가 시작된 지 16분밖에 되지 않았기 때문입니다.
- 15:05 Gemini API 콘솔에 로그인했습니다. 지난 20분 동안 3개 대륙의 38개 개별 IP에서 412개의 이미지 생성 요청이 있었습니다.
- 15:08 사이트를 내렸습니다. API 키를 폐기했습니다. 아직 사용하지 않을 새 키를 생성했습니다.
- 15:42 아키텍처 재작성을 시작했습니다.
- 18:30 수정 사항이 포함된 v2를 출시했습니다. 사이트를 다시 온라인으로 전환했습니다.
- 23:00 4시간의 실수에 대한 총 청구 금액: $84.30.
참고로 저는 첫 달 전체 API 사용량에 $200을 책정했습니다. 제가 더 잘 알았어야 할 실수 때문에 17분 만에 예산의 42%를 소진했습니다.
제가 한 일
이미지 편집기의 첫 번째 버전은 다음과 같이 연결되었습니다: 사용자의 프롬프트와 업로드된 이미지를 받아 NEXT_PUBLIC_GEMINI_API_KEY에 포함된 API 키와 @google/genai 클라이언트를 사용하여 브라우저에서 직접 Google의 Gemini Image API를 호출하는 React 컴포넌트가 포함된 Next.js 페이지였습니다.
네. 유료 API 키를 NEXT_PUBLIC_*에 넣었습니다. 그러면 Next.js는 이를 클라이언트 JavaScript에 성실히 번들로 포함시킵니다. DevTools를 열면 누구나 볼 수 있습니다.
변명을 하자면(그리고 이 변명은 그리 훌륭하지 않습니다), 원래 프로토타입은 사설 localhost 실험이었습니다. 빠르게 반복하고 있었고 API 라우트를 설정하고 싶지 않았기 때문에 키를 클라이언트 측 환경 변수로 연결했습니다. 공개 배포를 준비할 때 약 40가지 항목을 변경했는데 키 위치는 그중 하나가 아니었습니다.
이 실수는 저만의 것이 아니었습니다. 제가 존경하는 6명 정도의 사람들도 2024-2025년에 이와 유사한 변형을 겪었습니다. 이는 제가 본 가장 흔한 예방 가능한 AI 비용 사고입니다.
어떻게 그렇게 빨리 도용되었는가
저를 놀라게 한 세부 사항은 요청이 얼마나 빨리 들어오기 시작했는지였습니다. 약 600명의 팔로워가 있는 Mastodon 인스턴스에 게시했는데, 그들 중 누구도 악의적으로 이렇게 하지 않았을 것입니다. 트래픽은 다른 곳에서 오고 있었습니다.
요청 로그에서 알아낸 내용은 다음과 같습니다.
- 사이트가上线된 지 5분 이내에 자동화된 크롤러가 사이트를 방문하여 JavaScript에서 번들된
NEXT_PUBLIC_GEMINI_API_KEY를 추출하고, 제가 몰랐던 키 거래 채널에 게시했습니다. - 10분 이내에 약 38개의 다른 IP가 해당 키를 사용하여 제 제품과 전혀 상관없는 이미지를 생성하고 있었습니다. 로그에서 샘플링한 대부분의 프롬프트는 일반적이었습니다("빨간 스포츠카", "숲 속의 애니메이션 소녀"). 일부는 적대적 프롬프트로 모델의 한계를 테스트하고 있었습니다.
- 15분쯤 되자 누군가 Google이 허용하는 최대 속도로 이미지를 생성하는 루프를 돌리고 있었습니다.
인디 빌더들이 이해했으면 하는 부분은 바로 이것입니다: API 키를 위해 새 사이트를 스크랩하는 것만이 유일한 작업인 크롤러가 존재합니다. 이들은 최근 SSL 인증서 발급을 모니터링하고, 새 도메인을 방문하며, JavaScript 번들을 파싱하고, 발견된 키를 몇 분 안에 게시합니다. 유명할 필요도, 인기 있는 서브레딧에 있을 필요도 없습니다. 그저 키가 유출된 채로 온라인에만 있으면 됩니다.
그날 오후에 출시한 아키텍처
수정은 외관적인 것이 아니라 구조적인 것이었습니다. API 키가 브라우저로 전송되지 않도록 데이터 흐름을 재작성했으며, 이 아키텍처가 현재 우리가 사용하는 것입니다.
브라우저 (키 없음, 절대)
↓ POST /api/ai/generate { prompt, imageFile }
Next.js API 라우트 (서버 측)
↓ envConfigs.gemini_api_key (서버에 보관)
Google Gemini API세 가지 구체적인 변경 사항:
1. API 키를 서버 전용 환경 변수로 이동. NEXT_PUBLIC_GEMINI_API_KEY를 GEMINI_API_KEY로 이름을 변경했습니다. Next.js는 기본적으로 서버 전용 환경 변수를 클라이언트로 전송하는 것을 거부합니다. 이중 안전 장치로: "API_KEY" 또는 "SECRET"이 포함된 환경 변수 이름이 NEXT_PUBLIC_으로 시작하는 경우 앱을 충돌시키는 시작 검사를 추가했습니다.
2. /api/ai/* 프록시 생성. 모든 모델 호출은 단일 서버 측 엔드포인트를 통과합니다. 브라우저는 프롬프트와 사용자가 업로드한 파일을 보내고, 서버는 API 키를 첨부하여 실제 Gemini API 호출을 수행하고 결과만 반환합니다. 당연해 보이지만 이것이 전체 수정 사항입니다.
3. 프록시에 IP당 속도 제한 추가. 키가 안전하더라도 한 사용자가 Free 등급 생성을 스팸으로 보내 크레딧을 소진하는 것을 원하지 않았습니다. 프록시는 Free 등급에서 IP당 분당 10회, Pro+에서 분당 60회를 적용합니다. 이는 원래 사고를 막지는 못했을 것입니다. 공격자들은 IP를 로테이션하고 있었기 때문입니다. 하지만 다음 유형의 남용을 훨씬 어렵게 만듭니다.
전체 코드 변경은 약 180줄 추가, 90줄 제거였습니다. "사이트 오프라인"부터 "새 아키텍처로 사이트 재운영"까지 2시간 48분이 걸렸습니다. 훌륭하지는 않지만, 재앙 수준도 아닙니다.
다음에 다르게 할 점
솔직한 답변은: 유료 서비스에 대한 클라이언트 측 API 호출과 함께 공개 AI 앱을 절대 출시하지 마십시오. "단지 프로토타입"이라도, "MVP 테스트"라도, "친구들을 위한 빠른 데모"라도 안 됩니다. 위협 모델은 악의적인 사용자가 아닙니다. 이미 이 시나리오를 위해 존재하는 자동화된 인프라입니다.
사고에서 얻은 몇 가지 추가 교훈:
번들을 사전 점검하세요. 출시 전에 grep -r "NEXT_PUBLIC_" .next/static/을 실행하고 거기에 무엇이 있는지 확인하십시오. 자격 증명처럼 보이는 것이 있으면 운영 전에 수정하십시오. 저는 이제 이것을 배포 전 git 훅으로 사용합니다.
하드 데일리 캡을 설정하세요. Google Cloud를 사용하면 Gemini API의 일일 달러 금액 상한을 설정할 수 있습니다. 저는 첫 달에 $50, 그 이후에 $200으로 설정했습니다. 한도에 도달하면 API가 오류를 반환하고 이메일을 받게 됩니다. 나쁜 하룻밤에 $5,000까지 가도록 두는 것보다 훨씬 낫습니다.
제품에 가장 저렴한 모델을 사용하세요. 저는 Free 등급 표면에서 Nano Banana 2 4K 편집당 $0.151을 지출하고 있었습니다. 공격 전에도 단위 경제성은 취약했습니다. 이제 Free 등급 생성을 편집당 $0.045인 Nano Banana 2K로 라우팅하고 Pro+용으로 4K를 유지합니다. 3.4배의 비용 절감으로 $84 청구서가 $25에 가까워졌을 것입니다.
프롬프트 길이를 제한하세요. 남용자 중 한 명은 Gemini가 토큰당 청구하는 임베디드 이미지 데이터와 함께 거대한 프롬프트를 보내고 있었습니다. 프록시는 이제 들어오는 프롬프트 크기를 50KB로 하드 캡합니다. 합법적인 사용자가 이 제한에 부딪힌 적은 없지만, 남용자는 첫 번째 요청에서 부딪힙니다.
증거
제가 지어낸 이야기가 아니라는 것을 확인하고 싶은 분들을 위해: 4월 28일자 제 Gemini API 콘솔은 14:42부터 15:08까지 411개의 요청을 보여주며, 지출 보고서는 이미지 생성 비용으로 $83.96, 입력 토큰 비용으로 $0.34를 보여줍니다. 그날 저녁 Google Cloud 지원팀에 이메일을 보냈지만 환불을 거부당했습니다. 충분히 타당합니다. 키는 합법적으로 제 것이었고 사용을 승인했으니까요. 그들은 제가 이미 출시한 것과 똑같은 아키텍처를 제안했습니다.
핵심 내용은 방 건너편에서도 읽을 수 있을 만큼 크게 씁니다:
AI API 키는 베어러 자격 증명입니다. 서버에 있어야 합니다. 다른 곳에는 절대 있어서는 안 됩니다.
이 글에서 단 한 가지만 얻어가신다면, 이것을 얻어가시길 바랍니다.
다음 읽을거리
— Lena
