대상: 1인·소규모 개발에서 Git을 쓰며 "실수로 API 키를 커밋하면 어쩌지?" 걱정하는 사람. 공격자 메커니즘은 없다 — 오직 시크릿이 자기 저장소에서 새어 나가지 못하게 막는 장치다.
본 사이트의 견해: .gitignore만으로는 시크릿을 지킬 수 없다
"내 .env는 .gitignore에 있으니 괜찮아"는 흔한 말이지만, 놓치는 경우가 있다. .gitignore는 새 파일이 추적되는 것만 막을 뿐이다. 이미 커밋된 시크릿, 급히 git add -f로 강제 추가한 것, .env.local.bak 같은 예상 밖 이름은 잡지 못한다. 시크릿이 들어가는 경로의 일부만 막는다. 실제로 커밋 내용을 들여다보고 "시크릿처럼 보이는 문자열"을 표시하는 무언가 — 탐지기 — 가 필요하다. 그것이 gitleaks의 역할이다.
왜 "커밋 전"에 막는가
시크릿이 위험한 이유는 Git이 히스토리를 영원히 보관하기 때문이다. 최신 파일에서 지워도 과거 커밋에 남는다. 그리고 push되는 순간 그 히스토리는 다른 머신, 서버, CI 로그로 복사된다.
그러니 시크릿 방어는 사실 유출 후 복구가 아니라 유출 전 예방이다. "공개 디렉터리에 시크릿을 남기지 마라"는 인벤토리(→ 웹루트 인벤토리)와 같은 발상을, 코드에 적용한 것이다.
어느 단계에서 시크릿을 탐지하는가
시크릿이 코드에서 세상으로 가는 경로 위에 두 단계의 점검을 둘 수 있다: 로컬 커밋의 순간, 그리고 공유되기 직전(CI).
코드 속 시크릿
API 키, 키, 토큰
점검 1: pre-commit
커밋 시 로컬에서 탐지 & 중단
점검 2: CI / cron
push 후, 머지/배포 전 탐지
공개 / 공유
이 지점을 지나면 유출로 간주
gitleaks 연결하는 법
먼저 기존 히스토리를 스캔
gitleaks git(v8.19.0 이전 버전에서는 gitleaks detect)으로 저장소 히스토리 전체를 한 번 스캔하라. 과거 커밋에 남은 시크릿을 찾아내는 것이 첫 작업이다. 여기서 나온 것은 아래처럼 폐기가 필요하다고 가정한다.pre-commit 훅으로 점검 1 추가
gitleaks git --pre-commit --staged(예전의 gitleaks protect --staged)를 실행해, 시크릿이 포함된 커밋을 그 자리에서 중단하라. pre-commit 프레임워크(.pre-commit-config.yaml)에 넣어 모두에게 같은 점검이 로컬에서 적용되게 하라.CI / cron으로 점검 2 추가
gitleaks git을 CI 작업으로 — 자체 호스팅이면 주기적 스캔으로 — 돌려 빠져나간 것을 잡아라(의존성 감사와 같은 기계 모니터링 발상)..gitleaks.toml에서 오탐 조정
.gitleaks.toml에서 규칙과 허용 목록을 조정하라. 올바른 실천은 "경고를 끄기"가 아니라 "왜 안전한지 기록하기"다 — 근거 없는 제외는 다음 사고의 원인이 될 수 있다.탐지되면 '폐기'가 먼저, 히스토리 재작성은 나중
커밋되어 push된 시크릿을 발견하면, 최우선은 그 키나 토큰을 폐기하고 재발급(교체)하는 것이다. git filter-repo로 히스토리에서 지우는 것도 중요하지만, 그것은 "더 퍼지는 것을 멈추는" 정리일 뿐이다. 공개 저장소, 포크, CI 로그, 남의 클론에 닿은 시크릿은 회수 불가라고 가정하고 — 먼저 무효화한 다음 히스토리를 정리하라. 순서를 뒤집으면 지우는 동안 악용된다.
.gitignore에 의존
- 새 추적만 막음
- 이미 커밋된 시크릿은 탐지되지 않음
git add -f나 이상한 파일명에 무력- "그거 추가할 생각 없었는데"를 검증 못 함
gitleaks(탐지 + 폐기 실천)
- 작업 트리와 히스토리 내용을 실제로 검사
- 두 단계의 점검으로 막음: pre-commit과 CI
- 탐지 시 폐기까지 이어지는 절차
- 오탐은 기록된 근거와 함께 제외
어떤 명령을 쓰는가(v8.19.0 이후)
gitleaks는 v8.19.0에서 명령을 재편했다. 예전의 detect와 protect는 아직 실행되지만 사용 중단되었고 도움말에도 나오지 않는다. 새로 설정한다면 다음을 쓰라.
| 명령 | 스캔 대상 | 주된 용도 |
|---|---|---|
gitleaks git | Git 커밋 히스토리 | 첫 전체 히스토리 스캔, CI, 정기 스캔 |
gitleaks git --pre-commit --staged | 지금 커밋하려는 변경(스테이징된 것) | pre-commit 훅(공식 훅이 정확히 이것을 실행) |
gitleaks dir | Git에 없는 것을 포함한 폴더와 파일 | 공개 웹 폴더, 백업, 압축을 푼 릴리스 zip |
gitleaks stdin | 표준 입력으로 파이프된 텍스트 | 다른 명령의 출력이나 로그 |
gitleaks는 유출을 하나 이상 찾으면 종료 코드 1, 찾지 못하면 0으로 끝나므로, CI나 cron 작업은 이 종료 코드로 실패 처리할 수 있다. --redact를 붙이면 시크릿 자체가 출력이나 로그에 남지 않는다.
설정 예: pre-commit 훅과 GitHub Actions
pre-commit 프레임워크를 쓴다면 저장소 루트에 다음 파일을 둔다. rev는 추가하는 시점의 최신 릴리스로 지정하라(2026년 10월 기준 v8.30.1).
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaksid는 gitleaks(Go로 빌드) 외에 gitleaks-docker(Docker에서 실행)나 gitleaks-system(머신에 이미 설치된 gitleaks 사용)도 쓸 수 있다. 그다음 각자 자기 머신에서 다음을 실행한다.
pip install pre-commit # install pre-commit itself
pre-commit install # register the hook in this repo (once per clone)
pre-commit run --all-files # check every file once, right after registering
pre-commit autoupdate # run now and then to move rev to a newer releaseGitHub의 CI에는 공식 GitHub Action이 있다. 전체 히스토리가 필요하므로 fetch-depth: 0으로 체크아웃하라.
# .github/workflows/gitleaks.yml
name: gitleaks
on:
pull_request:
push:
schedule:
- cron: "0 4 * * *"
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}조직 계정이 소유한 저장소에서는 이 Action에 라이선스 키(GITLEAKS_LICENSE)가 필요하다. 공식 README에 따르면 키는 gitleaks.io에서 무료로 받을 수 있고, 개인 계정에는 필요 없다. 자체 호스팅 Git 서버나 GitHub 이외의 CI에서는 작업 안에서 gitleaks git --redact를 실행하면 같은 일을 한다.
어디서 점검하는가: 네 곳 비교
어느 점검이든 하나만으로는 빈틈이 남는다. 각각이 무엇을 막고 어디서 새는지 나란히 놓으면, 왜 조합하는지가 보인다.
| 위치 | 막는 시점 | 다루는 범위 | 빈틈과 한계 |
|---|---|---|---|
| pre-commit 훅(로컬) | 커밋 전 | 스테이징된 변경 | git commit --no-verify나 SKIP=gitleaks로 건너뛸 수 있다. 저장소를 클론해도 훅은 설치되지 않는다 |
| CI(GitHub Actions 등) | 머지·배포 전 | push된 커밋과 히스토리 | push는 이미 일어났다. 공개 저장소라면 유출로 간주하라 |
| 정기 스캔(cron) | 나중에 발견 | 저장소 전체 히스토리 | 발견까지 지연이 있다. 새 규칙이 과거 커밋에서 찾는 것을 잡는다 |
| GitHub push protection | GitHub로 push할 때 | GitHub가 지원하는 시크릿 종류 | 이유를 대면 우회할 수 있다. 다른 Git 서버에서는 작동하지 않는다 |
사용자용 GitHub push protection은 공개 저장소로의 push에 기본으로 켜져 있다. 무료 안전망이지만 로컬 커밋은 막지 않고, 지원하지 않는 형식의 키나 직접 조립한 연결 문자열은 통과할 수 있다(자세히: GitHub에 노출된 자격 증명이 여전히 유효한 이유). gitleaks는 그 앞 단계와 GitHub 바깥 전부를 맡는다.
자신의 설정을 확인하는 방법
한 번 설정했다고 돌고 있다는 보장은 없다. 자기 저장소에서 다음을 차례로 확인하라.
gitleaks 버전을 확인한다
gitleaks version을 실행하라. v8.19.0보다 오래된 버전에는 gitleaks git 등 새 명령이 없으니 업데이트하라.
훅이 등록됐는지 확인한다
.git/hooks/pre-commit이 있는지 보라. git config --get core.hooksPath가 다른 폴더를 가리킨다면 그곳을 보라. Git은 클론할 때 훅을 복사하지 않으므로, 새 머신이나 새로 클론한 저장소에서는 pre-commit install을 다시 실행하라.
히스토리 전체를 한 번 스캔한다
저장소 루트에서 gitleaks git --redact -v를 실행하라. 탐지 결과가 없으면 종료 코드는 0이다. 무언가 나오면 출력의 파일명과 커밋을 단서로, 발급처의 콘솔에서 그 키가 아직 유효한지 확인하라.
Git 밖의 폴더도 스캔한다
서버의 공개 폴더, 백업, 배포하는 zip의 내용은 Git 히스토리 스캔으로는 보이지 않는다. gitleaks dir (폴더 경로) --redact로 확인하라(→ 공개 디렉터리 인벤토리).
.env가 커밋된 적이 있는지 확인한다
git log --all --oneline -- .env를 실행해 과거 커밋 중 .env를 건드린 것이 있는지 보라. 한 줄이라도 나오면, 그 시점의 내용이 히스토리에 들어 있다.
오탐 다루기
더미 테스트 키나 문서의 예시는 자주 걸린다. 제외 방법은 세 가지이며, 통하는 것 중 가장 좁은 범위의 방법을 쓰라.
- 줄 단위: 그 줄에
gitleaks:allow주석을 단다 - 탐지 결과 단위: 출력에 나온 지문(fingerprint)을 저장소 루트의
.gitleaksignore에 추가한다(README는 이를 실험적 기능으로 표시한다) - 파일·패턴 단위:
.gitleaks.toml의 허용 목록(allowlist)에 추가한다
.gitleaks.toml을 직접 작성한다면, 기본 규칙을 유지하도록 [extend] 아래에 useDefault = true를 넣어라. 자기 규칙만 있는 설정은 기본 규칙이 잡았을 키를 소리 없이 놓친다.
오래된 저장소에 기존 탐지 결과가 많다면, 과거 보고서를 --baseline-path에 넘겨 새 결과만 보고되게 할 수 있다. 다만 그 보고서에 있는 키는 먼저 폐기하라. 기준선(baseline)에 넣는다고 키가 안전해지지는 않는다.
흔한 실수와 고치는 법
- 설정 파일만 두면 된다고 생각함: 누군가
pre-commit install을 실행하기 전까지 훅은 아무것도 하지 않는다. 모두가 실행하게 하고, CI에서도 같은 점검을 두 번째 층으로 돌려라. --no-verify에 익숙해짐: CI 점검을 머지의 필수 조건으로 만들어, 건너뛴 훅도 잡히게 하라.- CI 로그에 시크릿을 출력함:
--redact를 붙여라. CI 로그도 많은 사람이 읽을 수 있다. - CI에서 얕은 클론(shallow clone)을 스캔함: 최신 커밋만 검사되어 히스토리 속 시크릿을 놓친다.
fetch-depth: 0으로 설정하라. - 히스토리에서 지우고 끝냄: GitHub의 공식 가이드도 시크릿의 폐기나 교체를 첫 단계로 둔다. 포크, 클론, 캐시에 있는 사본은 지울 수 없다.
- 진짜 키를 "오탐"으로 허용 목록에 넣음: 이유를 주석으로 쓰고 리뷰에서 확인하라.
실제 사례: 공개된 채 남아 있던 액세스 키
2022년 10월 7일, 토요타자동차는 커넥티드 카 서비스 T-Connect의 사용자 웹사이트 소스 코드 일부가 GitHub에 공개되어 있었다고 발표했다. 회사 발표에 따르면 다음과 같다.
- 2017년 12월, 사이트를 개발하던 위탁업체가 소스 코드 일부를 실수로 공개 설정된 GitHub 계정에 올렸다
- 그 코드에는 고객 정보를 저장한 데이터 서버의 액세스 키가 들어 있었다
- 코드는 2022년 9월 15일에 비공개로 바뀌었고, 액세스 키는 9월 17일에 변경됐다
- 이메일 주소와 고객 관리 번호 296,019건이 해당됐다. 접근 기록상 제3자의 이용은 없었지만, 회사는 완전히 배제할 수는 없다고 밝혔다
업로드부터 삭제까지 약 5년이다. 또한 이 일은 회사 자체 저장소가 아니라 위탁업체의 계정에서 일어났다. 자기 저장소에 둔 스캐너가 코드가 흘러가는 모든 곳에 닿지는 못하므로, 각 키가 할 수 있는 일을 제한하고 정기적으로 키를 교체하는 것도 함께 하라. 그리고 이 사례에서도 키는 코드를 내린 이틀 뒤에 변경됐다. 찾았다면, 그다음은 키 교체다.
비공개 저장소도 시크릿을 두기에 안전한 곳은 아니다. 2026년 CAMPFIRE 사례에서는 회사 측에 따르면 부정 사용된 GitHub 자격 증명이 출발점이었고, GitHub에서 볼 수 있던 정보로 클라우드 자격 증명이 탈취됐다(→ CAMPFIRE 침해, 영어). 비공개 저장소에도 시크릿을 두지 말고, gitleaks로 정기적으로 스캔하라.
본 사이트는 어떻게 하는가
본 사이트의 기본 규칙은 시크릿을 Git에 아예 두지 않는 것 이다 — 연결 문자열과 API 키는 서버의 보호된 설정(권한 제한된 env 파일)에만 있고, 저장소나 인계 노트에 평문으로 두지 않는다(→ 시크릿을 git 밖에 두기 / 자체 호스팅 Git vs GitHub). 그 위에, 사람은 늘 실수한다는 전제로 탐지기를 겹친다. 설계는 "넣지 않음"을 보장하고, gitleaks 같은 스캔이 "그래도 들어간 것"을 잡는다 — 이 두 층이 본 사이트의 시크릿 유출 방어다. 시크릿을 다루는 원칙 자체는 .env 파일과 시크릿, 비밀번호를 안전하게 저장하기와 같다.
다음으로 읽기
- GitHub 설정: GitHub 2단계 인증 설정 방법
- 기초: .env 파일과 API 키: 무엇이 잘못되고 어떻게 지키는가
- GitHub 자격 증명에서 침해까지: CAMPFIRE 침해(2026, 영어)
- 유출 이후: GitHub에 노출된 자격 증명 543,699건이 여전히 유효 — 삭제가 아니라 폐기해야 하는 이유
- 사람 없이 공격이 진행될 때: Hugging Face 침입과 방어자가 버려야 할 전제(영어)
- 인벤토리: 공개 디렉터리에 시크릿을 남기고 있지 않은가
- 자체 호스팅: 시크릿을 git 밖에 두기 / 자체 호스팅 Git vs GitHub
- 용어집: ".env"란 무엇인가 — env 파일이 유출되면 일어나는 일
- 도구: 시크릿 유출 스캐너(붙여넣고 점검)
출처
- gitleaks(공식 저장소) README: 명령(git, dir, stdin), v8.19.0의 detect/protect 사용 중단, 종료 코드,
--redact,gitleaks:allow,.gitleaksignore,--baseline-path, 설정 확장 — github.com/gitleaks/gitleaks / 공식 훅 정의 — .pre-commit-hooks.yaml / Releases - gitleaks-action(공식 GitHub Action) README: 워크플로 예시와 라이선스 키 — github.com/gitleaks/gitleaks-action
- pre-commit 문서(install, run --all-files, autoupdate, SKIP) — pre-commit.com
- Git 문서, "githooks"(pre-commit 훅과 --no-verify) — git-scm.com
- GitHub Docs, "About push protection" — docs.github.com / "Removing sensitive data from a repository" — docs.github.com
- 토요타자동차, 고객 이메일 주소 등의 유출 가능성에 관한 공지(2022년 10월 7일, 일본어) — global.toyota