뉴스 AI 뉴스

Claude Code '다시 묻지 않기'는 안전할까, 공개 깃허브 허락 목록 1,122개에 비밀번호가 들어 있었다

2026년 11월 08일 34회 7시간 전
Claude Code '다시 묻지 않기'는 안전할까, 공개 깃허브 허락 목록 1,122개에 비밀번호가 들어 있었다

Claude Code가 셸 명령을 실행해도 되는지 물을 때 "예, 다시 묻지 않기"를 누르면, 그 명령이 글자 그대로 .claude/settings.local.json에 저장됩니다. 명령 안에 데이터베이스 비밀번호나 API 토큰이 들어 있었다면 그 값도 평문으로 남습니다. 보안 회사 Apiiro가 공개 깃허브를 조사해 보니 이 파일 104,185개 중 1,122개에 실제로 쓰이는 모양의 비밀 값이 들어 있었습니다.

  • Claude Code의 저장 방식에서 생기는 일입니다. 모델이 무엇이든 같습니다. Apiiro는 Opus 5.5와 Fable 5.1에서도 재현했다고 적었습니다.
  • Claude Code는 이 파일을 PC의 전역 git 무시 목록에 넣어 둡니다. 하지만 저장소의 .gitignore에는 넣지 않아서, 다른 PC에서 열거나 일부러 추가하면 올라갑니다. 이 블로그 PC에서 가짜 값으로 직접 확인했습니다.
  • 비밀 값 탐지 도구도 다 잡지 못했습니다. 1,122개 중 215개는 gitleaks와 TruffleHog 둘 다 놓쳤습니다. PGPASSWORD=나 sshpass -p 뒤에 친 비밀번호는 평범한 낱말처럼 보이기 때문입니다.
  • 이 블로그 PC에서는 비밀 값이 나오지 않았습니다. Claude 설정 파일과 에이전트가 남긴 기록을 점검했습니다. 대신 설치해 둔 편집기 확장 하나에 그 확장을 만든 사람의 settings.local.json이 들어 있었습니다.

이 글은 Apiiro 조사 글(1차 자료), Claude Code 공식 설정 문서, 같은 문제를 다룬 깃허브 이슈 4건, 해커뉴스에 남긴 Claude Code 팀 직원의 답을 읽고 정리했습니다. 점검과 git 시험은 이 블로그의 윈도우 PC에서 직접 했고, 시험에는 가짜 값만 썼습니다. 점검 스크립트는 글 끝에 실었습니다.

이 글의 내용을 20초로 요약한 영상입니다(소리 없음).

요약 여섯 칸. 공개 깃허브의 .claude/settings.local.json 104,185개, 그중 진짜 비밀 값이 든 파일 1,122개, 예전 커밋과 다른 파일까지 합치면 비밀 값 2,539개(저장소 1,994곳), 탐지 도구 둘 다 놓친 파일 215개, 이 블로그 PC의 Claude 설정 파일 6개에 비밀 값처럼 보이는 규칙 0개, 같은 문제 신고 4건 모두 닫힘

Apiiro 조사 숫자와 이 블로그의 점검 결과를 함께 정리했습니다.

무슨 일이 있었나

Apiiro는 11월 1~3일 공개 깃허브에서 .claude/settings.local.json을 모두 찾아 받았습니다. 저장소 103,617곳에서 104,185개가 나왔고, 그중 1,122개(1.1%)에 비밀 값이 있었습니다. 알려진 키 모양(OpenAI, AWS, GitHub, Supabase 등)이거나, 공개 서버 주소와 같은 줄에 있는 비밀번호만 셌습니다. 로컬 주소, 예시 값, 기본 비밀번호는 뺐습니다.

