# gitleaks로 저장소의 시크릿을 찾고 커밋 전에 막기

> 임시 git 저장소에 가짜 키를 넣고 gitleaks로 커밋 이력을 스캔합니다. JSON과 SARIF 리포트, baseline, 오탐 제외, pre-commit 훅까지 차례로 설정하고 GitHub Actions 예시를 붙입니다.

- 주소: https://copyandrun.com/blog/gitleaks-secret-scan/
- 분야: DevSecOps
- 발행일: 2026-10-16
- 태그: gitleaks, 시크릿 스캔, pre-commit, SARIF

가짜 AWS 키와 가짜 GitHub 토큰을 넣은 임시 저장소를 gitleaks로 스캔해서, 파일을 지운 뒤에도 커밋 이력에 시크릿이 남는다는 점을 확인합니다. 이어서 리포트 출력, baseline, 오탐 제외, pre-commit 훅을 설정해 새 시크릿이 커밋되기 전에 막히는 것까지 해 봅니다.

## 준비물

| 항목 | 내용 |
| --- | --- |
| gitleaks | 8.30.1 (Docker 이미지 `zricethezav/gitleaks:v8.30.1`) |
| Docker | Docker Desktop (Engine 29.5.3) |
| Git | 2.54.0 |
| Python | 3.12 (pre-commit 설치용) |
| pre-commit | 4.7.0 |
| Go | 1.26.5 (7번 절의 기본 gitleaks 훅이 소스를 빌드할 때만 필요하고, docker 훅을 쓰면 필요 없습니다) |
| 예상 소요 시간 | 약 30분 (Go 훅은 첫 설치에 시간이 더 걸립니다. 7번 절 참고) |

gitleaks 저장소의 README는 `git`, `dir`, `stdin` 세 가지 스캔 서브커맨드를 안내하고, 이전 버전에서 쓰던 `detect`와 `protect`는 8.19.0에서 폐기됐다고 적고 있습니다. 이 글은 새 서브커맨드만 씁니다. 예제 값은 모두 형식만 맞춘 가짜이며 실제 키는 쓰지 않습니다.

이미지를 받고 버전을 확인합니다.

```bash
docker pull zricethezav/gitleaks:v8.30.1
docker run --rm zricethezav/gitleaks:v8.30.1 version
```

기대 출력의 마지막 줄입니다.

```text
v8.30.1
```

매번 긴 `docker run` 명령을 치지 않도록 셸 함수를 하나 만듭니다. Windows의 Git Bash에서는 경로 변환이 `-v` 인자를 망가뜨리므로 `MSYS_NO_PATHCONV=1`과 `pwd -W`를 씁니다. Linux와 macOS에서는 `-v "$PWD:/repo"`를 쓰면 됩니다. 사용하는 환경에 맞는 쪽 하나만 등록합니다.

```bash
# Windows Git Bash
gl() { MSYS_NO_PATHCONV=1 docker run --rm -v "$(pwd -W):/repo" -w /repo zricethezav/gitleaks:v8.30.1 "$@"; }

# Linux, macOS
gl() { docker run --rm -v "$PWD:/repo" -w /repo zricethezav/gitleaks:v8.30.1 "$@"; }
```

이 글의 화면은 Windows Git Bash 쪽 함수로 실행한 결과입니다. 이 함수는 현재 폴더를 컨테이너의 `/repo`로 연결해 gitleaks를 실행합니다. 이후 `gl`로 적은 명령은 모두 이 함수를 거칩니다.

## 1. 가짜 시크릿이 든 저장소 만들기

커밋을 세 개 만듭니다. 두 번째 커밋에서 가짜 키가 든 설정 파일을 추가하고, 세 번째 커밋에서 그 파일을 지웁니다.

