OpenAI API 키를 코드에 박아넣고 push했다
Cursor로 챗봇을 만들었다. OpenAI API를 호출하려면 키가 필요하니까 코드에 직접 넣었다.
const response = await fetch("https://api.openai.com/v1/chat/completions", {
headers: {
"Authorization": "Bearer sk-proj-abc123..."
}
});
잘 돌아간다. 기분 좋게 GitHub에 push했다. 그런데 다음 날 아침, OpenAI에서 메일이 왔다. "API 키가 노출되어 비활성화했습니다." 혹시 누군가가 이 키로 이미 요청을 보냈다면? 그 요금은 내 카드로 청구된다.
이건 가상의 시나리오가 아니다. 2024년 한 해 동안 GitHub에서 노출된 API 키와 시크릿이 3,900만 건이다. 봇들이 공개 저장소를 24시간 돌며 키를 긁어간다. push하고 5분 만에 키가 도용된 사례도 보고됐다. 어떤 개발자는 AWS 키가 노출돼 하룻밤 사이에 5만 달러(약 7천만 원)가 청구된 적도 있다.
코드에 키를 넣는 건, 카페에서 신용카드 번호를 큰 소리로 읽는 것과 같다.
코드는 설계도, 비밀번호는 금고에
건물을 지을 때를 생각해보자. 설계도에는 방 배치, 배관 위치, 전기 도면이 들어간다. 그런데 금고 비밀번호까지 설계도에 적어두는 사람은 없다. 설계도는 시공사에 넘기고, 관리사무소에 보관하고, 필요하면 여러 사람이 본다. 비밀번호가 설계도에 적혀 있으면 설계도를 보는 모든 사람이 금고를 열 수 있다.
코드도 마찬가지다. 코드는 설계도고, API 키는 금고 비밀번호다. 설계도(코드)는 GitHub에 올려서 협업하고 버전 관리를 한다. 비밀번호(API 키)는 설계도와 분리해서 따로 보관해야 한다.
이때 비밀번호를 적어두는 메모지가 .env 파일이다.
.env 파일: 내 모니터에만 붙어있는 포스트잇
.env 파일은 프로젝트 루트에 만드는 텍스트 파일이다. 키=값 형식으로 민감한 정보를 적어둔다.
OPENAI_API_KEY=sk-proj-abc123...
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
RESEND_API_KEY=re_xxxx...
이 파일은 내 컴퓨터에만 존재한다. 모니터 옆에 붙여둔 포스트잇 같은 것이다. 포스트잇을 사진 찍어서 단체 카톡방에 올리면 안 되듯, .env 파일을 GitHub에 올리면 안 된다.
코드에서는 process.env로 이 값을 읽는다.
const response = await fetch("https://api.openai.com/v1/chat/completions", {
headers: {
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
}
});
코드에는 키 값 대신 process.env.OPENAI_API_KEY라는 참조만 남는다. 설계도에 "금고 비밀번호는 관리실에 문의"라고 적어놓는 셈이다. 이 코드를 GitHub에 올려도 실제 키는 노출되지 않는다.
process.env가 뭔데?
process.env는 Node.js가 제공하는 객체로, 현재 실행 환경의 환경변수를 담고 있다. 운영체제 수준의 환경변수(PATH, HOME 같은 것)와 .env 파일에 정의한 값이 여기에 들어간다.
Next.js 프로젝트라면 .env.local 파일을 자동으로 읽어서 process.env에 넣어준다. 순수 Node.js 프로젝트라면 dotenv 패키지를 설치해서 코드 시작 부분에 require('dotenv').config()를 호출하면 된다.
.gitignore: 포스트잇을 사진에서 제외하기
.env 파일을 만들었다고 끝이 아니다. Git은 기본적으로 프로젝트의 모든 파일을 추적한다. .env도 예외가 아니다. git add .을 하면 .env까지 같이 올라간다.
이걸 막는 장치가 .gitignore 파일이다. 프로젝트 루트에 .gitignore를 만들고 한 줄을 추가한다.
# 환경변수 파일
.env
.env.local
.env.*
이제 Git이 .env 파일을 무시한다. git add .을 해도 .env는 스테이징에 올라가지 않는다.
Cursor나 v0로 프로젝트를 생성하면 .gitignore가 자동으로 만들어지는 경우가 많다. 하지만 .env가 빠져 있는 경우도 있으니 반드시 확인하자. 배포 전에 .gitignore를 열어서 .env가 포함되어 있는지 한 번만 보면 된다.
.env.example은 왜 만드나?
혼자 작업한다면 필요 없다. 하지만 다른 사람과 협업하거나 나중에 다른 기기에서 작업할 때를 대비해서 .env.example을 만들어두면 편하다.
# .env.example (GitHub에 올려도 됨)
OPENAI_API_KEY=여기에_키를_넣으세요
DATABASE_URL=여기에_DB_URL을_넣으세요
실제 값 대신 플레이스홀더를 넣은 파일이다. "이 프로젝트를 돌리려면 이런 키들이 필요합니다"라는 안내 역할을 한다. 이 파일은 .gitignore에 넣지 않고 GitHub에 같이 올린다.
배포할 때 환경변수는 어디에 넣나
로컬에서는 .env 파일이 있으니 잘 돌아간다. 문제는 배포할 때다. .env 파일은 GitHub에 안 올렸으니(올리면 안 되니), 배포 서버에는 .env가 없다. 앱이 process.env.OPENAI_API_KEY를 읽으려 하면 undefined가 나온다. "로컬에서는 되는데 배포하면 안 돼요"라는 질문의 절반이 여기서 나온다.
배포 플랫폼마다 웹 UI에서 환경변수를 입력하는 곳이 있다. 포스트잇을 내 모니터에서 떼어서 배포 서버의 금고에 넣어주는 과정이다.
Vercel
프로젝트 대시보드에서 Settings > Environment Variables로 들어간다. Key에 OPENAI_API_KEY, Value에 실제 키를 입력한다. Production, Preview, Development 중 어떤 환경에 적용할지 체크하고 저장하면 끝이다. 다음 배포부터 자동으로 process.env에 들어간다.
Cloudflare Pages
Settings > Environment Variables에서 추가한다. Production과 Preview 환경을 따로 설정할 수 있다. Cloudflare Workers를 쓴다면 wrangler secret put OPENAI_API_KEY 명령어로 터미널에서 직접 넣을 수도 있다. Workers에서는 코드에서 env.OPENAI_API_KEY로 접근한다(process.env가 아니라 env 객체인 점이 다르다).
Netlify
Site Settings > Environment Variables에서 입력한다. Vercel과 거의 같은 방식이다.