비밀 값 1,379개 중 3분의 1 이상이 데이터베이스에 바로 들어가는 값이었습니다(데이터베이스 비밀번호 379개, Supabase 서비스 키 122개). 공개 서버의 SSH 로그인 43개, GitHub·GitLab·npm 토큰 42개, AWS 키 23개도 있었습니다. Apiiro는 회사 63곳에 11월 2~3일 알렸고, 그중 한 곳은 키가 실제로 살아 있었다고 확인해 주었다고 적었습니다.

파일을 지워도 끝나지 않습니다. 지금은 없는 예전 커밋에서 비밀 값 734개가 더 나왔고, 포크 211개에도 같은 값이 복사돼 있었습니다. 팀이 일부러 공유하는 .claude/settings.json, 저장소에 올린 에이전트 대화 기록, 인계 메모, 기억 파일까지 합치면 저장소 1,994곳에서 비밀 값 2,539개였습니다.

이 글은 해커뉴스 첫 화면에 올라 점수 347점, 댓글 237개를 받았습니다(11월 8일 오후 확인).

비밀 값은 어떻게 이 파일에 들어가나

Claude Code는 셸 명령을 실행하기 전에 물어봅니다. "스테이징 로그인이 왜 안 되는지 봐 줘"라고 하면, Claude는 설정 파일에 있는 비밀번호를 넣어 PGPASSWORD=… psql -h … 같은 명령을 제안합니다. 값이 없으면 명령이 돌아가지 않으니 넣는 것입니다. 여기서 "예, 다시 묻지 않기"를 고르면 Claude Code가 이 명령 글자 그대로를 허락 규칙(allow)으로 저장합니다.

비밀 값이 파일에 남는 다섯 단계. 1 PC 안 .env 나 설정 파일에 비밀 값이 있음. 2 Claude 가 그 값을 넣은 명령을 제안. 3 예, 다시 묻지 않기를 누름. 4 명령 글자 그대로 .claude/settings.local.json 에 평문으로 저장. 5 전역 git 무시 목록이 있지만 다른 PC, 나중에 git init 한 폴더, 이미 커밋한 파일, git add -f, npm 패키지, 압축 파일은 막지 못함

허락 규칙이 명령 글자 그대로라서 생기는 일입니다.

Claude Code 설정 문서에는 이렇게 적혀 있습니다. 이 파일을 처음 쓸 때 **/.claude/settings.local.json 한 줄을 PC의 전역 git 무시 목록(보통 ~/.config/git/ignore)에 넣어서 커밋에 들어가지 않게 한다는 것입니다. 이 블로그 PC에도 그 줄이 있었습니다. 다만 이 목록은 그 PC에만 있습니다. 저장소의 .gitignore에는 아무것도 넣지 않아서 저장소를 따라가지 않습니다.

무시 목록이 있는데 왜 올라갔나

가짜 토큰을 넣은 settings.local.json을 시험 폴더에 두고 git만 돌려 봤습니다. Claude는 돌리지 않았고 커밋도 하지 않았습니다.

가짜 값으로 한 git 시험 표. 이 PC 처럼 전역 무시 목록에 줄이 있으면 파일이 목록에 안 보여 안전. 같은 폴더를 전역 무시 목록이 없는 PC 에서 열면 커밋 대상으로 보이고, git add -A 하면 올라감. 이 PC 에서도 git add -f 로 강제 추가하면 올라감

전역 무시 목록은 그 PC에서만 지켜 줍니다.

Apiiro는 빈틈으로 나중에 git 저장소가 된 폴더, 다른 PC에 받은 사본, 이미 커밋된 파일을 꼽았습니다. 저장소에 올리지 않아도 npm 패키지, 도커 이미지, 압축 파일, 동기화 폴더로 함께 나갈 수 있다고 했습니다. 실제로 Lakera는 4월에 npm 패키지 안에서 같은 파일을 찾았습니다.

또 하나 눈에 띄는 숫자가 있습니다. 비밀 값이 든 저장소 1,105곳 중 539곳에서는 Claude가 공동 작성자로 들어간 커밋이 이 파일을 바꿨습니다. 사람이 아니라 Claude가 git add -A로 함께 올린 경우가 적지 않다는 뜻입니다.

