보안 가이드

gitleaks로 커밋 전에 시크릿을 막아라: push 전에 API 키 유출 잡기

실수로 API 키를 커밋하면 Git 히스토리에 남아 영원히 유출된다. gitleaks가 두 단계의 점검 — pre-commit 훅과 CI — 으로 시크릿을 막는 법, 그리고 탐지만으로는 끝나지 않는 폐기 단계.

게시 2026-06-12 업데이트 2026-10-06 12분 읽기
이 페이지에서

대상: 1인·소규모 개발에서 Git을 쓰며 "실수로 API 키를 커밋하면 어쩌지?" 걱정하는 사람. 공격자 메커니즘은 없다 — 오직 시크릿이 자기 저장소에서 새어 나가지 못하게 막는 장치다.

본 사이트의 견해: .gitignore만으로는 시크릿을 지킬 수 없다

"내 .env는 .gitignore에 있으니 괜찮아"는 흔한 말이지만, 놓치는 경우가 있다. .gitignore는 새 파일이 추적되는 것만 막을 뿐이다. 이미 커밋된 시크릿, 급히 git add -f로 강제 추가한 것, .env.local.bak 같은 예상 밖 이름은 잡지 못한다. 시크릿이 들어가는 경로의 일부만 막는다. 실제로 커밋 내용을 들여다보고 "시크릿처럼 보이는 문자열"을 표시하는 무언가 — 탐지기 — 가 필요하다. 그것이 gitleaks의 역할이다.

왜 "커밋 전"에 막는가

시크릿이 위험한 이유는 Git이 히스토리를 영원히 보관하기 때문이다. 최신 파일에서 지워도 과거 커밋에 남는다. 그리고 push되는 순간 그 히스토리는 다른 머신, 서버, CI 로그로 복사된다.

히스토리에 남음
최신에서 사라져도 과거 커밋에 남음
push = 확산
다른 머신·서버·CI 로그로 복사됨
폐기 필수
한 번 유출된 키는 재발급뿐
두 단계 점검
pre-commit과 CI에서 막음

그러니 시크릿 방어는 사실 유출 후 복구가 아니라 유출 전 예방이다. "공개 디렉터리에 시크릿을 남기지 마라"는 인벤토리(→ 웹루트 인벤토리)와 같은 발상을, 코드에 적용한 것이다.

어느 단계에서 시크릿을 탐지하는가

시크릿이 코드에서 세상으로 가는 경로 위에 두 단계의 점검을 둘 수 있다: 로컬 커밋의 순간, 그리고 공유되기 직전(CI).

코드 속 시크릿

API 키, 키, 토큰

→

점검 1: pre-commit

커밋 시 로컬에서 탐지 & 중단

→

점검 2: CI / cron

push 후, 머지/배포 전 탐지

→

공개 / 공유

이 지점을 지나면 유출로 간주

유출 경로와 두 단계의 점검. pre-commit은 로컬에서 막고, CI는 머지/배포 전에 막는다.

gitleaks 연결하는 법

1

먼저 기존 히스토리를 스캔

도입 시 gitleaks git(v8.19.0 이전 버전에서는 gitleaks detect)으로 저장소 히스토리 전체를 한 번 스캔하라. 과거 커밋에 남은 시크릿을 찾아내는 것이 첫 작업이다. 여기서 나온 것은 아래처럼 폐기가 필요하다고 가정한다.
2

pre-commit 훅으로 점검 1 추가

모든 커밋에 gitleaks git --pre-commit --staged(예전의 gitleaks protect --staged)를 실행해, 시크릿이 포함된 커밋을 그 자리에서 중단하라. pre-commit 프레임워크(.pre-commit-config.yaml)에 넣어 모두에게 같은 점검이 로컬에서 적용되게 하라.
3

CI / cron으로 점검 2 추가

훅은 각자 비활성화할 수 있으니, 무엇이든 공유되기 전에 다시 점검하라. gitleaks git을 CI 작업으로 — 자체 호스팅이면 주기적 스캔으로 — 돌려 빠져나간 것을 잡아라(의존성 감사와 같은 기계 모니터링 발상).
4

.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 gitGit 커밋 히스토리첫 전체 히스토리 스캔, CI, 정기 스캔
gitleaks git --pre-commit --staged지금 커밋하려는 변경(스테이징된 것)pre-commit 훅(공식 훅이 정확히 이것을 실행)
gitleaks dirGit에 없는 것을 포함한 폴더와 파일공개 웹 폴더, 백업, 압축을 푼 릴리스 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: gitleaks

id는 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 release

GitHub의 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 protectionGitHub로 push할 때GitHub가 지원하는 시크릿 종류이유를 대면 우회할 수 있다. 다른 Git 서버에서는 작동하지 않는다

