Claude Code의 "다시 묻지 않음(Don't Ask Again)" 버튼은 승인한 명령어를 그대로 .claude/settings.local.json에 허용 규칙(allow rule)으로 저장합니다. 그 명령어에 비밀번호나 API 키가 포함되어 있었다면, 그 시크릿이 이제 프로젝트 폴더에 평문으로 저장되는 셈이고, 파일이 커밋되면 전체에 공개됩니다. 보안 회사 Apiiro는 공개 GitHub에서 1,122개의 이런 파일을 찾아냈으며, 그 안에 실제 시크릿 1,379개가 담겨 있었습니다. 이 글에서는 명령어 하나로 자신의 저장소를 점검하고, 정리하며, 재발을 막는 방법을 소개합니다.
무슨 일이 있었나: 수치로 보는 Apiiro 조사 결과
Apiiro는 Claude Code가 개인별 권한 승인 내역을 저장하는 프로젝트별 파일인 .claude/settings.local.json의 복사본을 공개 GitHub에서 검색했습니다. 그 결과 103,617개 저장소에서 104,185개의 복사본을 발견했고, iTnews의 조사 보도에 따르면 그중 1,122개 파일에 실제 시크릿이 최소 1개 이상, 총 1,379개의 고유한 시크릿이 포함되어 있었습니다.
| 시크릿 유형 | 건수 |
|---|---|
| 데이터베이스 비밀번호 | 379 |
| Supabase 서비스 롤(service-role) 키 | 122 |
| 공개 서버용 SSH 로그인 정보 | 43 |
| GitHub, GitLab, npm 토큰 | 42 |
| AWS 키 | 23 |
Apiiro가 검색 범위를 과거 git 버전의 파일, 공유되는 .claude/settings.json, 저장된 에이전트 트랜스크립트까지 넓히자, 시크릿 수는 1,994개 저장소에서 총 2,539개로 늘어났습니다. Apiiro는 해당 자격 증명이 아직 유효한지는 테스트하지 않았지만, 통지를 받은 한 조직은 키가 실제로 활성 상태라고 확인했습니다.
스캐너 수치가 가장 우려되는 부분입니다. 실제 시크릿이 포함된 1,122개 파일 중 TruffleHog는 643개를, gitleaks는 564개를 탐지했습니다. 215개 파일은 두 도구 모두를 통과했습니다. 따라서 "우리는 시크릿 스캐너를 돌린다"는 것이 안전하다는 증거가 되지 않습니다.
이 이야기는 Hacker News에서 582포인트로 상위에 올랐습니다. Anthropic에서 Claude Code를 이끄는 Boris Cherny는 해당 스레드에서 Claude Code는 지금까지 시크릿을 제거해 온 적이 없으며 팀이 이를 검토 중이라고 답변했습니다. Apiiro의 원본 연구도 읽어보세요.
"다시 묻지 않음"이 시크릿을 파일에 담게 되는 과정
Claude Code가 Bash 명령어를 실행하려면 먼저 승인을 요청합니다. "예, 다시 묻지 않음(Yes, and don't ask again)"을 선택하면 Claude Code는 상시 승인 내용을 allow 규칙으로 .claude/settings.local.json에 기록하는데, 이는 Anthropic의 설정 문서에 설명된 방식입니다. 이 규칙에는 명령어 텍스트가 그대로 기록됩니다. 명령어가 다음과 같았다면:
psql "postgresql://admin:Sup3rS3cret@db.example.com:5432/prod" -c "select count(*) from users"
설정 파일에는 이런 내용이 저장됩니다:
{
"permissions": {
"allow": [
"Bash(psql \"postgresql://admin:Sup3rS3cret@db.example.com:5432/prod\" -c \"select count(*) from users\")"
]
}
}
비밀번호가 규칙에 포함된 이유는 명령어에 있었기 때문입니다. Claude Code는 승인한 내용을 한 글자도 바꾸지 않고 그대로 저장합니다.

