"보안이 중요하다는 말은 계속 듣는데, 대체 어디서 시작하지?" 이것이 인디 개발자를 위한 가장 첫걸음이다. 겁주려는 게 아니라 — 오늘 실천할 수 있는 것만, 순서대로.
왜 시크릿(.env와 API 키)부터인가
실제 사고 대부분은 정교한 공격이 아니라 유출된 시크릿에서 시작된다. 본 사이트가 토대로 삼는 두 사례는 모두 시크릿 유출이었다:
시크릿을 지키면 흔한 사고 대부분을 막는다. 그래서 여기서 시작한다.
.env란 무엇인가 (30초)
.env는 앱의 시크릿 값을 모아둔 파일이다: 데이터베이스 비밀번호, 외부 API 키, 세션을 암호화하는 키. 시크릿들이 한 파일에 함께 있으니, 한 번 유출되면 전부 한꺼번에 노출된다 — 그래서 가장 먼저 지켜야 할 파일이다(→ 용어집: .env란 무엇인가).
시크릿을 두는 곳(.env, 환경 변수, 시크릿 관리 서비스)
시크릿을 두는 곳은 .env만이 아니다. 규모와 용도에 맞춰 고른다.
| 두는 곳 | 알맞은 경우 | 주의할 점 |
|---|---|---|
.env 파일 | 로컬 개발, 작은 서버 한 대 | Git과 공개 디렉터리 바깥에 둔다 |
| 호스팅의 환경 변수 설정(관리 화면에서 입력) | 운영 환경 | 관리 화면에 접근할 수 있는 사람을 제한하고, 빌드 로그에 값이 남지 않게 한다 |
| CI(자동 테스트·배포)의 시크릿 | GitHub Actions 같은 자동화 | CI에서 실행되는 외부 도구도 값을 볼 수 있다. 필요한 권한만 가진 키를 넘긴다 |
| 시크릿 관리 서비스 | 여러 사람, 여러 서버 | 읽기 권한을 최소로 두고 정기적으로 교체한다 |
CI 시크릿에 대해서는 2021년 Codecov 사고가 참고가 된다. CI에서 실행되던 외부 도구가 변조되어, CI 환경 변수에 담긴 시크릿이 빼돌려졌다(→ Codecov 사고).
공개해도 되는 키와 안 되는 키
API 키 중에는 브라우저나 모바일 앱 안에 넣어 배포하도록 만든 것도 있고, 서버 전용인 것도 있다. 브라우저로 보내는 것은 누구나 읽을 수 있다고 생각한다.
| 예 | 공개해도 되나 | 이유 |
|---|---|---|
Stripe 공개 가능 키(pk_로 시작) | 된다 | 문서에 프런트엔드 코드에 넣어도 된다고 나와 있고, 결제 생성 같은 민감한 작업은 할 수 없다 |
Stripe 비밀 키(sk_)나 제한 키(rk_) | 안 된다 | 서버 측 작업을 할 수 있다. 문서는 권한을 좁힌 제한 키를 권한다 |
NEXT_PUBLIC_이 붙은 Next.js 환경 변수 | 공개된다 | 빌드할 때 값이 브라우저용 JavaScript에 그대로 들어간다 |
NEXT_PUBLIC_이 없는 Next.js 환경 변수 | 공개되지 않는다(서버 전용) | 비밀 값은 여기에 둔다 |
망설여지면 그 서비스의 공식 문서에서 클라이언트 측에서 써도 되는 키인지 확인한다. 나와 있지 않다면 공개해서는 안 되는 키로 다룬다.
API 키 유출이 뜻하는 것
API 키는 "서비스가 호출한 사람을 당신으로 취급하게 하는 문자열" 이다. 잘못된 손에 들어가면 피해가 이렇게 퍼진다.
탈취된 키는 흔히 재판매되어 시간차를 두고 악용된다 — "처음엔 아무 일 없다가, 어느 날 갑작스러운 청구서"가 전형적 패턴이다. 그러니 "알아챈 뒤"는 너무 늦다. 핵심은 애초에 유출하지 않는 것이다.
키가 유출됐을 수 있을 때 할 일
코드에서 키를 지워도 키는 멈추지 않는다. GitGuardian의 2025년 보고서에 따르면, 2022년에 공개 리포지토리에서 유출된 시크릿의 70%가 보고서 시점에도 여전히 유효했다. 다음 순서로 대응한다.
- 발급처의 관리 화면에서 키를 폐기하거나 교체한다. 서비스에 유예 기간이 있다면 유출된 키는 "지금 바로" 끝낸다
- 운영 환경에 새 키를 설정하고 앱이 동작하는지 확인한다
- 발급처의 사용 이력과 청구 내역에서 내가 하지 않은 활동이 없는지 확인한다
- 파일과 Git 이력에서 지우는 일은 폐기한 뒤에 해도 된다(지우기만 해서는 이미 사본을 가진 사람을 막지 못한다)
자세한 내용은 GitHub에 공개된 자격 증명 54만 건이 여전히 유효하다에 있다. GitHub는 개인 계정이 공개 리포지토리에 시크릿을 푸시하는 것을 막는 "푸시 보호"를 기본으로 켜 두지만, 모든 종류의 시크릿을 잡아내지는 못한다.
오늘 시작할 네 가지 습관
어려운 것 없다. 이 넷뿐이다.
노출하지 마라
.env나 앱 본체를 웹에서 보이는 디렉터리(웹 루트)에 두지 마라. public/ 만 노출하라. 공유 호스팅용 구체적 절차: 이 가이드.
커밋하지 마라
.env* 를 .gitignore에 추가하고(!.env.example은 허용); 값이 없는 예시만 공유하라. 한 번 커밋되면 히스토리에서 복원되니, 처음부터 빼두라.
유출되면 전부 교체하라
키 하나만이 아니라 — 유출됐을 수 있는 모든 시크릿을 교체하라. 순서: 외부 API / OAuth → 암호화 키 → 메일 → DB. 본 것처럼 행동하라.
스스로 점검하라
가끔 /.env가 외부에서 열리지 않는지 확인하라(아래).
자가 점검(자신이 소유한 도메인에 대해서만)
# 본문이 있는 200이면 노출된 것. 403/404면 일단 괜찮음.
curl -sI https://your-domain/.env | head -1
curl -sI https://your-domain/.git/config | head -1배포할 때마다 이걸 확인하면 배치 실수를 일찍 잡는다.
외부에서 보이는 것 말고도 Git과 빌드 결과물도 확인한다(자기 리포지토리에서 실행).
# .env가 커밋된 적이 있는가? (출력이 있으면 어느 시점에 커밋된 것)
git log --all --oneline -- .env
# 지금 Git이 추적하는 env 계열 파일(.env.example 말고 다른 것이 있으면 확인)
git ls-files | grep -i "env"브라우저로 키가 나가지 않는지 확인하려면, 빌드 결과물 폴더(Next.js라면 .next/static)에서 키의 앞 몇 글자를 검색한다. 공개 전에 코드를 시크릿 유출 스캐너에 붙여 넣어 확인할 수도 있다(어디에도 전송되지 않는다).
흔한 실수 vs 올바른 첫걸음
흔함
- "되니까",
.env를 public 바로 아래 둠 - "나중에 지우지",
.env를 한 번 커밋함 - 악용을 본 키 하나만 교체
올바른 첫걸음
- 앱 본체를 웹 루트 바깥에,
public/만 노출 - 처음부터
.gitignore로.env제외 - 유출되면 환경의 모든 시크릿 교체
다음 단계
여기서부터 다음 주제는 "내 스택의 위험한 기본값"과 "공개된 CVE에 뒤처지지 않는 시스템"이다. 서두르지 말고, 하나씩.
출처
- Next.js, "How to use environment variables in Next.js"(16.x) — nextjs.org
- Stripe Docs, "API keys" — docs.stripe.com
- Laravel 13.x, "Configuration"(Environment File Security) — laravel.com
- GitHub Docs, "About push protection" — docs.github.com
- GitGuardian, "The State of Secrets Sprawl 2025" — blog.gitguardian.com
다음으로 읽기
- 유출된 뒤: GitHub에 노출된 자격 증명 543,699건이 여전히 유효하다: 지우기만 하지 말고 폐기해야 하는 이유
- Gyazo 유출(2026년 9월): 무엇이 유출됐고, 이용자는 오늘 무엇을 해야 하나
- 용어집: .env란 무엇인가 · CVE란 무엇인가 · RCE란 무엇인가
- 방어: .env를 공개 웹에서 떼어놓기 · Next.js CVE 위생
- 사고: 탈취된 API 키가 부정 과금으로 청구된 사례
- 사고: 탈취된 키로 계정이 정지된 사례
- 공급망: npm install 한 번으로 자격 증명이 털릴 때(키가 저장된 환경에서 남의 코드가 실행된다)