AI 코딩 보안 2026.11.10 · 조회 11

AI 코딩 도구 '다시 묻지 않기' 허용 목록에 남은 비밀번호·키: 깃허브 공개 사본 1,122건, 확인법과 대응 순서 [2026]

이번 조사 대상이 된 한 AI 코딩 도구에서는 명령 실행을 '다시 묻지 않기'로 승인하면 그 명령이 로컬 설정 파일의 허용 목록에 저장된다. 명령에 적은 비밀번호와 토큰까지 평문으로 남고, 이 파일이 커밋돼 깃허브 공개 사본 104,185개 중 1,122개에서 실제 비밀값이 나왔다(Apiiro 조사). 내 저장소 확인법과 키 폐기가 먼저, 기록 정리는 그다음인 순서를 정리했다.

S
보안 전문 기업 SENTRIX 레드팀
해킹대회 수상 · 보안 실무 10년 · 실전 진단 경험을 바탕으로 작성합니다.
웹사이트를 운영하고 있다면
보안 헤더·인증서·공개 노출부터 무료로 점검하세요. 결과 링크는 이메일로 받습니다. 로그인 이후 권한·업무 로직은 별도 검증이 필요합니다.

다음 화면에서 이메일과 점검 권한을 확인합니다. 회원가입·카드 없이 무료 요약을 받고, 상세 리포트는 선택 구매합니다.

AI 코딩 도구 허용 목록 파일의 비밀값 노출은, 승인한 명령을 그대로 저장하는 도구(이번 조사 대상)에서 명령 실행을 '다시 묻지 않기'로 승인할 때 그 명령이 프로젝트 안 로컬 설정 파일(예: settings.local.json)에 허용 규칙으로 저장되고, 명령에 적어 둔 비밀번호·토큰·키까지 평문으로 남은 이 파일이 공개 저장소에 함께 커밋되어 누구나 읽을 수 있게 되는 노출이다.
보안 업체 Apiiro의 조사(2026년 11월 5일 공개, 11월 7일 보도)에서 깃허브 공개 저장소 103,617곳의 이 파일 사본 104,185개를 살펴보니 1,122개(1.1%)에 실제 비밀값이 들어 있었다. 서로 다른 비밀값 1,379개 가운데 데이터베이스 비밀번호가 379개, SSH 로그인 43개, 깃허브·깃랩·npm 토큰 42개, AWS 키 23개였다. 연구진은 키 형식에 맞거나 공개 호스트 옆에 적힌 값만 '실제 비밀값'으로 셌고 유효한지는 시험하지 않았으니, 전부가 지금도 쓸 수 있는 값이라는 뜻은 아니다. 그래도 이 노출은 공격 기법이 아니라, 비밀값을 명령줄에 직접 적는 습관과 내 PC에만 있어야 할 파일이 저장소에 같이 커밋되는 상황이 겹쳐서 생긴다. 도구 제작사는 지금까지 비밀값을 걸러내는 처리를 하지 않았고 막을 방법을 검토 중이라고 밝혔으니, 당분간은 쓰는 쪽에서 막아야 한다.
이 글은 자주 발생하는 실수 패턴, 점검·조치 절차, 자가진단을 정리했다.

한눈에 보는 핵심

다시 묻지 않기를 누르면 무엇이 어디에 저장되나요?

이번 조사 대상 도구의 공식 문서는 명령 실행을 '다시 묻지 않기'로 승인하면 그 승인이 프로젝트 안 설정 폴더의 settings.local.json 에 허용 규칙으로 저장된다고 설명합니다. 연구 보고서에 따르면 규칙이 명령 전체 문자열이면 그 안에 적은 비밀번호나 토큰도 평문으로 같이 남습니다. 도구가 npm run 같은 앞부분 패턴을 규칙으로 제안하는 경우도 있어서 모든 승인이 위험한 것은 아니지만, 비밀값이 든 명령을 승인하면 그 값이 규칙에 들어갈 수 있습니다.

내 PC에만 있는 파일이 어떻게 공개 저장소에 올라가나요?

도구는 이 파일을 처음 쓸 때 내 PC의 전역 git 제외 목록에 넣어 커밋에서 빠지게 합니다(공식 문서). 문제는 그 설정이 내 PC 한 곳에만 있다는 점입니다. 연구진은 나중에 저장소가 된 폴더, 다른 PC에서 받은 clone, 이미 커밋된 파일은 이 장치가 덮지 못한다고 짚었습니다. 비밀값이 나온 저장소 1,105곳을 추적한 결과 539곳에서는 AI 도구가 공동 저자로 기록된 커밋이 이 파일을 바꿨습니다.