사용자용 GitHub push protection은 공개 저장소로의 push에 기본으로 켜져 있다. 무료 안전망이지만 로컬 커밋은 막지 않고, 지원하지 않는 형식의 키나 직접 조립한 연결 문자열은 통과할 수 있다(자세히: GitHub에 노출된 자격 증명이 여전히 유효한 이유). gitleaks는 그 앞 단계와 GitHub 바깥 전부를 맡는다.

자신의 설정을 확인하는 방법

한 번 설정했다고 돌고 있다는 보장은 없다. 자기 저장소에서 다음을 차례로 확인하라.

1

gitleaks 버전을 확인한다

gitleaks version을 실행하라. v8.19.0보다 오래된 버전에는 gitleaks git 등 새 명령이 없으니 업데이트하라.

2

훅이 등록됐는지 확인한다

.git/hooks/pre-commit이 있는지 보라. git config --get core.hooksPath가 다른 폴더를 가리킨다면 그곳을 보라. Git은 클론할 때 훅을 복사하지 않으므로, 새 머신이나 새로 클론한 저장소에서는 pre-commit install을 다시 실행하라.

3

히스토리 전체를 한 번 스캔한다

저장소 루트에서 gitleaks git --redact -v를 실행하라. 탐지 결과가 없으면 종료 코드는 0이다. 무언가 나오면 출력의 파일명과 커밋을 단서로, 발급처의 콘솔에서 그 키가 아직 유효한지 확인하라.

4

Git 밖의 폴더도 스캔한다

서버의 공개 폴더, 백업, 배포하는 zip의 내용은 Git 히스토리 스캔으로는 보이지 않는다. gitleaks dir (폴더 경로) --redact로 확인하라(→ 공개 디렉터리 인벤토리).

5

.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 파일과 시크릿, 비밀번호를 안전하게 저장하기와 같다.

다음으로 읽기

출처

  • 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

FAQ

Qgitleaks가 무엇인가요?
A

Git 저장소의 작업 트리와 커밋 히스토리를 스캔해 시크릿 — API 키, 개인 키, 토큰 — 이 커밋됐는지 탐지하는 무료 도구입니다. 정규식 규칙과 문자열 엔트로피(무작위성)로 '시크릿처럼 보이는 문자열'을 찾고, pre-commit 훅이나 CI에서 자동 점검으로 돌 수 있습니다.

Q.gitignore에 넣으면 시크릿이 보호되지 않나요?
A

충분치 않습니다. .gitignore는 '새 추적'만 막을 뿐 — 이미 커밋된 시크릿이나 git add -f 로 강제 추가된 것은 탐지하지 못합니다. 일부 사고만 막을 수 있으므로, gitleaks 같은 탐지기를 pre-commit과 CI 양쪽에 함께 두세요.

Q실수로 시크릿을 커밋하고 push했다면 어떻게 하나요?
A

그 시크릿을 유출된 것으로 다루세요. 최우선은 히스토리 재작성이 아니라, 노출된 키나 토큰을 폐기하고 교체(재발급)하는 것입니다. 공개 저장소나 로그에 닿은 시크릿은 회수할 수 없다고 가정하고 — 먼저 무효화한 다음, 히스토리를 정리하고 예방(gitleaks)을 더하세요.

Qgitleaks detect와 gitleaks protect는 아직 쓸 수 있나요?
A

실행은 되지만, v8.19.0에서 사용 중단(deprecated)되어 도움말에서도 숨겨졌습니다. 현재 명령은 gitleaks git(커밋 히스토리 스캔), gitleaks dir(폴더와 파일 스캔), gitleaks stdin(파이프 입력 스캔)입니다. pre-commit 점검은 이제 gitleaks git --pre-commit --staged이며, 공식 pre-commit 훅도 이것을 실행합니다. 새로 설정할 때는 새 명령을 쓰세요.

QGitHub push protection이 켜져 있어도 gitleaks가 필요한가요?
A

다루는 범위가 다르므로 둘 다 쓰세요. 사용자용 GitHub push protection은 공개 저장소로의 push에 기본으로 켜져 있지만, 이유를 골라 우회할 수 있고, 로컬 커밋이나 GitHub 이외의 Git 서버에는 작동하지 않습니다. gitleaks는 커밋 전에 자기 머신에서 시크릿을 막고, 자체 호스팅 Git 서버나 어떤 CI에서도 같은 규칙을 돌립니다.

Q더미 테스트 키를 gitleaks가 잡지 않게 하려면 어떻게 하나요?
A

방법은 세 가지입니다. 그 줄에 gitleaks:allow 주석을 달거나, 탐지 결과의 지문(fingerprint)을 .gitleaksignore에 넣거나, .gitleaks.toml의 허용 목록(allowlist)으로 파일이나 패턴을 제외합니다. 어느 방법이든 왜 안전한지 주석을 남기세요. 가장 피해야 할 실패는 진짜 키를 오탐으로 허용 목록에 넣는 것입니다.