같은 문제는 2025년 8월부터 깃허브에 네 번 신고됐습니다. 이 블로그가 깃허브 API로 확인한 결과는 아래와 같습니다.

이슈올린 날내용상태
#55442025-08-11허락한 명령 속 비밀 값이 평문으로 저장됨2026-01-03 닫힘(계획 없음)
#119822025-11-19로컬 설정 파일을 더 이상 .gitignore 에 넣지 않음2026-03-05 닫힘(계획 없음)
#131062025-12-04토큰이 settings.local.json 에 저장되고 기본으로 무시되지 않음2025-12-08 닫힘(중복)
#912702026-09-01VS Code 확장의 '항상 허락'이 공유 settings.json 에 저장됨2026-11-05 닫힘(계획 없음)

이 표가 뜻하는 것은, 신고는 1년 넘게 이어졌지만 고쳐졌다는 표시가 없다는 점입니다. 마지막 이슈의 내용은 설정 문서에도 있습니다. VS Code 확장에서는 허락 카드에서 저장할 파일을 고를 수 있고, 팀이 공유하는 .claude/settings.json을 고르면 그 규칙이 저장소에 들어갑니다.

Anthropic은 뭐라고 했나

해커뉴스에서 Claude Code 팀의 Boris라고 밝힌 직원이 답했습니다. 요지는 네 가지입니다.

  • Claude Code는 지금까지 비밀 값을 걸러 내지 않았고, 막을 방법을 찾고 있다. 간단한 비밀 값 검사를 포함해 몇 가지를 시험하는 중이다.
  • 이 파일은 PC에만 두는 파일이고, Claude가 만들 때는 자동으로 git 무시 목록에 넣는다. 이번 경우는 사용자가 직접 만들어 커밋한 것이다.
  • 허락 목록을 하나하나 관리하는 방식보다 자동 모드(auto mode)를 권한다. 자동 모드는 허락 목록을 쓰지 않는다.
  • 어떤 에이전트를 쓰든 깃허브 같은 곳에서 비밀 값 검사(secret scanning)를 켜 두라.

둘째 설명은 Apiiro 숫자와 다릅니다. Apiiro는 Claude가 공동 작성한 커밋이 이 파일을 바꾼 저장소를 539곳으로 셌습니다. 어느 쪽이 많은지는 이 글에서 확인하지 못했습니다. 11월 8일 기준 Claude Code 변경 기록(2.1.319까지)에서 이 문제를 고쳤다는 항목은 찾지 못했습니다.

이 블로그 PC는 괜찮았나

Apiiro가 말한 파일을 이 PC에서 찾아봤습니다. Claude 설정 파일과, 에이전트가 남긴 기록(대화 기록, 기억 파일, 작업 메모)을 읽기만 하는 스크립트로 봤습니다. 비밀 값은 화면에 내지 않고 종류와 개수만 냈습니다.

이 블로그 PC 점검 표. Claude 설정 파일 비밀 값 0개. 편집기 확장 안에 들어 있던 설정 파일 비밀 값 0개. 에이전트 대화 기록·기억 파일·작업 메모 비밀 값 0개. 모양이 걸린 것은 로컬 주소, 예시 주소, 자리 채우기 값, 이 글의 시험용 가짜 토큰뿐

읽기만 하는 스크립트로 점검했습니다.

비밀 값 모양에 걸린 것이 몇 개 있었지만 모두 비밀 값이 아니었습니다. 로컬 주소(localhost, 127.0.0.1)나 예시 주소를 쓴 시험 명령, 프로그램에 넣는 자리 채우기 값, 이 글을 쓰면서 넣은 가짜 토큰이었습니다.