파일을 지우고 다시 커밋하면 끝인가요?

아닙니다. 파일을 지워도 과거 커밋과 포크, 다른 사람의 clone에 값이 그대로 남기 때문입니다. GitHub 문서도 비밀값이면 첫 단계는 폐기와 재발급이라고 하고, 폐기하면 그 값으로 접근할 수 없으니 그것만으로 충분할 수도 있다고 설명합니다. 기록 정리는 그다음입니다.

이미 비밀 탐지 도구를 쓰고 있는데 안 잡히나요?

놓칠 수 있습니다. 연구에서 비밀값이 든 1,122개 파일 가운데 gitleaks는 564개, TruffleHog는 643개를 잡았고 215개는 둘 다 잡지 못했습니다. 키 형식을 찾는 도구는 PGPASSWORD= 뒤에 적힌 평범한 단어를 비밀번호로 보지 않기 때문입니다. 쓰는 도구에 가짜 값 한 줄을 넣어 시험하고, 못 잡으면 사용자 정의 규칙을 추가해야 합니다.

자주 발생하는 실수 패턴

#패턴위험도
1스테이징 서버 로그인 조사나 마이그레이션을 시킬 때 도구가 제안한 명령에 비밀번호·토큰이 그대로 들어가는데(PGPASSWORD=값, mysql -p값, sshpass -p, Bearer 헤더), 그 명령을 '다시 묻지 않기'로 승인치명
2내 PC의 전역 제외 설정에만 기대고 저장소 .gitignore 에는 규칙이 없음(나중에 저장소가 된 폴더, 다른 PC에서 받은 clone, 이미 커밋된 파일은 막히지 않는다)높음
3파일만 지우고 키는 그대로 둠(파일을 지워도 이전 버전에 든 비밀값 734개가 509개 저장소 기록에 남아 있었다)치명
4커밋을 AI 도구에 맡기고 커밋 대상 파일 목록을 확인하지 않음(비밀값이 나온 저장소 1,105곳을 추적하니 539곳은 AI 공동 저자 커밋이 이 파일을 바꿨다)높음
5편집기 확장의 승인 창에서 저장 위치로 팀 공유 설정 파일을 고름(저장소에 올리는 파일이라 규칙이 팀 전체에 배포되고, 조사한 공유 설정 76,011개 중 298개에 비밀값이 있었다)높음
6키 형식만 보는 비밀 탐지 도구를 믿음(비밀값이 든 1,122개 중 215개는 gitleaks와 TruffleHog 둘 다 놓쳤다)높음

재발 방지 설정: .gitignore, 커밋 전 검사, 기록 정리

# 1) 저장소 자체 .gitignore 에 넣는다. 도구가 넣어 주는 전역 무시는 내 PC 한 곳에만 있다
cat >> .gitignore <<'EOF'
# AI 코딩 도구의 개인용 허용 목록(승인한 명령이 그대로 저장됨)
**/settings.local.json
EOF

# 2) 이미 추적 중이면 추적만 끊는다(파일은 디스크에 남는다). 경로는 자가진단 1단계 결과
git rm --cached -- '<추적 중인 경로>'
git add -- .gitignore
git status --short    # 삭제(D)와 .gitignore 변경(M)만 있는지 본다
git commit -m "chore: 로컬 허용 목록 파일 추적 해제"

# 3) 명령줄 비밀번호를 잡는 사용자 정의 규칙. 기본 규칙은 키 형식 위주라 놓친다
cat > .gitleaks.toml <<'EOF'
[extend]
useDefault = true

[[rules]]
id = "cmdline-password"
description = "명령줄에 직접 적은 비밀번호"
regex = '''(?:PGPASSWORD=|sshpass\s+-p\s*|mysql(?:\s+[^\n|]*?)?\s-p)[\\"']{0,2}([^\s$"'|\\]{4,})'''
secretGroup = 1
keywords = ["pgpassword", "sshpass", "mysql"]
EOF