```bash
mkdir gl-demo && cd gl-demo
git init -b main
git config user.email dev@localhost
git config user.name dev
mkdir app && echo "print('hello')" > app/main.py
git add . && git commit -m "첫 커밋: 앱 뼈대"

cat > app/config.py <<'EOF'
AWS_ACCESS_KEY_ID = "AKIAJQXZPLMTRNBKWD3C"
GITHUB_TOKEN = "ghp_aB3dE5gH7jK9mN1pQ3sT5vW7yZ9bC1dE3fG5"
DB_PASSWORD = "not-a-secret"
EOF
git add . && git commit -m "설정 파일 추가"

git rm app/config.py && git commit -m "설정 파일 삭제"
```

AWS 공식 문서의 예시 값 `AKIAIOSFODNN7EXAMPLE`은 쓰지 않았습니다. 이 값은 gitleaks가 탐지하지 않아서 실습에 맞지 않기 때문입니다. 자세한 내용은 아래 자주 막히는 곳에 있습니다.

## 2. 작업 폴더와 커밋 이력을 따로 스캔하기

`gl dir`은 지금 폴더에 있는 파일을 검사하고, `gl git`은 커밋 이력 전체를 검사합니다. 둘을 차례로 돌려 결과를 비교합니다.

```bash
gl dir --no-banner .
gl git --no-banner .; echo "exit=$?"
```

기대 출력입니다. 시각과 소요 시간은 달라집니다.

```text
INF scanned ~15 bytes (15 bytes) in 8.71ms
INF no leaks found
INF 2 commits scanned.
INF scanned ~145 bytes (145 bytes) in 192ms
WRN leaks found: 2
exit=1
```

설정 파일을 지웠기 때문에 `dir`은 아무것도 찾지 못합니다. `git`은 두 번째 커밋을 읽어 2건을 찾고 종료 코드 1로 끝납니다. 시크릿이 한 번 커밋되면 파일을 지워도 이력에 남으므로, 이력까지 스캔해야 합니다. 종료 코드는 발견이 있으면 1이고 `--exit-code` 옵션으로 바꿀 수 있습니다.