뜻밖의 것은 편집기 확장 폴더였습니다. 설치해 둔 확장 하나에 그 확장을 만든 사람의 .claude/settings.local.json이 들어 있었습니다. 허락 규칙 2개뿐이고 비밀 값은 없었지만, 이 파일이 저장소 밖으로 나가는 길이 실제로 있다는 예입니다. 확장을 만드는 사람이라면 배포 파일에 .claude/ 폴더가 들어가지 않는지 확인해 보세요.

내 PC를 점검하려면

파이썬만 있으면 됩니다. 아래 스크립트를 check_dont_ask.py로 저장하고 돌리면 사용자 폴더 아래의 Claude 설정 파일을 모두 찾아, 비밀 값처럼 보이는 규칙의 수와 git 상태를 알려 줍니다. 아무것도 고치거나 지우지 않습니다.

python check_dont_ask.py
python check_dont_ask.py D:\work E:\src

check_dont_ask.py 를 가짜 토큰이 든 시험 폴더에 돌린 화면. 전역 git 무시 목록에 settings.local.json 이 들어 있음, 설정 파일 2개, 시험 파일은 허락 규칙 1개 중 비밀 값처럼 보이는 규칙 1개(Bearer/Authorization 헤더), git 이 무시함(전역 무시 목록), 저장소 .gitignore 에는 없음. 값은 출력하지 않음

가짜 토큰을 넣은 시험 파일에 돌린 화면입니다.

스크립트는 Apiiro가 놓치기 쉽다고 한 모양(PGPASSWORD=, sshpass -p, mysql -p, Bearer 머리글, 주소 안의 아이디:비밀번호)과 잘 알려진 키 모양을 봅니다. $DB_URL처럼 변수로 넘긴 것은 세지 않습니다. 모양으로 찾는 것이라 놓치는 값도, 잘못 걸리는 값도 있습니다. 걸린 파일은 직접 열어 확인해 주세요.

지금 무엇을 하면 되나

  1. 파일을 열어 봅니다. 작업 폴더마다 .claude/settings.local.json의 allow 목록을 보고, 비밀 값이 든 규칙을 지웁니다.
  2. 들어 있던 비밀 값은 바꿉니다. 커밋한 적이 있다면 파일을 지워도 예전 커밋, 포크, 다른 사람의 사본에 남습니다. 지우는 것보다 바꾸는 것이 먼저입니다.
  3. 저장소에 올라가 있다면 git rm --cached .claude/settings.local.json으로 추적을 끊고, 저장소의 .gitignore에 .claude/settings.local.json 한 줄을 넣습니다. 그래야 다른 PC에서도 막힙니다.
  4. 명령에 비밀 값을 직접 치지 않습니다. $DB_URL처럼 환경 변수로 넘기면 규칙에는 변수 이름만 남습니다.
  5. 허락은 낱개 명령보다 모양으로 합니다. 예를 들어 Bash(npm run test:*)처럼요. 아니면 Anthropic이 권하는 자동 모드를 씁니다.
  6. 깃허브의 비밀 값 검사(secret scanning)를 켭니다. 다만 명령 속 비밀번호는 놓칠 수 있습니다.

앞서 쓴 Claude Code 훅으로 개인정보를 PC에서 먼저 가리기도 도움이 됩니다. 요청과 명령에 들어가기 전에 비밀 값 모양을 가리는 방법입니다. 같은 Claude Code의 개인정보 문제는 User-Agent에 계정 이메일이 들어간 일과 VS Code 확장 변조 소식에서도 다뤘습니다.

이 글의 한계

  • 공개 깃허브의 숫자(104,185개, 1,122개 등)는 Apiiro가 센 값입니다. 이 블로그가 다시 세지는 않았습니다.
  • Claude Code에서 "다시 묻지 않기"를 직접 눌러 재현하지는 않았습니다. 저장 방식은 공식 설정 문서와 Apiiro의 재현으로 확인했고, 이 블로그는 git 동작만 가짜 값으로 시험했습니다.
  • 점검은 이 블로그의 윈도우 PC 한 대(Claude Code 명령줄 2.1.269와 데스크톱 앱)에서 했습니다. 맥과 리눅스는 보지 않았습니다.
  • 점검 스크립트는 모양으로 찾습니다. 키 모양이 아닌 비밀번호를 옵션 없이 쳤다면 놓칠 수 있습니다.
  • 2026년 11월 8일 기준입니다.