# 4) 커밋 전 검사 hook (기존 hook 이 있으면 덮어쓰니 내용을 합친다). gitleaks 가 있으면 함께 돌린다
cat > .git/hooks/pre-commit <<'EOF'
#!/bin/sh
if git diff --cached --name-only --diff-filter=ACMR | grep -qE '(^|/)settings\.local\.json$'; then
  echo "차단: 로컬 허용 목록 파일이 커밋 대상에 있다 (git restore --staged 로 뺀다)"
  exit 1
fi
if command -v gitleaks >/dev/null 2>&1; then
  gitleaks git --pre-commit --redact --staged --verbose
fi
EOF
chmod +x .git/hooks/pre-commit

# 5) 이미 올라갔다면: 키를 먼저 폐기한 뒤에만. git-filter-repo 2.47 이상
#    마지막 push 는 원격의 브랜치·태그를 이 clone 기준으로 덮어쓰고, 여기 없는 원격 ref 는 지운다.
#    팀에 알려 푸시를 멈춘 뒤 괄호까지 통째로 붙여 넣는다. 중간에 하나라도 실패하면 거기서 멈춘다.
(
  set -e
  git clone --mirror <저장소 주소> ../backup-before-cleanup.git    # 되돌릴 때 쓰는 백업
  git clone <저장소 주소> ../cleanup
  cd ../cleanup
  git-filter-repo --sensitive-data-removal --invert-paths --path-glob '*settings.local.json'
  git remote get-url origin >/dev/null 2>&1 || git remote add origin <저장소 주소>
  git remote -v
  git push --force --mirror --dry-run origin    # 바뀌거나 지워질 ref 목록을 먼저 본다
  read -r -p '원격 주소와 ref 목록이 맞으면 yes 입력: ' ok
  [ "$ok" = yes ]
  git push --force --mirror origin
)

이 파일은 설정이 아니라 승인 기록이라서, 비밀번호가 든 명령을 승인해 규칙이 명령 전체로 저장되면 그 값이 가공 없이 평문으로 남는다. 그래서 막는 순서도 파일이 올라가지 못하게 하고, 비밀값을 명령에 쓰지 않게 하고, 마지막에 탐지하는 순이다.
코드는 bash 기준(Windows는 Git Bash)이고, hook은 저장소에 같이 올라가지 않으니 팀에서는 core.hooksPath 로 공유 폴더를 지정하며, 3번 규칙은 시작점이라 가짜 값 한 줄로 먼저 시험한다(원 조사: Apiiro 보고서).

자가진단

# 저장소 맨 위 폴더에서 실행한다. Git Bash, macOS, Linux 셸 기준이다
# 3)과 4)는 출력에 비밀값이 그대로 보일 수 있으니 화면 공유 중에는 돌리지 않는다

# 1) 지금 추적 중인가 (하위 폴더에 흩어진 사본까지 잡는다)
git ls-files '*settings.local.json'
#    판정: 한 줄이라도 나오면 이미 저장소에 올라가 있다. 비어 있어도 2단계는 한다
#    커밋한 적 없이 디스크에만 있는 사본은 find . -name settings.local.json 으로 찾아 열어 본다

# 2) 지금은 없어도 과거에 올라간 적이 있는가 (모든 브랜치와 태그 기준)
git log --all --oneline -- '*settings.local.json'
#    판정: 커밋이 하나라도 나오면 기록에 남아 있다. 파일을 지웠어도 마찬가지다

# 3) 지금 추적 중인 JSON 에 허용 규칙이 들어 있는가 (팀 공유 설정 파일 포함)
git grep -nE '"(Bash|PowerShell)\(' -- '*.json'
#    판정: 규칙을 눈으로 읽는다. 비밀번호, 토큰, 접속 문자열이 보이면 폐기 대상이다. npm run 처럼 앞부분 패턴뿐이면 정상이다

# 4) 과거에 커밋된 규칙 전체 (이름에 settings 가 든 JSON 만 본다)
git log --all -p --format= -- '*settings*.json' | grep -E '^[+-][[:space:]]*"(Bash|PowerShell)\(' | sort -u
#    판정: 나온 값 전부를 폐기 목록에 적는다. 지금 파일에 없는 값도 포함한다

# 5) 이 저장소가 스스로 막고 있는가
grep -n 'settings.local.json' .gitignore
#    판정: 아무것도 안 나오면 내 PC의 전역 제외 설정에만 기대는 상태다. 다른 PC에서 받은 clone 에서는 막히지 않는다

