Claude Code 시크릿 관리: .env부터 프로덕션 로테이션까지
Claude Code로 .env, CI/CD 시크릿, 클라우드 키, 로그 마스킹, 권한 경계를 안전하게 다룹니다.
API 키, 데이터베이스 URL, OAuth client secret, 클라우드 배포 자격 증명은 실제 서비스를 만들 때 반드시 필요합니다. 하지만 한 번 잘못 붙여 넣으면 Git 히스토리, CI 로그, 스크린샷, 이슈, README, 블로그 초안에 오래 남습니다. Claude Code에 디버깅, 배포, 문서화를 맡길수록 첫 경계는 분명해야 합니다. Claude Code는 안전한 구조를 만드는 도구이지, 실제 시크릿 값을 보고 저장하는 장소가 아닙니다.
시크릿 관리는 단순히 비밀번호를 숨기는 일이 아닙니다. 설정을 코드에서 분리하고, 로컬/개발/스테이징/프로덕션 값을 나누고, 최소 권한을 적용하고, 앱 시작 시 값을 검증하고, 로그를 마스킹하고, 사고가 나기 전에 키를 교체할 수 있게 만드는 운영 방식입니다. The Twelve-Factor App의 Config 원칙은 여전히 기본입니다. 설정은 저장소에 하드코딩하지 않고 환경에서 주입해야 합니다. 실무에서는 여기에 .gitignore, .env.example, 검증된 config loader, CI/CD secrets, 클라우드 secret store, 로테이션 체크리스트가 더 필요합니다.
flowchart LR
Dev["Local .env"] --> Loader["Validated config loader"]
CI["CI/CD secrets"] --> Deploy["Deployment runtime"]
Store["AWS / GCP / Azure secret store"] --> Deploy
Deploy --> App["Application process"]
App --> Logs["Redacted logs"]
1. 무엇을 어디에 둘지 먼저 정한다
초보자가 자주 하는 실수는 모든 환경 변수를 같은 수준으로 다루는 것입니다. 판단 기준은 간단합니다. 유출되었을 때 누군가가 비용을 발생시키거나, 이메일을 보내거나, 데이터를 읽고 쓰거나, 배포하거나, 사용자를 가장할 수 있다면 시크릿입니다. 공개 base URL, feature flag 이름, OAuth client ID는 보통 단독으로 권한을 주지 않기 때문에 다른 수준으로 다룹니다.
| 사용 사례 | 예시 | 로컬 개발 | 프로덕션 |
|---|---|---|---|
| 이메일/API 연동 | SendGrid API key, Stripe secret key, GitHub token | .env에 테스트 키 사용 | CI/CD secrets 또는 secret store에서 주입 |
| 데이터베이스 | DATABASE_URL, 사용자, 비밀번호 | 로컬 또는 개발 전용 사용자 | 읽기/쓰기 권한 분리와 비밀번호 교체 |
| 클라우드 배포 | AWS/GCP/Azure 자격 증명 | 장기 로컬 키를 피함 | OIDC 또는 배포 전용 role 사용 |
| OAuth/Webhook | OAUTH_CLIENT_SECRET, webhook signing secret | 로컬 앱 등록을 별도 생성 | prod secret은 저장소 밖에 보관 |
핵심은 환경 분리입니다. 프로덕션 Stripe key를 로컬에서 재사용하지 않습니다. 조직 전체 권한이 있는 개인 GitHub token을 CI에 넣지 않습니다. 배포 키에 관리자 권한을 주지 않습니다. 이것이 최소 권한입니다. 작업에 필요한 범위만, 필요한 환경에만, 가능하면 제한된 기간 동안 부여합니다.
2. 저장소에는 값이 아니라 형태를 커밋한다
.env는 로컬 개발에는 편리하지만 커밋하면 안 됩니다. 저장소에는 .env.example을 두고 필요한 변수명, 형식, 어디서 발급받는지, 어떤 용도인지 설명합니다. 신규 멤버 온보딩에서는 환경 변수 관리 가이드와 함께 읽게 하면 반복 질문이 줄어듭니다.
# Local secrets
.env
.env.*
!.env.example
!.env.test.example
# Logs and generated output that may contain tokens
npm-debug.log*
yarn-debug.log*
coverage/
dist/
# .env.example - placeholders only, never real values
NODE_ENV=development
APP_BASE_URL=http://localhost:3000
# Use a local database user, not production credentials.
DATABASE_URL=postgres://app_user:replace-me@localhost:5432/app_dev
# Use test/sandbox keys for local development.
SENDGRID_API_KEY=SG.xxxxxx
STRIPE_SECRET_KEY=sk_test_xxxxxx
GITHUB_TOKEN=ghp_xxxxxx
# OAuth secrets must be separated by environment.
OAUTH_CLIENT_ID=local-client-id
OAUTH_CLIENT_SECRET=replace-me
# Deployment reads these from CI or a cloud secret store.
AWS_REGION=ap-northeast-1
DEPLOY_ROLE_ARN=arn:aws:iam::123456789012:role/app-deploy-dev
Claude Code를 사용할 때는 경계를 문장으로 남깁니다. .env.example, 배포 매니페스트, package scripts, 변수명은 읽어도 되지만 .env는 열지 않고, 실제 값을 대화나 문서에 쓰지 않는다고 명시합니다. 실제 값이 필요하면 사람이 로컬이나 CI 설정 화면에 입력하고, Claude Code에는 변수명만 알려줍니다.
3. Node.js는 시작 시 검증하고 로그는 마스킹한다
필수 시크릿이 없거나 형식이 틀리면 앱이 바로 실패해야 합니다. 다음 Node.js loader는 dotenv와 envalid로 환경을 검증하고, 로그와 헬스체크에 사용할 redactConfig를 제공합니다.
import { config as loadDotenv } from "dotenv";
import { cleanEnv, str, url } from "envalid";
const envFile = process.env.NODE_ENV === "test" ? ".env.test" : ".env";
loadDotenv({ path: envFile });
const secretKeyPattern = /(KEY|TOKEN|SECRET|PASSWORD|DATABASE_URL|PRIVATE)/i;
const env = cleanEnv(process.env, {
NODE_ENV: str({
choices: ["development", "test", "staging", "production"],
default: "development",
}),
APP_BASE_URL: url({ default: "http://localhost:3000" }),
DATABASE_URL: url(),
SENDGRID_API_KEY: str(),
STRIPE_SECRET_KEY: str(),
GITHUB_TOKEN: str(),
OAUTH_CLIENT_ID: str(),
OAUTH_CLIENT_SECRET: str(),
AWS_REGION: str({ default: "ap-northeast-1" }),
DEPLOY_ROLE_ARN: str(),
});
export function appConfig() {
return Object.freeze({
nodeEnv: env.NODE_ENV,
appBaseUrl: env.APP_BASE_URL,
databaseUrl: env.DATABASE_URL,
sendgridApiKey: env.SENDGRID_API_KEY,
stripeSecretKey: env.STRIPE_SECRET_KEY,
githubToken: env.GITHUB_TOKEN,
oauthClientId: env.OAUTH_CLIENT_ID,
oauthClientSecret: env.OAUTH_CLIENT_SECRET,
awsRegion: env.AWS_REGION,
deployRoleArn: env.DEPLOY_ROLE_ARN,
});
}
export function redactValue(key, value) {
if (!secretKeyPattern.test(key)) return value;
if (!value) return "<empty>";
const text = String(value);
if (text.length <= 8) return "<redacted>";
return `${text.slice(0, 4)}...${text.slice(-4)}`;
}
export function redactConfig(config) {
return Object.fromEntries(
Object.entries(config).map(([key, value]) => [key, redactValue(key, value)]),
);
}
if (process.argv[1] === new URL(import.meta.url).pathname) {
console.log(redactConfig(appConfig()));
}
흔한 사고는 장애 대응 중 console.log(process.env)를 넣고 그 CI 로그를 그대로 이슈나 채팅에 붙이는 것입니다. Claude Code에 원본 로그를 요약해 달라고 하면, 그 값이 테스트나 문서에 다시 들어갈 수도 있습니다. 먼저 마스킹하고 질문하세요. 스크린샷도 마찬가지입니다. 토큰이 보이면 업로드 전에 잘라내거나 가립니다.
4. Claude Code에 권한 경계를 명시한다
Claude Code가 필요한 것은 실제 값이 아니라 변수명, 형식, 마스킹된 에러, 권한의 목적입니다. 보안, 배포, 디버깅 작업을 시작할 때는 아래 프롬프트를 붙여 경계를 고정합니다.
You may inspect .env.example, package.json, deployment manifests, and secret names.
Do not open, print, summarize, store, or copy .env, CI/CD secret values, cloud credentials, production dumps, or screenshots containing tokens.
When you need a value, ask me to set it outside chat and confirm only the variable name.
If a secret appears in command output, stop, redact it, and report which file or command exposed it.
Before changing permissions, explain the least-privilege scope and ask for approval.
Do not paste real secrets into prompts, logs, documentation, code comments, tests, tickets, or articles.
명령 실행도 제한합니다. printenv, cat .env, 클라우드 자격 증명을 보여주는 CLI, 전체 CI 로그 덤프, 스크린샷 캡처는 사전 확인이 필요한 작업으로 둡니다. 실제 값이 없어도 .env.example, 타입 정의, 마스킹된 오류, 공식 문서만 있으면 대부분의 수정은 가능합니다.
5. CI/CD secrets와 클라우드 secret store를 나누어 쓴다
현실적인 분담은 로컬은 .env, 파이프라인은 CI/CD secrets, 프로덕션 런타임은 클라우드 secret store입니다. AWS에서는 AWS Secrets Manager, Google Cloud에서는 Secret Manager, Azure에서는 Key Vault를 사용합니다. GitHub 저장소는 secret scanning을 켜서 실수로 올라간 token을 빠르게 찾습니다.
name: deploy
on:
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
env:
NODE_ENV: production
AWS_REGION: ap-northeast-1
steps:
- uses: actions/checkout@v4
- name: Validate required secret names
run: test -n "${{ secrets.DEPLOY_ROLE_ARN }}" && test -n "${{ secrets.DATABASE_URL }}"
- name: Deploy without echoing secrets
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
DEPLOY_ROLE_ARN: ${{ secrets.DEPLOY_ROLE_ARN }}
run: npm run deploy
CI에서 위험한 습관은 “잠깐 echo로 확인하자”입니다. GitHub Actions가 많은 secret을 마스킹하지만, 변형된 문자열, 인코딩된 URL, JSON에 넣은 값, 스크린샷, secret으로 등록되지 않은 값까지 항상 보호하지는 못합니다. Claude Code에는 값이 아니라 변수명과 검증 절차를 출력하게 하세요.
6. 로테이션을 평소 절차로 만든다
시크릿 교체는 사고 후에만 하는 일이 아닙니다. SendGrid, Stripe, GitHub token, DB 비밀번호, OAuth client secret, webhook signing secret, 클라우드 trust policy는 owner, 환경, 사용 서비스, 마지막 교체일, 다음 검토일을 기록합니다.
## Secret rotation checklist
- [ ] Identify owner, environment, consumers, and business impact.
- [ ] Create a new secret with the smallest required scope.
- [ ] Store it in CI/CD secrets or the cloud secret store.
- [ ] Deploy one service or job with the new value.
- [ ] Confirm logs and metrics without printing the secret.
- [ ] Revoke the old secret.
- [ ] Scan Git history, tickets, docs, screenshots, and chat snippets.
- [ ] Record the rotation date and next review date.
Claude Code는 이 체크리스트를 이슈 템플릿이나 Runbook으로 바꾸는 데 유용합니다. 하지만 이전 key나 새 key 값을 주면 안 됩니다. “SENDGRID_API_KEY 참조 위치를 찾고 v2 key 전환 계획을 작성해 달라”처럼 값 없이 요청합니다.
7. 릴리스 전에 실패 사례를 확인한다
다음 중 하나라도 해당하면 배포나 공개를 멈춥니다.
.env또는.env.production이 커밋되었고 노출된 key를 아직 폐기하지 않았다.- 스크린샷, CI 로그, 오류 보고서, 기사 예시에 token, DB URL, OAuth secret이 보인다.
- 배포 전용 권한이면 충분한데 클라우드 key에 관리자 권한이 있다.
- 프로덕션 Stripe, SendGrid, DB, GitHub 자격 증명을 로컬에서 재사용한다.
- OAuth client secret이나 webhook signing secret에 owner와 교체 절차가 없다.
- AI에게 실제 값을 붙여 넣었고, 그 값이 README, 테스트, 이슈, 초안에 다시 들어갔다.
보안 사고는 코드 오류뿐 아니라 운영 구멍에서 옵니다. 보안 감사 체크리스트로 권한을 점검하고, 보안 실패 사례로 리뷰어의 기준을 맞추세요. 서비스 경계는 API 개발 가이드, SendGrid나 트랜잭션 메일은 이메일 자동화 글도 함께 보면 좋습니다.
8. 팀에 단계적으로 도입한다
처음부터 모든 서비스를 secret store로 옮기려 하면 멈춥니다. 한 애플리케이션을 골라 .gitignore, .env.example, config loader, 로그 마스킹, CI/CD secrets, 프로덕션 secret store, 로테이션 기록까지 한 번에 연결합니다. 그다음 SendGrid, Stripe, GitHub token, DATABASE_URL, OAuth client secret, 배포 자격 증명 순서로 확장합니다.
ClaudeCodeLab의 교육과 컨설팅은 실제 저장소 구조를 기준으로 진행하지만 실제 시크릿 값은 받지 않습니다. Claude Code가 볼 수 있는 파일을 정하고, CI/CD secret 경계를 설계하고, GitHub secret scanning을 켜고, AWS/GCP/Azure secret store 이전 계획과 재사용 가능한 프롬프트, Runbook을 남깁니다. 추상적인 강의보다 다음 날 코드 리뷰에서 바로 쓰는 자료가 더 효과적입니다.
정리하면, 시크릿 관리는 문자열을 숨기는 것 이상입니다. 값을 코드에서 분리하고, 환경을 나누고, 시작 시 검증하고, 로그를 마스킹하고, 권한을 좁히고, 정기적으로 교체할 수 있어야 합니다. Claude Code는 이 체계를 만들고 검토하는 데 강력하지만, 시크릿 값을 복사해 두는 장소가 되어서는 안 됩니다.
Masa의 샘플 Node.js 앱에서 실제로 검증한 결과, config loader가 누락된 값을 바로 잡아냈고 redactConfig 덕분에 CI 로그의 DB URL과 API key가 가려졌습니다. 배포 workflow에서도 불필요한 write 권한을 제거했습니다. 다만 오래된 스크린샷에 Stripe 테스트 key가 남아 있어 게시 전에 재발급했습니다. 로그, 이미지, 초안까지 소스 코드처럼 확인해야 한다는 결론입니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Claude Code 권한 세이프티 래더: 통제력을 잃지 않고 allow 넓히기
read-only에서 제한 편집, 검증 명령, deploy 확인까지 권한을 단계적으로 넓히는 방법.
Claude Code Small PR Proof Pack: 작은 PR을 리뷰 가능한 상태로 만드는 증거 세트
Claude Code의 작은 PR에 diff, 검증, 공개 URL, CTA 경로, rollback을 붙이는 실무 체크리스트.
Claude Code 커밋 전 리뷰 게이트: diff, 테스트, 공개 URL, CTA 확인
Claude Code 작업을 커밋하기 전에 diff 범위, build, 공개 URL, Gumroad 링크, 상담 CTA, 테스트 누락과 무관한 파일을 확인하는 방법입니다.