자주 묻는 질문

"다시 묻지 않기"를 누른 적이 없으면 괜찮나요?

이 파일은 그 단추를 처음 누를 때 생깁니다. 다만 VS Code 확장에서 공유 파일(settings.json)을 골랐거나, 인계 메모·대화 기록을 저장소에 올렸다면 그쪽에 남았을 수 있습니다. 점검 스크립트로 확인해 보세요.

Claude Code를 쓰지 않고 Cursor나 Copilot만 쓰면요?

Apiiro는 코딩 에이전트 16개의 문서를 비교해, 허락한 명령 전체를 기본으로 프로젝트 파일에 쓰는 것은 Claude Code뿐이라고 적었습니다. 그래도 저장소에 올린 VS Code Copilot 자동 승인 설정 173개를 봐서 9개 저장소에서, 에이전트 대화 기록 2,523개를 봐서 87개 저장소에서 비밀 값이 나왔다고 했습니다.

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

아닙니다. 예전 커밋과 포크에 남습니다. 비밀 값을 먼저 바꾸고, 그다음 파일을 정리하세요.

출처

참고: 점검 스크립트 전문

아래가 check_dont_ask.py 전체입니다. 파이썬 3.9 이상, 따로 설치할 것은 없습니다.

r"""Claude Code 의 '다시 묻지 않기'로 저장된 허락 규칙에 비밀 값이 들어 있는지 점검한다(읽기만 한다).

사용:
    python check_dont_ask.py                  # 사용자 폴더 아래를 찾는다
    python check_dont_ask.py D:\work E:\src   # 찾을 폴더를 직접 준다
    python check_dont_ask.py --json out.json  # 결과를 파일로도 남긴다(값은 들어가지 않는다)

무엇을 보나:
  1. 폴더 아래의 모든 .claude/settings.local.json, .claude/settings.json 과 사용자 설정 ~/.claude/settings.json
  2. 파일마다 허락 규칙(permissions.allow) 수, 비밀 값처럼 보이는 규칙 수(종류별)
  3. 그 파일이 git 저장소 안에 있으면: 저장소에 올라가 있는지(추적), git 이 무시하는지, 예전 커밋에 들어간 적이 있는지
  4. 전역 git 무시 목록에 settings.local.json 이 들어 있는지

비밀 값 자체는 화면에도 파일에도 쓰지 않는다. 종류 이름과 개수만 낸다.
이 스크립트는 아무것도 고치거나 지우지 않는다.
"""
import json, os, re, subprocess, sys
from pathlib import Path

sys.stdout.reconfigure(encoding="utf-8")

