
CFO의 Claude Code가 숨긴 API 키와 술래잡기를 한 이야기
요약
AI를 활용해 빠르게 서비스를 구축하는 과정에서 발생하는 보안 취약점과 비밀 정보(API Key) 관리의 중요성을 다룹니다. 소스 코드, README, DB 평문 저장 등 잘못된 보안 관행의 위험성을 사례를 통해 경고합니다.
핵심 포인트
- API 키를 소스 코드나 README에 직접 작성하는 것은 매우 위험함
- 비밀 정보는 리포지토리 외부로 격리되어야 함
- DB에 저장하더라도 암호화되지 않은 평문 상태라면 보안 효과가 없음
- 단순히 위치를 옮기는 것이 아니라 진정한 비닉(Secrecy)이 필요함
비엔지니어 임원이 AI에게 전부 맡겨서 2일 만에 실서비스까지 만든 toB SaaS가 있습니다.
고객이 제대로 사용하고 있습니다. 잘 돌아가고 있습니다. 기능 추가도 빠릅니다. 정말 대단합니다.
그래서 그 인프라와 운영을 제가 맡게 되었습니다. '돌아가고 있는 실서비스'의 뒷면을 하나씩 점검해 나가는 역할입니다. 이것이 뭐랄까, 보물찾기라고 해야 할지, 지뢰찾기라고 해야 할지.
오늘은 그중에서도, 비밀 정보(API 키 등)의 위치를 두고 펼쳐진 술래잡기 이야기를 하겠습니다.
결론부터 말씀드리면, 제가 "그거 위험합니다"라고 지적할 때마다, 비밀스러운 은닉 장소가 이사를 반복했습니다. 게다가 이사를 할 때마다 "이걸로 안전해졌죠?"라는 표정을 짓더군요.
숨기는 것과, 비닉(秘匿, Secrecy)하는 것은 다릅니다. 이 기사는 바로 그 한 점에 관한 이야기입니다.
처음에 발견한 것은 아주 정면 승부였습니다.
API 키가, 소스 코드에 직접 적혀 있었습니다.
API_KEY = "(실제 키 문자열)" # ← 이런 것이 본문에 직접 작성됨
vibe coding(=AI에게 느슨하게 지시하여 만들게 하는 스타일)에서 흔히 있는 일입니다. 동작시키는 것이 최우선이기에, 일단 돌아가는 형태——즉 값을 그대로 매립하는 방식——로 귀결되기 쉽습니다. AI도 '돌아가는 코드'는 내놓지만, '비밀을 비밀로서 취급하는 코드'까지는 요청하지 않으면 내놓지 않습니다.
그래서 제가 말했습니다. "키를 소스에 직접 적는 것은 위험합니다. 리포지토리(Repository)가 노출되면 한 번에 유출됩니다"라고.
비엔지니어 임원은 순수해서 바로 고쳐주었습니다. 고쳐주긴 했는데——
고쳤다고 해서 확인해 보니, API 키가 소스에서 사라져 있었습니다. '오, 제법인데'라고 생각했더니,
README에 적혀 있었습니다.
'설치 절차'로서 아주 친절하게 말이죠.
## 환경 구축
1. 리포지토리를 클론(Clone)
2. 다음 키를 설정해 주세요: (실제 키 문자열)
……여러분, 이게 무슨 뜻인지 아시겠습니까? 비밀이 소스에서 절차서로 이동했을 뿐, 리포지토리 안에 있다는 사실은 1mm도 변하지 않았습니다. 오히려 코드 깊숙이 묻혀 있던 것이, 최상단에 놓이는 읽기 자료(=README) 속에서 당당하게 눈에 띄는 위치로 승격되었습니다.
숨바꼭질로 치면, 옷장에서 나온 줄 알았더니 현관에 서 있는 느낌입니다. 발견하기 쉽게 만들어서 어쩌자는 건가요.
본인 입장에서는 '코드에서는 지웠다'라는 성취감이 있을 것입니다. 그 마음은 이해합니다. 하지만 비밀 관리(Secret Management)의 세계에서는, 리포지토리 안에 있는 시점에서 전부 끝난 것입니다. 클론한 사람 모두에게 배포하고 있는 것과 마찬가지니까요.
"README도 안 됩니다. 애초에 리포지토리 안에 두어서는 안 됩니다"라고 다시 한번 설명했습니다.
이번에는 진지하게 고민해 준 모양인지, 다음에 봤을 때는 데이터베이스(Database)에 저장하는 형태가 되어 있었습니다.
이것은——방향성 측면에서는 맞습니다. 리포지토리 밖으로 나왔습니다. 코드에도 README에도 비밀이 남지 않게 되었습니다. 전진입니다. 진심으로 박수를 쳤습니다.
그런데 말입니다.
자세히 들여다보니, 그 값은 평문(Plaintext) 그대로 들어 있었습니다.
암호화도, 마스킹(Masking)도 아무것도 없었습니다. 테이블을 열면 누구나 읽을 수 있는 상태로 키 문자열이 그대로 자리 잡고 있었습니다.
DB에 넣었다고 해서=안전한 것은 아닙니다. 장소를 바꿨을 뿐, 비닉은 아직 한 번도 이루어지지 않았던 것입니다.
소스 안 → 절차서 안 → DB 안(단, 평문). 은닉 장소가 세 번의 여행을 거쳐 겨우 "밖으로 나왔습니다". 하지만 "비닉할 수 있게 되었다"에는 아직 한 걸음도 다가가지 못했습니다. 술래잡기의 술래는 계속해서 같은 장소를 찾고 있습니다.
이 술래잡기 이야기를 범인(=임원)을 비웃는 이야기로 만들고 싶은 게 아닙니다. 오히려, 직관적으로는 매우 자연스러운 움직임입니다.
인간의 직관은 이렇습니다.
보이지 않는 곳에 두면, 안전하다.
옷장, 다락방, 금고인 '척'하는 상자. 보이지 않게 만들면 지켰다는 기분이 듭니다. 이것은 물리 세계에서는 절반 정도 맞습니다.
하지만 소프트웨어의 비밀 관리에서는 이 직관이 어긋납니다. 질문되어야 하는 것은 '보기 힘든가'가 아니라,
거기에 두었을 때, 배포물(리포지토리)에 섞이지 않는가
읽을 수 있는 사람을 정말 필요한 사람으로만 한정할 수 있는가
설령 읽을 수 있다고 하더라도, 내용이 암호화되어 있어 의미가 없는가
하는 점입니다. 장소를 옮기는 것은 '숨기는 것'입니다. 이 조건들을 충족해야 비로소 '비닉하는 것'이 됩니다. 세 곳을 거쳐 다녔더라도 비닉의 조건을 한 번도 충족하지 못했다면, 술래잡기는 끝나지 않습니다.
대략 요약하자면, 이렇습니다.
- 코드에도 리포지토리(Repository)에도 비밀을 두지 않는다(직접 작성, README, 설정 파일 커밋 모두 NG). 코드는 '비밀의 이름'만 알고 있을 뿐, 내용은 모르는 상태로 만든다. - 내용은 실행 환경(Runtime Environment)의 외부에서 주입한다. 환경 변수나 비밀 전용 보관소(=시크릿 매니저 (Secret Manager))를 통해 전달한다. - 어쩔 수 없이 DB에 저장해야 한다면 암호화하여 저장한다. 평문(Plaintext)으로 보관하는 것은 'DB가 유출되면 전부 유출된다'는 것과 동의어다. - 그리고 유출되었을지도 모르는 키는 다시 만든다(로테이션 (Rotation)). 단 한 번이라도 평문으로 리포지토리나 로그에 올라간 키는 이미 오염된 것으로 취급한다.
이 SaaS는 이제 출시 직전입니다. 그래서 비밀 관련 문제는 최우선으로 해결했습니다. 취약성에 대해서도 외부 레드팀 (Red Team)에 소스 코드까지 보여주며 한 차례 확인을 마친 상태입니다. 비밀은 '유출되면 즉시 끝'이기 때문에, 리팩터링 (Refactoring)이나 테스트보다 먼저 손을 대야 했습니다. 순서상으로는 이곳을 가장 먼저 해결했어야 했습니다.
한편, 명백히 리팩터링이 필요한 코드나 대폭 누락된 테스트는 일단 그대로 방치하고 있습니다. 왜냐하면, 돌아가고 있으니까요. 출시 이후에 업데이트를 할 때마다 조금씩 깔끔하게 정리해 나갈 예정입니다. CFO가 앞에서 열심히 기능을 만들고, 그 뒤에서 제가 버그를 청소하며 돌아다니는 것. 그런 분담으로 지금은 어쨌든 출시하는 단계입니다.
여기까지 읽으면 '뭐, 비엔지니어가 만들었으니 그럴 수도 있겠네'라고 생각하시겠죠. 저도 그렇게 생각했습니다.
하지만, 이 부분이 이번에 가장 흥미로웠던 점입니다.
이 임원은 최상위 플랜의 AI를 항상 풀가동하고 있는 사람입니다. 가장 똑똑하다고 여겨지는 최신 모델(Opus 4.8)을 가장 높은 등급으로 사용하고 있습니다. 상식적으로 생각하면, 이런 초보적인 실수는 결코 일어나지 않을 법한 장비를 갖춘 셈입니다.
그런데 일어났습니다. 비밀이 소스 코드에 직접 작성되고, README로 이사 가고, DB에 평문으로 안착했습니다. 최강의 도구를 가지고 있음에도 말입니다.
이것은 AI의 잘못이 아닙니다. 똑똑한 모델은 '작동하는 코드'를 순식간에 만들어냅니다. 하지만 무엇을 비밀로 취급해야 하고, 어디에 두어야 하는가는 사용자가 요구해야만 충족됩니다. 요구하지 않으면, 똑똑한 상태 그대로 비밀을 평문으로 DB에 넣는 일을 완벽하게 수행해 줍니다.
즉——
가장 똑똑한 모델을 사용하더라도, 사용자가 '무엇을 지켜야 하는지' 모른다면 그 똑똑함은 안전함으로 변환되지 않는다.
도구의 성능과 완성된 결과물의 안전성은 별개의 축입니다. 칼이 잘 드는 것과 손가락을 베이지 않는 것이 별개의 문제인 것과 같습니다.
'만드는 것'은 빨라졌지만, '비밀을 비밀로서 다루는 것'은 별개의 기술이다. AI가 이틀 만에 프로덕션을 만들 수 있는 시대라도, 이 부분은 지시하지 않으면 채워지지 않는다. 빛이 강한 만큼 그림자도 뚜렷하게 나타난다. -
숨기는 장소를 바꾸는 것은 '숨기는 것'이지 '비닉(Obfuscation)하는 것'이 아니다. 장소를 옮기는 것에서 성취감을 느낀다면, 일단 멈춰 서야 한다는 신호다. - 검토하는 쪽의 리뷰는 '작동하는가'가 아니라 **'어떻게 유출될 수 있는가'**를 본다. 잘 돌아가는 프로덕션일수록, 뒤편에 평문 키가 자리 잡고 있는 경우가 많다.
최상위 플랜의 가장 똑똑한 모델을 사용해도, 일어날 일은 일어난다. 도구의 성능과 결과물의 안전성은 별개의 축이다. 똑똑함은 사용자가 '무엇을 지킬 것인가'를 요구해야 비로소 안전함으로 변환된다.
비엔지니어가 프로덕션을 만들 수 있다는 것은 정말 대단한 일입니다. 부정할 생각은 없습니다.
다만, 비밀은 '보이지 않는 곳'이 아니라 '올바른 곳'에 두어야 한다.
벽장에 처박아두고 안심하기 전에, '그거 현관에서 보이지 않나요?'라고 한 번 의심해 보시기 바랍니다. 여러분도 부디 조심하세요.
이 기사는 junueno.dev에 처음 공개한 기사를 전재한 것입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기