API 키 유출 경로 7가지 정리 및 .env 검색 대응 절차
요약
본 글은 API 키가 유출될 수 있는 7가지 경로(Git 기록, 프론트엔드 결과물, 로그 등)를 분석하고, 이에 대한 실질적인 대응 방안을 제시합니다. 가장 중요한 대책은 '무효화할 수 있는 체제 구축'과 '유출하지 않는 설계'이며, 커밋 전 스캔 및 Push Protection 결합이 효과적입니다.
핵심 포인트
- API 키 유출 경로는 7가지로 분류되며, 단순히 .env 파일 검색에만 국한되지 않습니다.
- 가장 중요한 대책은 무효화 체계를 갖추고, 웹 루트에 비밀 정보를 두지 않는 설계입니다.
- 공개 리포지토리의 경우, GitHub Secret Scanning 및 Push Protection을 활용해야 합니다.
- 로그나 에러 화면 등에서 디버그 출력을 막는 것이 중요합니다.
이 글의 요점
- API 키 유출 원인은 Git 기록, 프론트엔드 결과물, 공개 설정 오류, 로그, 외부 도구, 서버 공개 파일, 인위적인 공유의 7개 경로로 정리할 수 있으며, ".env를 직접 도난당하는" 경우는 그 일부에 불과합니다.
- 공개 사이트에는
/.env를 찾는 접근이 일상적으로 도착하므로, 웹 루트에 비밀 정보를 두지 않는 설계와, 유출을 전제로 즉시 무효화 절차를 준비해 두는 것이 가장 효과적인 대책입니다. - 대책의 우선순위는 '무효화할 수 있는 체제 → 유출하지 않는 구성 → 탐지의 자동화' 순서입니다. 커밋 전 스캔과 GitHub의 Push Protection을 결합하면, 노력이 적은데도 효과를 볼 수 있습니다.
"API 키 유출"로 검색하는 많은 사람들은 다음 의문을 가지고 있습니다.
- 내 사이트에
/.env로 접근이 오고 있다. 괜찮은가? - 유출된다면 어디가 위험한가?
- 유출되면 무엇을 해야 하는가?
참고한 것은 자사 사이트에 도착한 .env 검색 1,566건을 분석한 Qiita의 글입니다 (https://qiita.com/songchong/items/02672765fe53f911a1a0). 제목에 따르면 공개 사례를 7가지 경로로 나누고 있습니다.
본 기사는 그 소재를 '자신의 환경에서 무엇을 점검할 것인가'라는 시각으로 재구성한 것입니다. 7개 경로의 구분은 필자의 정리이므로, 원 기사의 분류와 일치하지 않을 수 있습니다. 건수 외의 수치는 언급하지 않습니다.
공개 웹 서버에는 /.env 나 /.git/config, /wp-config.php.bak 등을 기계적으로 찾는 접근이 도착합니다. 노리는 것은 설정 파일의 분실입니다.
이것들은 특정 사이트를 노린 공격이 아닙니다. 인터넷 전체를 총망라로 조사하는 자동 스캔의 일종입니다. 접근 로그에 404가 나열되어 있어도, 즉시 침해되었다고 할 수는 없습니다.
위험한 것은 로그 안에 '200'이 반환된 행이 있을 때입니다. 다음 명령어로 확인할 수 있습니다.
# nginx의 접근 로그에서 .env 계열에 대한 접근과 응답 코드를 본다
grep -E '/
ipsi(env|git)' /var/log/nginx/access.log | awk '{print $9}' | sort | uniq -c
200이나 403 외의 것이 섞여 있다면, 그 경로에 파일이 존재할 가능성이 있습니다. 먼저 응답 내용을 확인해야 합니다.
| # | 경로 | 전형적인 예시 | 주요 대책 |
|---|---|---|---|
| 1 | Git 기록 | 오커밋(誤コミット), 나중에 삭제한 키 | Push Protection, 기록 재작성, 무효화 |
| ... | /.env , 백업 파일 | 문서 루트 외부에 두기 | |
| 4 | 로그/에러 화면 | 디버그 출력, 스택 트레이스 | 마스킹, 운영 환경에서 디버그 비활성화 |
| 5 | CI/CD/컨테이너 | 이미지 레이어, 빌드 로그 | 시크릿 기능, 멀티 스테이지 빌드 |
| 6 | 외부 도구/채팅 | 붙여넣은 키, 화면 공유 | 공유 수단의 통일, 단명 토큰 |
| 7 | 의존/제3자 서비스 | 침해된 패키지, 연동 앱 | 권한 최소화, 주기적 로테이션 |
가장 많이 언급되는 경로입니다. 파일을 삭제해도 기록에는 남습니다. 공개 리포지토리라면 누구든 커밋을 추적할 수 있습니다.
GitHub는 알려진 형식의 시크릿을 커밋 시점에 감지하는 기능을 제공합니다 (https://docs.github.com/en/code-security/secret-scanning). Push Protection은 push 시점에서 차단하는 메커니즘입니다.
브라우저에 배포되는 JavaScript에 키가 들어있는 패턴입니다. 환경 변수를 내장(embed)하는 설정 상태로 비밀 키를 넘겨버리는 경우입니다.
예를 들어 Next.js는 NEXT_PUBLIC_ 로 시작하는 변수를 브라우저용으로 공개하는 사양입니다 (https://nextjs.org/docs/app/guides/environment-variables). 이 접두사를 붙이는 순간, 공개 전제가 됩니다. 소스맵을 운영 환경에 두는 경우에도 원본 코드가 읽힐 수 있습니다.
자사 사이트에 도착하는 .env 검색은 이 경로를 노립니다. 문서 루트의 하위에 .env 를 두면, 설정에 따라 URL로부터 가져올 수 있습니다.
대책은 간단합니다. 설정 파일을 Web 루트 외부(Web ルートの外)에 두는 것입니다. 그렇게 할 수 없다면, Web 서버 측에서 점 파일(.dot file)을 거부해야 합니다.
# nginx: 점으로 시작하는 파일 접근 거부
location ~ /\. {
deny all;
...
디버그 모드(デバッグモード)를 그대로 운영 환경에 배포하면, 환경 변수를 포함한 에러 화면이 표시될 수 있습니다. 로그 출력 단계에서 Authorization 헤더를 통째로 내보내는 구현도 같은 문제입니다.
로그 출력이 이루어지는 단계에서 키의 형식을 마스킹(mask)해야 합니다.
Docker 이미지는 레이어 히스토리(層の履歴)를 가집니다. 빌드 중에 COPY .env나 ENV KEY=...와 같이 작성하면, 나중에 지워도 레이어에 남아있습니다. 빌드를 위한 비밀 정보는 BuildKit의 secret 마운트 등을 통해 전달하는 것이 안전합니다.
채팅에 붙여넣기(貼り付け), 화면 공유, AI 어시스턴트에게 입력하는 행위가 해당됩니다. 기술적인 결함이 아니라 운영상의 문제입니다.
공유 수단을 하나로 정하세요. 비밀번호 관리자나 시크릿 관리 서비스로 모으면 경로가 줄어듭니다.
사용 중인 패키지나 연동 앱이 침해되면, 거기서 환경 변수가 읽힐 위험이 있습니다. 스스로 막기 어려운 경로입니다.
그렇기에 키의 권한은 최소화해야 합니다. 침해되어도 피해가 작아지기 때문입니다.
감지하기보다 미리 결정해야 할 것은 만료(失効) 절차입니다. 유출되었다면, 다음 순서로 움직여야 합니다.
- 해당 키를 발행처의 관리 화면에서 무효화한다
- 새로운 키를 발급하고 운영 환경 설정을 교체한다
- 이용 이력을 확인하여 의심스러운 호출이 없는지 조사한다
- 원인 경로를 특정하여 막는다
이력(履歴)에서 파일을 지우는 것은 네 번째 이후의 이야기입니다. 먼저 만료시킵니다. 미리 이력을 수정해도, 이미 복사되었을 가능성은 사라지지 않습니다.
gitleaks는 리포지토리의 시크릿을 감지하는 오픈소스 도구입니다 (https://github.com/gitleaks/gitleaks).
# 리포지토리 전체 이력 검사
gitleaks detect --source . -v
# 커밋 전 훅(コミット前フック)으로, 스테이징된 변경만 검사
...
도입 시에는 먼저 과거 이력에 대해 한 번 실행해 보세요. 오래된 키가 발견될 수 있습니다.
설정을 환경 변수에 두는 생각은 Twelve-Factor App에 정리되어 있습니다 (https://12factor.net/config). 코드와 설정을 분리함으로써, 실수로 커밋할 기회가 줄어듭니다.
더 나아가, .gitignore에 .env를 넣고 대신 .env.example을 커밋합니다. 내용은 더미 값만으로 합니다.
원문 기사와 공식 문서를 비교해 보니 알게 된 점이 있습니다. 1,566건이라는 숫자는 크게 보이지만, 내용은 자동 스캔의 시도 횟수입니다. 중요한 것은 그중 몇 건이 200을 반환했는지였습니다.
자신의 로그로 같은 확인만 해도, 대부분의 사람은 안심할 수 있을 것입니다. 다만, 손으로 모든 경로를 재현하여 테스트한 것은 아닙니다. 경로 4~7은 공개된 해설을 바탕으로 정리한 것입니다.
지금까지의 대책에는 한계도 있습니다.
- 스캐너는 만능이 아닙니다. 독자적인 형식의 키나, 암호화/분할된 문자열은 감지하기 어렵습니다. -
오탐지(誤検知) 운영 비용이 있습니다. 작은 팀에서는 무시 규칙이 늘어나 형해화되는 경우가 있습니다. -
이력 수정은 부작용이 큽니다. 팀 개발에서 강제 푸시(強制 push)를 하면, 다른 사람의 작업을 망가뜨립니다. 실행 전에 반드시 합의를 구해야 합니다. -
개인의 취미 사이트에서는 과도할 수 있습니다. 다만, 결제나 개인 정보에 관련된 키를 다룬다면 규모와 관계없이 필요합니다. -
프론트엔드만으로 완결되는 설계로는 지킬 수 없습니다. 브라우저로 전달된 값은 모두 보인다는 전제로 취급해야 합니다.
오늘 할 수 있는 것은 3가지입니다.
- 자사 사이트의 접근 로그에서
/.env에 대한 응답 코드를 확인한다 - 손안의 주요 리포지토리에서gitleaks detect를 한 번 실행한다 - 사용 중인 API 키의 만료 절차를 발행처별로 메모해 둔다
남은 질문은 하나입니다. 지금의 키를 5분 안에 교체할 수 있습니까?
접근이 왔다고 해서 침해된 것은 아닙니다. 자동 스캔이 전체에 보내는 시도일 뿐입니다. 응답 코드가 404나 403이라면, 파일은 가져가지 못했습니다. 200이 반환되었다면, 즉시 해당 키를 만료시키세요.
웹 서버가 파일을 정적 파일로 배포해 버릴 가능성이 있기 때문입니다. URL을 직접 지정하면 내용이 읽힐 수 있습니다. 설정 파일은 도큐먼트 루트(document root) 외부에 두는 것이 기본입니다.
우선 발급처에서 키를 만료시키고, 새로운 키로 교체해야 합니다. 이력 삭제는 그 이후에 진행하세요. 공개 리포지토리에서는 복사되었을 가능성을 전제로 생각합니다.
안전하지 않습니다. Git은 과거 커밋(commit)을 보존하고 있기 때문에 히스토리에서 추출할 수 있습니다. 히스토리 재작성(rewriting history)은 보조적인 수단이며, 만료시키는 것이 먼저입니다.
공개 전제 하에 발급된 키(도메인 제한이 걸린 공개용 키 등)라면 가능합니다. 비밀키는 둘 수 없습니다. 브라우저로 전달되는 값은 누구나 볼 수 있습니다.
우선 무료로 사용할 수 있는 gitleaks와 GitHub의 Push Protection을 병행하는 것이 좋습니다. 커밋 전과 푸시 시 두 단계에서 막을 수 있습니다. 도입 후에는 오탐지(false positive) 처리 방식을 결정해 두면 지속하기 쉽습니다.
일률적인 정답은 없습니다. 발급처의 권장 사항과 키의 권한 크기에 따라 결정합니다. 권한이 클수록 짧고 빈번하게 운영하는 것이 적합합니다. 우선 만료 절차를 갖추는 것이 먼저입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기