하드코딩된 비밀번호(Secret)를 삭제하는 것이 해결책이 되지 않는 이유 (CWE-798)
요약
AI 에디터가 학습 데이터의 영향으로 소스 코드에 실제와 유사한 API 키나 비밀번호를 하드코딩하는 보안 취약점(CWE-798)을 분석합니다. 한 번 커밋된 비밀값은 Git 히스토리에 영구히 남으므로, 환경 변수 사용과 비밀값 스캐너 도입이 필수적입니다.
핵심 포인트
- AI 에디터는 학습 데이터 패턴에 따라 실제 작동하는 형태의 비밀값을 생성할 위험이 있음
- 하드코딩된 비밀값은 Git 커밋 시 히스토리에 영구 기록되어 삭제해도 유출 위험이 지속됨
- 해결책으로 모든 비밀값을 환경 변수에서 로드하고 커밋 전 스캐너를 실행해야 함
요약 (TL;DR)
- AI 에디터들은 실제처럼 보이는 API 키, JWT 비밀값(secrets), DB 비밀번호를 소스 코드에 직접 붙여넣습니다. 이는 해당 방식 그대로 작성된 튜토리얼이 포함된 학습 데이터(training data) 때문입니다.
- 커밋된 파일 내의 하드코딩된 비밀값(hardcoded secret)은 git 히스토리에 기록되는 순간 즉시 활성화된 자격 증명(credential)이 되며, 나중에 이를 교체(rotating)하더라도 이미 노출된 사실을 되돌릴 수는 없습니다.
- 해결책은 모든 비밀값을 환경 변수(environment variable)로부터 읽어오고, 매 커밋 전에 비밀값 스캐너(secrets scanner)를 실행하여 키가 추적되는 파일(tracked file)에 절대 남지 않도록 하는 것입니다.
지난주에 사이드 프로젝트에서 Stripe를 연결해달라고 Cursor에 요청했습니다. Cursor는 약 10초 만에 작동하는 결제(checkout) 코드를 제공했습니다. 하지만 3번 라인에 다음과 같은 코드를 작성했습니다:
const stripe = require('stripe')('sk_live_51H8xY2eZvKYlo2CaBq...');
이것은 제가 커밋하려던 파일에 들어있는 실제 활성화된 비밀 키(secret key)입니다. 플레이스홀더(placeholder)도, TODO도 아닌, 실제 형식을 갖춘 키였습니다. AI는 이를 경고하지 않았습니다. 일반적인 설정 코드처럼 보였는데, 바로 그 점이 위험한 이유입니다.
이것은 제가 AI 생성 코드에서 가장 흔하게 발견하는 문제이며, 개발자가 부주의해서 발생하는 경우는 거의 없습니다. 도구가 학습된 대로 행동하고 있는 것입니다.
취약한 코드 (The Vulnerable Code)
하드코딩된 비밀값(hardcoded secret)이란 런타임(runtime) 시 환경(environment)에서 로드하는 대신 소스 코드에 직접 작성된 모든 자격 증명(credential)을 의미합니다. AI 에디터들은 이를 끊임없이 생성합니다:
// Cursor가 생성한 코드
const stripe = require('stripe')('sk_live_51H8xY2eZvKYlo2C...'); // CWE-798
const JWT_SECRET = 'my-super-secret-key-123';
...
# Python에서의 동일한 패턴
API_KEY = "sk-proj-abc123def456..." # CWE-798
DATABASE_URL = "postgres://admin:hunter2@prod-db:5432/app"
이들 각각은 모두 작동하는 자격 증명입니다. 문제는 단순히 눈에 보인다는 것이 아닙니다. 일단 git commit을 하면, 다음 커밋에서 해당 라인을 삭제하더라도 비밀값은 히스토리에 영구적으로 남는다는 점입니다. 저장소(repo)를 클론(clone)하거나 유출된 미러(mirror)를 읽는 사람은 누구나 이 키를 얻게 됩니다.
왜 이런 일이 계속 발생하는가
AI 에디터가 비밀번호(secret)를 하드코딩하는 이유는 학습 데이터가 압도적으로 예제 코드(example code)로 구성되어 있고, 이 예제 코드가 자체 완결성을 유지하기 위해 가짜 비밀값(fake secrets)을 포함하기 때문입니다. 모든 Stripe 퀵스타트(quickstart), JWT 튜토리얼, '5분 만에 데이터베이스 연결' 같은 블로그 게시물들은 스니펫이 그대로 실행되도록 키를 위한 문자열 리터럴(literal string)을 삽입합니다. 모델은 'API 클라이언트 설정'이라는 것이 '여기에 키 모양의 문자열을 넣는다'는 것을 학습했습니다.
모델은 어떤 문자열이 안전하게 노출될 수 있고, 어떤 문자열이 돈이나 데이터를 보호하는지 개념적으로 알지 못합니다. 생성자에게 sk_live_...와 `
Q: 나중에 커밋(commit)에서 하드코딩된 키를 삭제하면 안전한가요?
A: 아니요. Git은 전체 이력(history)을 유지하므로, 이전 커밋에서 비밀번호(secret)를 여전히 읽을 수 있습니다. 커밋된 비밀번호에 대한 유일하고 안전한 대응책은 이를 교체(rotate)하고, 이전 값을 폐기(revoke)한 뒤, 새로운 값을 발급하는 것입니다.
Q: 커밋되기 전에 이를 어떻게 잡아낼 수 있나요?
A: gitleaks와 같은 비밀번호 스캐너(secrets scanner)를 프리 커밋 훅(pre-commit hook)으로 실행하고, .env 파일을 .gitignore에 포함시키세요. 이 조합을 사용하면 키가 추적되는 파일(tracked file)에 도달하는 것을 원천 차단할 수 있으며, 이는 나중에 이력(history)에서 이를 제거하는 것보다 훨씬 쉽습니다.
저는 이를 위해 SafeWeave를 사용하고 있습니다. 이 도구는 MCP 서버로서 Cursor 및 Claude Code에 연결되어, 코드가 생성되는 즉시 실행되는 gitleaks 기반 스캐너를 사용하여 다음 단계로 넘어가기 전에 하드코딩된 비밀번호를 찾아냅니다. semgrep과 gitleaks를 이용한 기본적인 프리 커밋 훅(pre-commit hook)만으로도 이 포스트에서 다루는 내용의 대부분을 잡아낼 수 있습니다. 중요한 것은 어떤 도구를 사용하든 조기에 발견하는 것입니다.
SafeWeave 블로그에서 원문 전체를 읽어보세요: https://safeweave.dev/blog/why-deleting-a-hardcoded-secret-does-not-fix-it-cwe-798
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기