핵심은 하나다. 로컬에서 .env에 넣었던 값을, 배포 플랫폼 대시보드에서 똑같이 입력해야 한다. 이름이 정확히 일치해야 한다. OPENAI_API_KEY로 써놨는데 플랫폼에 OPENAI_KEY로 입력하면 당연히 안 된다.
실수로 키를 GitHub에 올려버렸다면
이미 push한 뒤에 알아챘다면. 침착하게 순서대로 한다.

1단계: 키를 즉시 무효화한다. 이게 최우선이다. OpenAI 대시보드에 가서 해당 키를 삭제하고 새 키를 발급받는다. AWS, Supabase, Firebase 등 다른 서비스도 마찬가지다. 노출된 키를 죽이는 게 첫 번째다.
2단계: .gitignore에 .env를 추가한다. 아직 안 했다면 지금 한다.
3단계: Git 히스토리에서 .env를 제거한다.
git rm --cached .env
git commit -m "remove .env from tracking"
git push
git rm --cached는 파일을 Git 추적에서만 제거하고 로컬 파일은 그대로 둔다. 이제 .env는 Git이 무시하지만, 과거 커밋 기록에는 여전히 남아 있다. 히스토리까지 완전히 지우려면 git filter-branch나 BFG Repo Cleaner 같은 도구가 필요한데, 초보자에게는 복잡하다. 가장 확실한 조치는 1단계에서 키를 무효화하는 것이다. 키가 죽었으면 히스토리에 남아 있어도 쓸모없다.
4단계: 새 키를 .env에 넣고, 배포 플랫폼에도 업데이트한다.
GitHub가 이런 실수를 잡아주기도 한다. GitHub Secret Scanning이라는 기능이 push된 코드에서 알려진 서비스의 키 패턴을 감지해서 알림을 보낸다. OpenAI나 AWS처럼 파트너십이 있는 서비스는 키가 감지되면 자동으로 비활성화되기도 한다. 하지만 모든 서비스를 커버하지는 않으니 이 기능에 의존하지 말고 애초에 올리지 않는 게 정답이다.
환경변수 체크리스트
프로젝트를 시작할 때, 배포하기 전에, 이것만 확인하자.
.env파일을 만들고 API 키를 여기에 넣었는가?.gitignore에.env가 포함되어 있는가?- 코드에서 키를 직접 쓰지 않고
process.env.키이름으로 참조하는가? - 배포 플랫폼 대시보드에 환경변수를 입력했는가?
.env.example을 만들어서 필요한 변수 목록을 남겼는가?
다섯 가지 중 하나라도 빠지면 사고가 난다. 특히 앞의 세 가지는 프로젝트 시작 직후에 해두면 나중에 신경 쓸 일이 없다.
환경변수 관리는 습관이다
환경변수를 배우는 건 어렵지 않다. .env 파일 하나, .gitignore 한 줄, process.env 한 문장이면 끝이다. 어려운 건 매번 챙기는 습관을 들이는 것이다.
Cursor든 Claude Code든, AI가 코드를 생성해줄 때 간혹 API 키를 하드코딩한 예시를 내놓는 경우가 있다. 그때 "이거 .env로 빼야 하는데"라고 바로 떠올리면 된다. 한 번이라도 키가 노출된 경험이 있는 사람은 절대 잊지 않는다. 그 경험 없이 습관을 만드는 게 이 글의 목적이다.