![gl dir은 no leaks found, gl git은 leaks found: 2와 exit=1이 나오는 것을 확인합니다.](https://copyandrun.com/img/gitleaks-secret-scan/01-scan-history-exit1.png)

## 3. 탐지 내용 자세히 보기

`-v`를 붙이면 발견 한 건마다 규칙 이름, 파일, 줄, 커밋이 출력됩니다. `--redact`는 시크릿 값을 `REDACTED`로 가립니다. 로그나 CI 화면에 값이 그대로 남는 것을 막으므로 이 글에서는 계속 붙입니다.

```bash
gl git --no-banner --redact -v .
```

기대 출력은 아래 두 건입니다. 두 건의 출력 순서는 실행마다 바뀔 수 있고, 시각과 커밋 해시, 날짜도 달라집니다.

```text
Finding:     ...WS_ACCESS_KEY_ID = "REDACTED
Secret:      REDACTED
RuleID:      aws-access-token
Entropy:     4.121928
File:        app/config.py
Line:        1
Commit:      e01f9d387ec539d0fe337070927d63ae25935d3d
Author:      dev
Email:       dev@localhost
Date:        2026-10-16T10:45:29Z
Fingerprint: e01f9d387ec539d0fe337070927d63ae25935d3d:app/config.py:aws-access-token:1

Finding:     GITHUB_TOKEN = "REDACTED
Secret:      REDACTED
RuleID:      github-pat
Entropy:     4.734184
File:        app/config.py
Line:        2
Commit:      e01f9d387ec539d0fe337070927d63ae25935d3d
Author:      dev
Email:       dev@localhost
Date:        2026-10-16T10:45:29Z
Fingerprint: e01f9d387ec539d0fe337070927d63ae25935d3d:app/config.py:github-pat:2

INF 2 commits scanned.
INF scanned ~145 bytes (145 bytes) in 188ms
WRN leaks found: 2
```

`DB_PASSWORD = "not-a-secret"`은 잡히지 않았습니다. 기본 규칙은 알려진 키 형식과 엔트로피 기준으로 판단하기 때문입니다. 각 건의 마지막 줄 Fingerprint는 뒤에서 오탐을 제외할 때 씁니다.

![탐지 두 건의 Finding, RuleID, 파일, 줄, 커밋 해시, Fingerprint가 출력되고 Secret 값은 REDACTED로 가려진 것을 확인합니다.](https://copyandrun.com/img/gitleaks-secret-scan/02-finding-detail.png)

## 4. JSON과 SARIF 리포트 만들기

`-f`로 형식을, `-r`로 저장할 파일을 지정합니다. 8.30.1의 도움말에 나오는 형식은 `json`, `csv`, `junit`, `sarif`, `template`입니다.

```bash
gl git --no-banner --redact -f json -r report.json .
python -c "import json; [print(r['RuleID'], r['File'], r['StartLine'], r['Secret']) for r in json.load(open('report.json'))]"

gl git --no-banner --redact -f sarif -r report.sarif .
ls -l report.sarif
```

기대 출력입니다. 두 줄의 순서와 SARIF 파일 크기는 달라질 수 있습니다.

```text
aws-access-token app/config.py 1 REDACTED
github-pat app/config.py 2 REDACTED
50646 report.sarif
```

JSON은 스크립트로 가공하기 쉽고, SARIF는 GitHub code scanning 같은 도구에 올릴 때 씁니다. SARIF에는 룰 정의 목록이 함께 들어가서 발견이 두 건뿐인데도 50KB가 넘습니다. `results` 항목만 뽑으면 JSON과 같은 위치 정보가 들어 있습니다.

다음 절부터는 `git add .`로 커밋하므로 리포트 파일을 지웁니다. 지우지 않으면 `report.sarif`가 커밋되고, 그 안의 룰 설명 문자열이 `sourcegraph-access-token`으로 추가 탐지돼 결과가 어긋납니다.

```bash
rm report.json report.sarif
```

![JSON 리포트에서 RuleID, 파일, 줄 번호를 뽑은 결과와 SARIF 파일 크기, SARIF results에서 뽑은 같은 두 건을 확인합니다.](https://copyandrun.com/img/gitleaks-secret-scan/03-json-sarif-report.png)

## 5. baseline으로 기존 발견 제외하기

이미 커밋된 시크릿은 키를 폐기하고 이력을 정리하기 전까지 계속 잡힙니다. 도입 첫날부터 CI를 빨갛게 만들지 않으려면 현재 발견을 baseline으로 저장하고, 그 뒤로 생기는 것만 보도록 합니다. baseline 파일은 같은 리포트 형식이라 `-f json -r`로 만듭니다.

```bash
gl git --no-banner --redact -f json -r gitleaks-baseline.json .
git add gitleaks-baseline.json && git commit -m "gitleaks baseline 추가"
```

새 시크릿이 든 파일을 커밋하고 baseline 없이, 있게 각각 스캔합니다.

```bash
cat > app/notify.py <<'EOF'
SLACK_WEBHOOK = "https://hooks.slack.com/services/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"
EOF
git add . && git commit -m "알림 모듈 추가"

gl git --no-banner --redact .; echo "exit=$?"
gl git --no-banner --redact -b gitleaks-baseline.json -v . | grep -E "RuleID|File"
```

기대 출력입니다.

```text
INF 4 commits scanned.
INF scanned ~1559 bytes (1.56 KB) in 257ms
WRN leaks found: 3
exit=1
RuleID:      slack-webhook-url
File:        app/notify.py
WRN leaks found: 1
```

baseline 없이는 3건, baseline을 주면 새로 들어온 Slack 웹훅 한 건만 남습니다. 종료 코드는 새 발견이 있으므로 여전히 1입니다. baseline을 만들 때와 스캔할 때 `--redact` 여부를 맞춰야 합니다. 이 글처럼 둘 다 `--redact`로 하면 baseline 파일에 시크릿 값이 들어가지 않아 저장소에 커밋해도 됩니다.

![baseline 없이는 3건과 exit=1, baseline을 주면 slack-webhook-url 한 건만 남는 것을 확인합니다.](https://copyandrun.com/img/gitleaks-secret-scan/04-baseline-new-only.png)

## 6. 오탐 제외하기

테스트용 가짜 토큰을 담은 파일을 하나 추가해 오탐을 만들고, 제외 방법 세 가지를 차례로 봅니다.

```bash
mkdir tests
cat > tests/fake_tokens.py <<'EOF'
SAMPLE_TOKEN = "ghp_Zx8Yw7Vu6Ts5Rq4Po3Nm2Lk1Jh0Gf9Ed8Cb7"
EOF
git add . && git commit -m "테스트용 가짜 토큰 추가"
gl git --no-banner --redact -b gitleaks-baseline.json -v . | grep -E "RuleID|File|Fingerprint"
```

기대 출력입니다. 테스트 파일의 github-pat와 앞에서 본 Slack 웹훅이 함께 나옵니다.

```text
RuleID:      slack-webhook-url
File:        app/notify.py
Fingerprint: 196d2ba13a64ab55e3295464d4d8c2b23bc5f72e:app/notify.py:slack-webhook-url:1
RuleID:      github-pat
File:        tests/fake_tokens.py
Fingerprint: 5f250eaac1b46a4d1628fb12fbb8c954796f1d63:tests/fake_tokens.py:github-pat:1
WRN leaks found: 2
```

**방법 1. `.gitleaksignore`에 Fingerprint 적기.** 제외할 한 건의 Fingerprint를 저장소 루트의 `.gitleaksignore`에 한 줄로 적습니다. 커밋 해시가 들어 있어서 그 커밋의 그 줄만 제외됩니다. README는 이 기능을 실험적이라고 표시합니다. 해시는 직접 실행한 결과에서 복사해야 하므로 아래 값은 앞의 출력에서 나온 `tests/fake_tokens.py`의 Fingerprint로 바꿔 적습니다.

```bash
echo "5f250eaac1b46a4d1628fb12fbb8c954796f1d63:tests/fake_tokens.py:github-pat:1" > .gitleaksignore
gl git --no-banner --redact -b gitleaks-baseline.json -v . | grep -E "RuleID|File"
```

기대 출력은 `tests/fake_tokens.py`가 사라지고 Slack 웹훅 한 건만 남는 것입니다.

```text
RuleID:      slack-webhook-url
File:        app/notify.py
WRN leaks found: 1
```

![.gitleaksignore에 Fingerprint를 적은 뒤 tests/fake_tokens.py가 빠지고 slack-webhook-url 한 건만 남는 것을 확인합니다.](https://copyandrun.com/img/gitleaks-secret-scan/05-gitleaksignore.png)

**방법 2. `.gitleaks.toml`로 경로 제외하기.** 테스트 폴더 전체처럼 범위가 넓을 때 씁니다. 기본 규칙을 유지하려면 `[extend]` 블록에 `useDefault = true`를 넣어야 합니다. README는 `useDefault = true`가 바이너리에 내장된 기본 설정을 확장한다고 설명하고, 이 블록 없이 설정 파일을 만들면 시크릿이 하나도 잡히지 않는 현상은 아래 자주 막히는 곳에서 다룹니다.

```toml
[extend]
useDefault = true

[[allowlists]]
description = "테스트용 가짜 토큰 폴더"
paths = ['''^tests/''']
```

이 파일을 저장소 루트에 `.gitleaks.toml`로 두고 같은 명령을 돌리면 `tests/fake_tokens.py`가 빠지고 `app/notify.py` 한 건만 남습니다. 다만 `tests/` 전체를 제외하면 그 폴더에 나중에 실제 시크릿이 들어가도 검출되지 않습니다. 가짜 토큰이 든 파일이 정해져 있다면 `paths = ['''^tests/fake_tokens\.py$''']`처럼 파일 하나만 지정하는 편이 안전합니다. 설정 파일은 저장소 루트의 `.gitleaks.toml`을 자동으로 읽고, `-c` 옵션이나 `GITLEAKS_CONFIG` 환경 변수로 다른 경로를 지정할 수도 있습니다.

**방법 3. 해당 줄에 `gitleaks:allow` 주석 달기.** 한 줄만 예외로 둘 때 씁니다.

```python
SAMPLE_TOKEN = "ghp_Zx8Yw7Vu6Ts5Rq4Po3Nm2Lk1Jh0Gf9Ed8Cb7"  # gitleaks:allow
```

`gl dir`로 스캔하면 이 줄은 제외됩니다. 다만 이미 커밋된 이전 줄은 주석이 없는 상태로 이력에 있으므로 `gl git`에서는 그대로 잡힙니다. 이력에 있는 건은 방법 1이나 2로 처리해야 합니다.

## 7. pre-commit으로 커밋 전에 막기

`pre-commit` 프레임워크에 gitleaks 훅을 등록합니다. 훅은 커밋하려고 스테이징한 변경만 검사합니다. gitleaks 저장소의 `.pre-commit-hooks.yaml`에는 훅이 세 가지 있습니다. `gitleaks`는 `language: golang`이라서 Go로 소스를 빌드하므로 Go가 설치돼 있어야 하고, `gitleaks-docker`는 공식 Docker 이미지를 실행하고, `gitleaks-system`은 PC에 설치된 gitleaks 바이너리를 씁니다. Go가 없다면 뒤의 docker 훅을 쓰면 됩니다.

먼저 Go 방식입니다.

```bash
pip install pre-commit
cat > .pre-commit-config.yaml <<'EOF'
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks
EOF
pre-commit install
git add .pre-commit-config.yaml
git commit -m "pre-commit 설정 추가"
```

첫 커밋에서 `Installing environment for https://github.com/gitleaks/gitleaks.` 메시지가 나오면서 Go로 gitleaks를 빌드합니다. 이번 실행에서는 이 첫 커밋에 7분 넘게(461초) 걸렸고, 환경에 따라 달라집니다. 이후에는 캐시를 씁니다. 기대 출력에는 `Detect hardcoded secrets....Passed`가 나옵니다.

이제 가짜 AWS 키가 든 파일을 커밋해 봅니다.

```bash
echo 'AWS_KEY = "AKIAJQXZPLMTRNBKWD3C"' > app/aws.py
git add app/aws.py
git commit -m "AWS 설정 추가"
```

기대 출력의 핵심입니다. gitleaks 로고와 Finding, Entropy 줄은 줄였습니다.

```text
Detect hardcoded secrets.....Failed
- hook id: gitleaks
- exit code: 1
RuleID:      aws-access-token
File:        app/aws.py
WRN leaks found: 1
```

커밋은 만들어지지 않고 `git log --oneline`의 맨 위는 이전 커밋 그대로입니다. 키를 환경 변수로 읽게 고쳐서 다시 커밋하면 통과합니다.

```bash
cat > app/aws.py <<'EOF'
import os
AWS_KEY = os.environ["AWS_ACCESS_KEY_ID"]
EOF
git add app/aws.py
git commit -m "AWS 키를 환경 변수로 읽기"
```

`Detect hardcoded secrets....Passed`가 나오고 커밋이 만들어집니다. 급할 때는 `SKIP=gitleaks git commit ...`으로 이 훅만 건너뛸 수 있습니다. 훅은 개발자 PC에서만 동작하므로 우회될 수 있고, 다음 절의 CI 검사가 마지막 방어선입니다.

![첫 커밋은 Passed, 가짜 AWS 키 커밋은 Failed로 막히고 git log의 맨 위는 이전 커밋 그대로이며, 환경 변수로 고친 커밋은 Passed로 만들어지는 것을 확인합니다.](https://copyandrun.com/img/gitleaks-secret-scan/06-pre-commit-block.png)

**Go 없이 docker 훅으로 하기.** Go가 없는 PC라면 `.pre-commit-config.yaml`을 아래처럼 바꿉니다. `gitleaks-docker` 훅은 이미지 이름에 태그를 붙이지 않아 최신 이미지를 쓰므로, `entry`를 덮어써서 앞에서 고정한 `v8.30.1` 태그를 지정했습니다.

```yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.30.1
    hooks:
      - id: gitleaks-docker
        entry: zricethezav/gitleaks:v8.30.1 git --pre-commit --redact --staged --verbose
```

Go를 PATH에서 뺀 셸(`go: command not found`)에서 이 설정을 커밋하고, 가짜 키가 든 `app/aws2.py`를 커밋해 봤습니다. 설정 커밋은 Passed로 3초 만에 끝났고 키 커밋은 같은 방식으로 Failed가 됐습니다. 훅이 Docker로 이미지를 실행하므로 Docker Desktop이 켜져 있어야 합니다.

![Go가 없는 셸에서 docker 훅으로 설정 커밋은 Passed, 가짜 AWS 키 커밋은 Failed로 막히는 것을 확인합니다.](https://copyandrun.com/img/gitleaks-secret-scan/07-pre-commit-docker.png)

## 8. GitHub Actions에서 같은 검사 돌리기

이 절은 실행해 보지 못했고 공식 저장소 안내를 기준으로 정리한 것입니다. 공식 액션을 쓰는 워크플로는 다음과 같습니다. 전체 이력을 스캔하므로 `fetch-depth: 0`이 필요합니다.

```yaml
name: gitleaks
on:
  pull_request:
  push:
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 }}
```

액션 저장소는 `GITLEAKS_LICENSE`가 조직 계정에서만 필요하고 개인 계정에는 필요 없다고 안내합니다. 또 `gitleaks-action@v2`는 2026년 9월 16일부터 동작하지 않는다고 적고 있으니 v3를 씁니다.

baseline을 쓰고 싶다면 공식 액션 단계를 빼고 이 글에서 실행한 명령을 그대로 단계로 옮깁니다. 공식 액션 안내문(README)에서 baseline 옵션을 찾지 못했습니다. 액션이 baseline을 지원하는지는 확인하지 못했으므로 모른다고 적어 둡니다. 액션 단계를 그대로 두고 docker 단계를 더하면, 액션 단계는 baseline 없이 전체 이력을 스캔해 기존 발견 때문에 실패할 것으로 예상됩니다. 이 부분은 실행해 보지 않은 추정입니다. Linux 러너에서는 경로 변환 문제가 없으므로 `$PWD`를 씁니다.

```yaml
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - name: gitleaks (baseline 이후 새 발견만)
        run: >
          docker run --rm -v "$PWD:/repo" -w /repo zricethezav/gitleaks:v8.30.1
          git --no-banner --redact -b gitleaks-baseline.json -v .
```

baseline 방식에서는 `gitleaks/gitleaks-action` 단계가 없으므로 `GITHUB_TOKEN`과 `GITLEAKS_LICENSE` 설정도 필요 없습니다. 발견이 있으면 종료 코드 1로 단계가 실패합니다. 머지까지 막으려면 저장소의 브랜치 보호 규칙에서 이 체크를 필수로 지정해야 합니다.

## 자주 막히는 곳

**AWS 공식 예시 키 `AKIAIOSFODNN7EXAMPLE`를 넣었는데 `no leaks found`가 나옵니다.** `dir`과 `git` 모두 재현했습니다. 값에 `EXAMPLE`이 들어 있어서 기본 규칙이 예시 값으로 보고 제외하는 것으로 보입니다. 탐지 동작을 시험할 때는 이 문자열 대신 `AKIA` 뒤에 영문 대문자와 숫자 2~7로 된 16자를 임의로 붙인 값을 씁니다. 직접 시험하니 뒤 16자에 숫자 0, 1, 8, 9가 하나라도 들어가면 탐지되지 않았습니다.

**Git Bash에서 `docker run -v "$PWD:/repo"`가 `Run 'docker run --help'` 오류로 끝납니다.** Git Bash가 경로를 Windows 형식으로 바꾸다가 인자 형식이 깨지기 때문입니다. 위 `gl` 함수처럼 `MSYS_NO_PATHCONV=1`과 `$(pwd -W)`를 쓰면 됩니다.

**`.gitleaks.toml`을 만들었더니 시크릿이 하나도 안 잡히고 `no leaks found`가 나옵니다.** 설정 파일에 `[extend]`와 `useDefault = true` 없이 `[[allowlists]]`만 적으면 기본 규칙이 빠지고 규칙이 하나도 없는 상태가 됩니다. 같은 저장소에서 `app/notify.py`의 Slack 웹훅까지 보고하지 않았습니다. README는 `[extend]`의 `useDefault = true`가 내장 기본 설정을 확장하는 방법이라고 설명하므로, 기본 규칙을 함께 쓰려면 설정 파일을 `[extend]` 블록으로 시작합니다.

**baseline을 적용했는데 기존 2건이 그대로 보고됩니다.** baseline을 `--redact`로 만들고 스캔은 `--redact` 없이 했을 때 재현했습니다. 두 쪽 모두 `--redact`를 쓰거나 둘 다 빼야 합니다.

**`gitleaks:allow`를 달았는데 `gl git`에서는 여전히 잡힙니다.** 주석은 그 줄을 스캔할 때만 효력이 있습니다. 주석을 달기 전 커밋에 있던 줄은 이력에서 계속 발견되므로 `.gitleaksignore`나 baseline으로 처리합니다.

## 이어서 해 볼 것

- **진짜 시크릿 대응**: 실제 키가 커밋됐다면 baseline에 넣어 두는 것으로 끝나지 않습니다. 해당 키를 먼저 폐기하고 새로 발급한 뒤, 이력 정리 여부를 정합니다.
- **규칙 직접 추가**: `.gitleaks.toml`에 `[[rules]]`를 더해 사내 토큰 형식을 정규식으로 탐지해 봅니다. `[extend]`로 기본 규칙을 함께 쓰면 됩니다.
- **SARIF를 code scanning에 올리기**: 4번 절의 `report.sarif`를 `github/codeql-action/upload-sarif` 단계로 올려 PR에 표시하는 구성을 시도해 봅니다. 비공개 저장소에서 쓸 수 있는지는 플랜 문서에서 확인해야 합니다.

## 확인 범위

직접 실행한 부분은 1번부터 7번까지와 자주 막히는 곳 다섯 항목의 재현입니다. 환경은 Windows의 Git Bash, Docker Desktop, gitleaks 8.30.1 이미지, Git 2.54.0, Python 3.12, pre-commit 4.7.0, Go 1.26.5입니다. 글의 화면은 모두 이 순서를 새 저장소에서 처음부터 다시 실행해 만들었습니다. 8번의 baseline용 단계는 `v8.30.1` 이미지로 로컬에서만 실행했습니다. 6번의 `.gitleaks.toml` 경로 제외(폴더 전체와 파일 하나 지정 두 가지)와 `gitleaks:allow` 주석도 직접 실행했습니다. 이 두 방법은 화면 캡처는 없고 건수만 확인했습니다. 7번의 Go 훅과 docker 훅은 둘 다 실행했고, docker 훅은 Go를 PATH에서 뺀 셸에서 확인했습니다. 훅 설치용 pre-commit은 가상 환경에 설치해 실행했습니다.

8번의 GitHub Actions 워크플로는 실행하지 못했습니다. 액션 버전, 라이선스 안내, 2026년 9월 16일 중단 공지는 gitleaks-action 저장소 안내를 읽고 옮긴 것이므로 적용 전에 해당 저장소 문서와 대조하시기 바랍니다. baseline을 쓰는 docker 단계도 로컬에서 같은 명령을 돌렸을 뿐 러너에서는 돌리지 않았습니다. 이 글은 8.30.1 기준이며 저장소 README에는 기능 개발을 마치고 보안 패치 중심으로 유지한다는 안내가 있습니다.

참고한 공식 문서는 https://github.com/gitleaks/gitleaks 와 https://github.com/gitleaks/gitleaks-action 입니다.
