API 키를 코드에 넣지 않는 이유와 .env 관리
Notion·메모에 키를 모아 두다 생긴 실수와, Git·배포 환경에서 비밀값을 나누는 현실적인 방법을 정리합니다.

API 키는 “프로그램용 비밀번호”입니다. AI, 지도, 결제, 클라우드, 메신저 봇까지 키가 있으면 그 권한으로 호출할 수 있습니다. 그래서 저장소에 평문으로 올리는 건 비밀번호를 이슈에 붙여 넣는 것과 비슷합니다.
저도 편하려고 Notion “API 키” 페이지에 Cloudflare·OpenRouter·Groq·텔레그램 봇 토큰을 한꺼번에 적어 둔 적이 있습니다. 작업할 때는 편한데, 그 페이지가 공유되거나 스크린샷·에이전트 로그에 섞이면 유출 면적이 커집니다. 블로그 글이나 공개 레포에는 절대 실키를 붙이지 않는 것을 원칙으로 바꿨습니다. 예시는 항상 sk-... / cfut_...처럼 마스킹합니다.
코드와 비밀을 나누는 이유
소스는 공유·리뷰·히스토리가 전제입니다. 키는 환경마다 다르고, 유출 시 바로 폐기·재발급할 수 있어야 합니다. 코드에 박아 두면:
- Git 이력에 영원히 남는다
- PR·CI 로그에 찍힐 수 있다
- 개인 저장소가 공개로 바뀌는 순간 사고가 된다
- 개발키와 운영키가 섞여 요금이 나온다
환경 변수로 빼면 코드는 그대로 두고 로컬/프리뷰/프로덕션 값만 바꿉니다.
로컬은 .env, Git에는 안 넣기
프로젝트 루트에 .env를 두고:
OPENROUTER_API_KEY=여기에_로컬용만
CLOUDFLARE_ACCOUNT_ID=계정ID는_공개돼도_되는_경우만
.gitignore에 .env, .env.*, !.env.example을 넣습니다. .env.example에는 키 이름만 남깁니다.
OPENROUTER_API_KEY=
CLOUDFLARE_API_TOKEN=
Astro/Vite처럼 클라이언트에 노출되는 접두사(PUBLIC_ 등)는 공개 값만 넣습니다. AdSense 클라이언트 ID처럼 원래 공개되는 값과, Workers 배포 토큰은 등급이 다릅니다.
배포 쪽
Cloudflare면 대시보드 Secrets / Environment variables, GitHub Actions면 Repository secrets에 넣습니다. 노션 배포 메모에 토큰 전체를 붙여 두고 wrangler deploy만 복사해 쓰던 방식은, 메모 유출 = 계정 유출입니다. 스크립트는 키를 읽기만 하고 출력하지 않게 합니다.
set -a; source .secrets/xxx.env; set +a처럼 로컬에서만 불러오는 패턴은 편하지만, .secrets/도 Git 제외가 필수입니다.
유출됐을 때
- 해당 서비스에서 키 폐기·재발급
- Git에 들어갔다면 히스토리 정리(단순 커밋 삭제로 부족할 수 있음)
- 같은 키가 붙은 봇·Webhook·Workers 재설정
- 노션/슬랙/이슈에 붙은 평문 삭제
OpenRouter·Groq처럼 무료 키가 여러 개면 “어디에 어떤 키가 붙었는지” 표로만 관리하고, 값 자체는 패스워드 매니저에 둡니다. 모델 비용 비교는 OpenRouter로 코딩할 때 모델 비용에 숫자 감각만 적어 두었습니다.
키 관리는 거창한 보안 체계보다, 평문을 한곳에 몰아두지 않는 습관이 먼저입니다. 새 서비스를 붙일 때마다 “이 값이 Git·노션·스크린샷 중 어디에 남는가?”만 물어봐도 사고가 많이 줄어듭니다.