GitHub에 공개되는 이유
Claude Code는 이 파일이 git에 포함되지 않도록 노력합니다. 이미 무시되지 않는 저장소에서 처음 .claude/settings.local.json을 작성할 때, 전역 git excludes 파일에 **/.claude/settings.local.json을 추가합니다. 하지만 이 보호에는 빈틈이 있습니다:
- 파일을 직접 수동으로 만든 경우. 문서는 명확하게 말합니다: 파일을 직접 만들었다면
.gitignore에 직접 추가해야 합니다. Cherny는 HN에서 노출된 복사본들은 수동으로 만들어진 후 커밋된 것으로 보인다고 언급했습니다. - 무시 규칙이 한 대의 머신에만 존재하는 경우. 이 규칙은 전역 excludes에 있을 뿐, 저장소의
.gitignore에 있는 것이 아닙니다. 팀원, CI 작업, 또는 폴더를 복사한 새 노트북에는 이 규칙이 상속되지 않습니다. - 규칙이 생기기 전에 강제 추가되었거나 이미 추적 중인 경우.
git add -f는 무시 규칙을 무시하며, 한 번 추적 대상이 된 파일에는 무시 규칙이 더 이상 적용되지 않습니다. - 공유 파일인 경우. VS Code 확장에서는 승인 카드에서 대상 파일을 선택할 수 있으며, 커밋되는
.claude/settings.json도 포함됩니다. 여기에 저장된 규칙은 모든 사람에게 전달됩니다.
명령어 하나로 저장소 점검하기
현재 작업 중인 저장소부터 확인하세요. 다음 명령어는 파일이 현재 추적 중인지, 과거에 커밋된 적이 있는지 알려줍니다:
git ls-files | grep -E '(^|/)\.claude/settings(\.local)?\.json$'
git log --all --name-only --format= | grep -E '\.claude/settings(\.local)?\.json$' | sort -u
첫 번째 줄이 무언가를 출력하면 파일이 현재 트리에 있는 것이고, 두 번째 줄이 무언가를 출력하면 이후 삭제했더라도 히스토리에 남아 있다는 뜻입니다.
폴더 아래의 모든 저장소를 한 번에 훑으려면:
find ~/code -path '*/.claude/settings.local.json' -exec sh -c \
'cd "$(dirname "$1")/.." && git ls-files --error-unmatch .claude/settings.local.json >/dev/null 2>&1 && echo "TRACKED: $PWD"' _ {} \;
GitHub 조직 전체를 조사하려면 GitHub CLI로 파일명 기준 코드 검색을 할 수 있습니다:
gh search code permissions --owner your-org --filename settings.local.json
그 다음, 찾은 파일의 내용을 직접 확인하세요. 스캐너가 유출 파일의 19%를 놓쳤기 때문에, 정상 판정을 믿기보다 규칙을 직접 읽어보세요:
grep -nE 'Bash\(' .claude/settings.local.json | grep -iE 'pass|token|key|secret|://[^/]*:[^@]*@'
user:password@ 형태의 연결 문자열, Authorization: Bearer 헤더, 리터럴 비밀번호가 뒤따르는 -p, 또는 sk-, ghp_, AKIA, eyJ로 시작하는 문자열은 모두 자격 증명입니다. gitleaks를 유일한 수단이 아니라 이중 확인용으로 저장소에 실행하세요.
유출된 설정 파일 해결 방법
순서가 중요합니다. 먼저 키를 교체해야 합니다. 파일을 삭제한다고 해서 이미 포크, 클론, 캐시에 퍼진 복사본이 없어지지는 않기 때문입니다.
- 파일에 한 번이라도 담겼던 모든 자격 증명을 교체합니다.
git log -p --all -- .claude/settings.local.json으로 파일의 전체 히스토리를 확인하고, 포함된 모든 시크릿을 목록화한 뒤 각 제공자에서 무효화하세요. Apiiro의 가이드도 이 단계를 첫 번째로 두고 있습니다. - 파일 추적을 해제하고 저장소 자체에서 무시하도록 설정합니다:
git rm --cached .claude/settings.local.json
echo '.claude/settings.local.json' >> .gitignore
git add .gitignore && git commit -m "Stop tracking Claude Code local settings"
git check-ignore -v .claude/settings.local.json
마지막 명령어는 일치하는 .gitignore 줄을 출력해야 합니다. 저장소 안의 규칙은 모든 클론을 보호합니다.
- 저장소가 공개되었거나 널리 공유되었다면 히스토리를 제거합니다. git-filter-repo는 모든 커밋에서 파일을 제거합니다:
git filter-repo --invert-paths --path .claude/settings.local.json
git push --force --all
이 작업은 모든 사람의 히스토리를 재작성하므로, 먼저 팀에 알리세요. 포크까지는 닿지 않으므로, 이는 정리 작업일 뿐 1단계를 대체하지 않습니다.
- 로컬 파일도 정리합니다.
.claude/settings.local.json을 열어 리터럴 시크릿이 포함된 규칙을 모두 삭제하고, 패턴으로 교체하세요(다음 섹션 참조). - 푸시 보호를 켭니다. GitHub 시크릿 스캐닝 푸시 보호는 알려진 시크릿 형식이 포함된 푸시를 차단합니다. 215개 파일이 놓쳔 것처럼 모든 것을 잡아주지는 않지만, 흔한 토큰 형식은 막아줍니다.
재발 방지하기
해결책은 말로 하면 간단합니다: 에이전트가 실행하는 명령어에 리터럴 시크릿을 절대 넣지 말고, 정확한 명령어 대신 패턴을 승인하는 것입니다.