# 6) 조직 전체 (GitHub CLI 로그인 필요. 기본 브랜치만 검색되고, 로그인한 계정이 볼 수 있는 저장소만 나온다)
gh search code allow --owner <조직> --filename settings.local.json --limit 100
#    판정: 나온 저장소마다 1~4단계를 반복한다. 비어 있어도 과거 기록까지 없다는 뜻은 아니다

위 시그널이 발견되면 결함이다. GitHub 문서: 저장소에서 민감한 데이터 제거(첫 단계는 키 폐기와 재발급)를 함께 참조하라.

조치 절차

순위조치
1파일과 기록에서 찾은 비밀값을 전부 폐기하고 재발급한다. 현재 파일뿐 아니라 과거 버전과 팀 공유 설정에 있던 값까지 포함하고, 노출된 기간의 접속 기록에서 낯선 접근이 없었는지 본다. 기록 정리보다 반드시 먼저 한다
2저장소 .gitignore 에 settings.local.json 규칙을 넣고 git rm --cached 로 추적을 끊는다. 커밋한 적이 없어도 내 PC의 파일을 열어 비밀값이 든 규칙부터 지운다
3키 폐기가 끝난 뒤 기록에서 파일을 제거한다(백업, 팀 푸시 중지, 새 clone, git-filter-repo, 바뀔 ref 확인, 강제 푸시). 강제 푸시는 원격의 다른 사람 작업까지 덮어쓸 수 있으니 백업과 푸시 중지를 먼저 한다. 비밀값이 처음 들어간 커밋부터 이후 커밋의 해시가 모두 바뀌고 포크와 다른 사람의 clone에는 값이 남으니 팀에 알린다. 캐시된 화면과 풀 리퀘스트 참조는 GitHub 지원에 요청하지만, 폐기로 위험이 줄어든 경우에는 받아주지 않을 수 있다
4명령에 비밀값을 쓰지 않는다. PostgreSQL 은 비밀번호 파일(.pgpass, Windows는 pgpass.conf), MySQL 은 옵션 파일이나 mysql_config_editor, SSH 는 키 인증을 쓰고, API 토큰은 비밀 관리 도구가 실행 시점에 환경변수로 넣게 해서 명령에는 $변수 이름만 적는다. PGPASSWORD 도 공식 문서가 권장하지 않는다. AI 도구 대화창에도 비밀값을 붙여넣지 않는다
5'다시 묻지 않기'는 비밀값이 끼지 않는 명령의 앞부분 패턴(예: npm run 으로 시작하는 명령)에만 쓴다. 비밀값이 필요한 명령은 그때마다 한 번만 승인하거나 직접 실행하고, 편집기 확장에서 저장 위치를 고를 수 있어도 팀 공유 설정 파일은 고르지 않는다
6탐지를 겹쳐 둔다. 커밋 전 hook, 저장소의 푸시 보호와 비밀 스캔 설정, 정기 기록 검사다. 도구가 만든 폴더(설정, 대화 기록, 메모 파일)도 .env 와 같이 스캔 대상에 넣고(같은 조사에서 대화 기록 파일 2,523개 중 120개에도 비밀값이 있었다), 쓰는 도구가 명령 안 비밀번호를 잡는지 가짜 값 한 줄로 시험한다

지금 해야 할 것

실무자: 저장소와 기록은 위 명령으로 직접 확인하고 키 폐기부터 하라. CodeScan 무료 스캔은 저장소를 보지 않고, 배포한 사이트에서 /.env 와 /.git 폴더가 웹에 열려 있는지만 확인한다(.git 이 열려 있으면 기록에 남은 파일도 내려받을 수 있다).
CISO·임원: 조직 저장소와 설정에 노출된 키 점검은 SENTRIX 보안 상담으로.

관련 글

내 사이트도 점검해보세요

URL 입력, 결과 링크는 메일로 받는 무료 점검. 회원가입·카드 불필요.

AI 코딩 보안 체크리스트 받기

AI 코딩 보안 체크리스트(PDF)를 무료로 받아보세요.

코드 자체가 걱정이라면 : 소스코드 보안 분석(SAST)이 ZIP 업로드만으로 취약점 위치와 수정 코드를 찾아드립니다.
이런 글을 계속 받아보려면 뉴스레터 구독 · RSS 피드