# 비밀 값처럼 보이는 모양. Apiiro 글이 말한 '명령 속 비밀번호'(PGPASSWORD=, sshpass -p, mysql -p, bearer 헤더)와
# 잘 알려진 키 모양을 함께 본다. $VAR 처럼 변수로 넘긴 것은 비밀 값으로 세지 않는다.
PATTERNS = [
    ("PGPASSWORD=값", r"PGPASSWORD=(?!\$)[^\s\"';&]+"),
    ("sshpass -p 값", r"sshpass\s+-p\s*(?!\$)\S+"),
    ("mysql -p값", r"mysql[^\n]*?\s-p(?!\$)[^\s\"']+"),
    ("Bearer/Authorization 헤더", r"(?i)(bearer\s+(?!\$)[A-Za-z0-9._\-]{12,}|authorization:\s*(?!\$)\S{12,})"),
    ("x-api-key 헤더", r"(?i)x-api-key:\s*(?!\$)\S{12,}"),
    # 로컬·사설 주소(localhost, 127.*, 192.168.* 등)는 뺀다(Apiiro 와 같은 기준)
    ("주소 안 아이디:비밀번호", r"[a-z][a-z0-9+.\-]*://[^\s/:@$]+:(?!\$)[^\s/@]+@(?!localhost|127\.|0\.0\.0\.0|10\.|192\.168\.|host\.docker)"),
    # 코드(tokens = ...)와 헷갈리지 않게 대문자 환경 변수 모양과 명령 옵션만 본다
    ("환경 변수 비밀번호·토큰(NAME=값)", r"\b[A-Z][A-Z0-9_]*(PASSWORD|PASSWD|_PWD|TOKEN|SECRET|API_KEY|APIKEY)[A-Z0-9_]*=(?!\$|%)[^\s\"';&|)`]{6,}"),
    ("--password·--token 옵션 값", r"--(password|passwd|token|api-key|apikey|secret)[= ](?!\$|%|<)[^\s\"'`]{6,}"),
    ("OpenAI·Anthropic 키(sk-)", r"\bsk-(ant-)?[A-Za-z0-9_\-]{20,}"),
    ("GitHub 토큰", r"\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}|\bgithub_pat_[A-Za-z0-9_]{40,}"),
    ("npm 토큰", r"\bnpm_[A-Za-z0-9]{30,}"),
    ("AWS 키", r"\bAKIA[0-9A-Z]{16}\b"),
    ("Google API 키", r"\bAIza[0-9A-Za-z_\-]{30,}"),
    ("Slack 토큰", r"\bxox[abpr]-[A-Za-z0-9\-]{10,}"),
    ("JWT(eyJ…)", r"\beyJ[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}\.[A-Za-z0-9_\-]{10,}"),
    ("개인 키", r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
]
RX = [(n, re.compile(p)) for n, p in PATTERNS]
SKIP = {"node_modules", ".git", "AppData", ".cache", "venv", ".venv", "__pycache__", "site-packages", "$Recycle.Bin"}
HOME = Path.home()


def hide(p: Path) -> str:
    """경로의 사용자 이름을 가린다."""
    s = str(p)
    return s.replace(str(HOME), "~") if s.startswith(str(HOME)) else s


def git(cwd, *a):
    r = subprocess.run(["git", *a], cwd=cwd, capture_output=True, text=True, encoding="utf-8", errors="replace")
    return r.returncode, r.stdout.strip()


def find_files(roots):
    out = []
    for root in roots:
        for d, dirs, files in os.walk(root):
            dirs[:] = [x for x in dirs if x not in SKIP]
            if os.path.basename(d) == ".claude":
                for f in ("settings.local.json", "settings.json"):
                    if f in files:
                        out.append(Path(d) / f)
    return sorted(set(out))


def rules_of(path):
    try:
        j = json.loads(path.read_text(encoding="utf-8"))
    except Exception as e:
        return None, f"읽기 실패({type(e).__name__})"
    perm = j.get("permissions") or {}
    return [r for r in (perm.get("allow") or []) if isinstance(r, str)], None


def check(path):
    rules, err = rules_of(path)
    row = {"file": hide(path), "bytes": path.stat().st_size, "error": err}
    if rules is None:
        return row
    hits = {}
    flagged = 0
    for r in rules:
        kinds = [n for n, rx in RX if rx.search(r)]
        if kinds:
            flagged += 1
            for k in kinds:
                hits[k] = hits.get(k, 0) + 1
    row.update({"rules": len(rules), "flagged_rules": flagged, "kinds": hits,
                "bash_any": sum(1 for r in rules if r.strip() in ("Bash", "Bash(*)", "Bash(:*)")),
                "interpreter_wildcard": sum(1 for r in rules if re.match(r"Bash\((python3?|node|bash|sh|powershell|pwsh|cmd)[ :]?\*?\)", r))})
    code, top = git(path.parent.parent, "rev-parse", "--show-toplevel")
    if code == 0:
        rel = os.path.relpath(path, top).replace("\\", "/")
        row["repo"] = hide(Path(top))
        row["tracked"] = git(top, "ls-files", "--error-unmatch", rel)[0] == 0
        row["ignored"] = git(top, "check-ignore", "-q", rel)[0] == 0
        # check-ignore -v 는 "출처:줄:규칙<탭>경로". 출처가 저장소 안 .gitignore 인지, 전역 무시 목록인지 나눈다
        src = git(top, "check-ignore", "-v", rel)[1].split("\t")[0].rsplit(":", 2)[0].strip('"').replace("\\\\", "/") if row["ignored"] else ""
        row["in_repo_gitignore"] = bool(src) and src.endswith(".gitignore") and ":" not in src
        row["ignored_by"] = ("저장소 " + src) if row["in_repo_gitignore"] else ("전역 무시 목록" if src else "")
        row["in_history"] = bool(git(top, "log", "--all", "--format=%h", "-1", "--", rel)[1])
    return row


def main():
    args = sys.argv[1:]
    out_json = None
    if "--json" in args:
        i = args.index("--json"); out_json = args[i + 1]; del args[i:i + 2]
    roots = [Path(a) for a in args] or [HOME]
    files = find_files(roots)
    user = HOME / ".claude" / "settings.json"
    if user.exists() and user not in files:
        files.insert(0, user)
    rows = [check(f) for f in files]

    # 전역 git 무시 목록
    code, ex = git(HOME, "config", "--global", "core.excludesFile")
    ex_path = Path(os.path.expanduser(ex)) if code == 0 and ex else Path(os.environ.get("XDG_CONFIG_HOME", HOME / ".config")) / "git" / "ignore"
    ex_text = ex_path.read_text(encoding="utf-8", errors="replace") if ex_path.exists() else ""
    glob_ok = "settings.local.json" in ex_text

    print(f"찾은 폴더: {', '.join(hide(r) for r in roots)}")
    print(f"전역 git 무시 목록({hide(ex_path)}): " + ("settings.local.json 이 들어 있음" if glob_ok else "settings.local.json 이 없음"))
    print(f"설정 파일 {len(rows)}개\n")
    for r in rows:
        print(f"- {r['file']}  ({r['bytes']:,}바이트)")
        if r.get("error"):
            print(f"    {r['error']}"); continue
        print(f"    허락 규칙 {r['rules']}개, 비밀 값처럼 보이는 규칙 {r['flagged_rules']}개"
              + (f": " + ", ".join(f"{k} {v}" for k, v in r["kinds"].items()) if r["kinds"] else ""))
        if r["bash_any"]:
            print(f"    [주의] 모든 셸 명령 허락 {r['bash_any']}개")
        if r["interpreter_wildcard"]:
            print(f"    [참고] python·node 같은 실행기 통째 허락 {r['interpreter_wildcard']}개")
        if "repo" in r:
            state = "저장소에 올라가 있음" if r["tracked"] else ("git 이 무시함(" + r["ignored_by"].split(":")[0] + ")" if r["ignored"] else "무시되지 않음(커밋하면 올라감)")
            print(f"    git: {state}" + (", 예전 커밋에 들어간 적 있음" if r["in_history"] else "")
                  + ("" if r.get("in_repo_gitignore") or r["tracked"] or not r["file"].endswith("settings.local.json") else ", 저장소 .gitignore 에는 없음"))
    total = sum(r.get("flagged_rules", 0) for r in rows)
    print(f"\n합계: 비밀 값처럼 보이는 규칙 {total}개. 값은 출력하지 않았습니다. 파일을 직접 열어 해당 규칙을 지우고, 들어 있던 비밀 값은 바꾸세요.")
    if out_json:
        Path(out_json).write_text(json.dumps({"global_exclude": glob_ok, "files": rows}, ensure_ascii=False, indent=1), encoding="utf-8")


if __name__ == "__main__":
    main()

관련 게시글

댓글 0개