시크릿은 참조만 하고, 직접 입력하지 마세요. 셸은 변수를 실행 시점에 확장하지만, allow 규칙에는 작성된 텍스트가 그대로 저장됩니다. 따라서 psql "$DATABASE_URL"은 규칙에 비밀번호가 아닌 $DATABASE_URL을 저장합니다:
{
"permissions": {
"allow": ["Bash(psql \"$DATABASE_URL\" *)", "Bash(npm run test *)"],
"deny": ["Read(./.env)", "Read(./.env.*)"]
}
}
deny 규칙은 Anthropic 설정 문서에서 그대로 가져온 것으로, Claude Code가 .env 파일을 읽지 못하게 막아 키가 명령어에 붙여넣어지는 일도 원천 차단합니다.
규칙은 좁게 유지하세요. 연구를 인용한 HN 댓글러들에 따르면 파일의 5%는 모든 셸 명령어를 허용했고, 43%는 Bash(python:*) 같은 인터프리터 규칙을 갖고 있었습니다. 그런 규칙은 에이전트가 Python을 통해 무엇이든 실행할 수 있게 하므로, 우회 경로가 추가된 "전부 허용" 규칙이나 다름없습니다.
자동 모드와 샌드박스를 고려하세요. Cherny는 손으로 작성한 권한 규칙보다 자동 모드를 권했고, Anthropic이 오픈소스 샌드박스를 만들었다고 말했습니다. 여러 댓글러는 자동 모드가 보안 경계가 아니라 분류기라고 지적했으므로, 위의 습관들과 함께 사용하세요.
에이전트를 프로덕션 시크릿에서 멀리 두세요. 가장 안전한 키는 에이전트가 아예 보지 못하는 키입니다. 관련 패턴은 AI 에이전트가 키를 보지 않고 API 키를 사용하는 방법에서, 유출된 AI 키로 공격자가 하는 일은 유출된 AI API 키와 LLMjacking에서 다뤘습니다.
Apidog CLI로 코딩 에이전트가 키를 입력하지 않고 API를 호출하게 하기
키가 승인된 명령어에 들어가는 흔한 경로는 빠른 API 호출입니다: 엔드포인트를 확인하기 위한 curl -H "Authorization: Bearer sk-..." 같은 것들입니다. 이 작업을 대신 Apidog CLI에 맡길 수 있습니다. 키는 Apidog 환경 변수에 저장되고, 테스트 시나리오는 이를 {{API_KEY}}로 참조하며, 에이전트가 실행하는 명령어에는 프로젝트, 시나리오, 환경 ID만 포함됩니다. 저장할 시크릿이 전혀 없는 것입니다.
한 번만 설정하세요. 에이전트를 통하지 않고 직접 터미널에서, 접근 토큰이 승인 과정을 거치지 않도록 합니다:
npm install -g apidog-cli@latest
apidog auth login
npx -y skills add https://apidog.com # pick Claude Code when prompted
그다음 Claude Code에 프롬프트 하나를 주면 됩니다:
List the environments in Apidog project 123456, then run test scenario 7890
against the staging environment and summarise any failed assertions.
The API key is stored in that environment as {{API_KEY}}. Never pass a key,
token or password on the command line, and never use --env-var for secrets.
에이전트는 다음과 같은, "다시 묻지 않음"으로 안심하고 승인할 수 있는 명령어를 실행합니다:
apidog environment list --project 123456
apidog run -t 7890 --project 123456 -e <envId> -r cli,json
솔직한 주의사항 두 가지. 첫째, apidog run은 변수의 공유 값을 사용하는데, 이 값은 Apidog에 동기화되어 프로젝트 멤버에게 보입니다. 따라서 프로덕션 루트 키가 아니라 범위가 제한된 테스트 키를 저장하세요. 값을 로컬 디스크에 두고 싶다면 apidog run은 --variables <path>도 받습니다. 저장소 밖의 파일을 가리키세요. 둘째, --env-var "key=value" 플래그는 명령줄에서 변수를 전달하기 위해 존재하는데, 이것이 바로 유출되는 그 패턴이므로, 에이전트에게 시크릿에는 이 플래그를 쓰지 말라고 알려두세요.
Enterprise 플랜 팀은 Apidog Vault Secrets로 HashiCorp Vault, Azure Key Vault, AWS Secrets Manager에서 시크릿을 가져올 수 있으며, Enterprise SaaS Secret Scanner는 Apidog 요청, 스크립트, 변수에 저장된 키를 표시해 줍니다. GitHub 저장소는 스캔하지 않으므로, 위의 점검도 계속 유지하세요. 전체 설정 방법은 Claude Code에서 Apidog CLI 사용하기와 Apidog에서 환경 및 시크릿 변수 관리하기를 참고하세요. Apidog의 Free 플랜은 최대 4명의 사용자까지 지원합니다.
FAQ
Claude Code의 settings.local.json은 기본적으로 gitignore되나요?
부분적으로 그렇습니다. Claude Code가 git 저장소에서 이 파일을 생성하면 전역 git excludes에 **/.claude/settings.local.json을 추가합니다. 하지만 이는 자신의 머신만 보호할 뿐, 파일을 직접 만들었거나 강제 추가한 경우에는 적용되지 않습니다. 저장소 자체의 .gitignore에도 이 줄을 추가하세요.
파일을 삭제하면 유출된 키도 없어지나요?
아니요. 키는 git 히스토리와 포크, 그리고 공개되어 있던 기간 동안 만들어진 모든 클론에 남아 있습니다. 먼저 키를 교체한 다음, 파일 추적을 해제하고 git-filter-repo로 히스토리를 제거하세요.
gitleaks나 TruffleHog가 이런 유출을 잡아주나요?
신뢰할 수는 없습니다. Apiiro의 표본에서 실제 시크릿이 있는 1,122개 파일 중 215개가 두 도구 모두에서 놓쳤습니다. 스캐너를 돌린 다음, 파일의 Bash(...) 규칙을 직접 읽어보세요.
모델의 문제인가요, Claude Code의 문제인가요?
Apiiro는 Opus 5.5와 Fable 5.1로 이 현상을 재현했으며, 모델이 아닌 Claude Code의 특성이라고 판단했습니다. 승인 기록에는 어떤 모델이 작성했든 명령어 텍스트가 그대로 저장됩니다.
Claude Code에서 명령어를 안전하게 승인하려면 어떻게 하나요?
Bash(npm run test *) 같은 패턴을 승인하고, 시크릿은 $DATABASE_URL 같은 환경 변수로 참조하세요. Read(./.env)와 Read(./.env.*)에 대한 deny 규칙도 추가하세요.
