보안 CLI
b58d3f1 기준)
Gitleaks는 소스 코드에 하드코딩한 비밀번호·API 키·토큰 등의 노출 후보를 찾아주는 프로그램입니다. 현재 파일과 과거 커밋을 따로 살피고, 의심스러운 문자열을 규칙과 문맥으로 걸러 보고서로 남깁니다. 사용자는 명령 하나를 실행하지만, 내부에서는 입력 읽기·규칙 선택·위치 계산·예외 처리·결과 출력이 각자의 역할을 합니다.
git 모드는 과거 출고 기록을, dir 모드는 지금 창고에 있는 상자를,
stdin 모드는 컨베이어 벨트로 들어오는 내용을 검사합니다.
짐의 출처가 달라도 검사대에 올라오는 순간에는 같은 형태의 조각이 됩니다.
검사 기준은 모양이 알려진 출입증을 찾는 규칙과 비슷합니다. 발견한 출입증이 실제로 문을 여는지까지 이 검사대가 확인하는 것은 아닙니다. 경고는 검토해야 할 노출 후보이며, 유효한 권한의 증명과 구분해야 합니다.
| 명령 | 입력 범위 | 처음 기억할 점 |
|---|---|---|
gitleaks git . | 로컬 Git 이력의 패치 | 과거에 추가했다가 지운 값도 이력에 남아 있으면 탐지 대상 |
gitleaks dir . | 현재 디렉터리 또는 파일 | 아직 커밋하지 않은 파일도 볼 수 있음. 과거 이력을 복원하지는 않음 |
gitleaks stdin | 표준입력으로 들어온 내용 | 파이프로 연결한 로그·텍스트도 같은 탐지기로 검사 |
명령의 시작점은 cmd/git.go:runGit, cmd/directory.go:runDirectory,
cmd/stdin.go:runStdIn입니다. 셋 모두 Detector.DetectSource를 호출합니다.
이 공통 지점을 찾으면 처음부터 모든 패키지를 읽을 필요가 없습니다.
# 설치된 Gitleaks로 실행하는 사용 예시
# Git 이력을 검사하고, 결과의 비밀값을 가린다
gitleaks git . --redact --report-format json --report-path findings.json
# 현재 디렉터리만 검사한다
gitleaks dir . --redact
# 텍스트를 표준입력으로 연결한다
printf '%s\n' '설정 예시 텍스트' | gitleaks stdin --redact
웹 서버나 데이터베이스를 갖추지 않아도 실용적인 제품을 만들 수 있습니다. 이 저장소는 작은 인터페이스와 명확한 데이터 구조가 어떻게 CLI·Git 훅·CI 보고서까지 이어지는지 보여줍니다.
2026-10-23 GitHub Trending Go Weekly #3에 오른 저장소입니다. 다만 순위만으로 특정 사건이나 기능이 인기 상승의 원인이라고 단정할 수는 없습니다. 여기서 살펴볼 것은 소스에 드러난 도입 편의성과 학습 가치입니다.
.pre-commit-hooks.yaml의 실행 명령은
gitleaks git --pre-commit --redact --staged --verbose입니다.
커밋 전에 스테이징한 변경을 검사하도록 연결합니다.
.github/workflows/gitleaks.yml은 자신의 저장소를 Gitleaks Action으로 검사하며,
체크아웃에 fetch-depth: 0을 지정합니다. 이력의 깊이가 검사 범위를 결정하기 때문입니다.
이 도구의 매력은 탐지 알고리즘 하나보다, 같은 검사를 로컬 커밋 전과 CI에 반복해서 넣을 수 있다는 데 있습니다. 이것은 트렌딩 원인에 대한 통계적 증명이 아니라, 구현과 배포 경로에서 읽어낸 장점입니다.
| 도구 | 중심 접근 | 선택할 때 볼 차이 |
|---|---|---|
| Gitleaks | 로컬 Git·파일·표준입력 + TOML 규칙 | 단일 CLI와 규칙 파일로 검사 정책을 공유하기 좋음. 이 글의 탐지 경로는 서비스 API에 키를 보내 검증하지 않음 |
| TruffleHog | 시크릿 탐지와 API 기반 유효성 검증 | 공식 문서는 검증 성공·미검증·검증 오류를 구분. 실제 활성 키인지 확인하는 흐름을 함께 고려할 때 비교 |
| detect-secrets | 탐지 플러그인·베이스라인·감사 절차 | 기존 결과를 기준선으로 관리하고 사람이 분류하는 작업 흐름을 학습하기 좋음 |
경쟁 도구의 설명은 TruffleHog 공식 문서와 detect-secrets 공식 문서 기준입니다. 이 표는 속도나 정확도 순위가 아닙니다. 동일한 데이터·규칙·실행 조건으로 측정한 비교 수치는 없습니다.
main.go에서 cmd.Execute로 이어지고, 하위 명령마다 입력 소스를 선택합니다.README.md 첫머리는 기능 구현이 완료되어 새 기능을 병합하지 않으며,
향후 릴리스는 보안 패치 중심이고 개발자의 초점은 Betterleaks로 옮겨간다고 안내합니다.
이는 README의 유지 방침입니다. 트렌딩 순위를 활발한 신규 기능 개발의 증거로 해석하면 안 됩니다.
학습할 설계와 사용 가능한 기능은 풍부하지만, 새 기능 기여를 준비한다면 먼저 현재 기여 방침을 확인해야 합니다.
상시 실행하는 애플리케이션 서버 대신 Go 프로세스가 입력을 읽고 결과를 만듭니다.
go.mod의 모듈 경로는 현재 GitHub 조직명과 다른
github.com/zricethezav/gitleaks/v8이며, Go 지시문은 go 1.24.11입니다.
소스의 import를 따라갈 때는 이 모듈 경로를 기준으로 봐야 합니다.
| 영역 | 실제 의존성과 버전 | 소스에서 맡는 역할 |
|---|---|---|
| 명령 처리 | spf13/cobra v1.9.1 | cmd/의 하위 명령·플래그·실행 함수 연결 |
| 설정 해석 | spf13/viper v1.19.0 | TOML을 읽어 설정 구조체로 변환 |
| 키워드 검색 | BobuSumisu/aho-corasick v1.0.3 | 여러 규칙의 키워드를 함께 찾아 정규식 후보 축소 |
| 동시성 제한 | fatih/semgroup v1.2.0 | Git 패치·파일 처리 작업의 동시 실행 수 제한 |
| Git 패치 해석 | gitleaks/go-gitdiff v0.9.1 | 외부 Git 명령의 출력을 파일·추가 행으로 분해 |
| 압축 파일 | mholt/archives v0.1.5 | 압축 형식 식별·내부 파일 읽기 |
| 파일 유형 | h2non/filetype v1.1.3 | 파일 앞부분의 바이트로 형식 판별 보조 |
| 로그 | rs/zerolog v1.33.0 | 경로·커밋·규칙 ID를 붙인 로그 출력 |
| 대체 정규식 | wasilibs/go-re2 v1.9.0 | gore2regex 빌드 태그를 선택한 경우 사용 |
| 테스트 지원 | testify v1.10.0google/go-cmp v0.7.0 | 검증문과 구조체 비교. 제품 실행 기능과 구분해서 읽기 |
모두 go.mod에 선언된 버전입니다. 선언 버전은 실행 측정값이나 최신 버전 추천을 뜻하지 않습니다.
데이터베이스에 탐지 결과를 쌓는 구조도 아니며, 기본 결과는 메모리의 []report.Finding에 모입니다.
React·Vue 같은 SPA는 없습니다. 사용자 접점은 터미널이며,
charmbracelet/lipgloss v0.5.0으로 발견 결과의 색과 강조를 표현합니다.
정적 보고서는 report/template.go의 Go text/template과
Masterminds/sprig/v3 v3.3.0을 사용합니다.
| 접점 | 위치 | 실제로 읽을 내용 |
|---|---|---|
| 터미널 표시 | detect/utils.go | printFinding이 규칙·파일·행·지문을 표시 |
| 정적 보고서 | report_templates/ | basic.tmpl 등 5개 템플릿. 별도 웹 서비스와 구분 |
| 자동화용 출력 | report/ | JSON·CSV·JUnit·SARIF 직렬화 |
NewTemplateReporter는 Sprig 함수 중 env, expandenv,
getHostByName을 제거합니다. 한편 text/template은 HTML 자동 이스케이프를 제공하지 않습니다.
새 HTML 보고서를 설계할 때는 입력 문자열을 어떤 문맥에 출력하는지 별도로 검토해야 합니다.
| 파일 | 확인한 설정 | 학습 포인트 |
|---|---|---|
Dockerfile | golang:1.24 → alpine:3.22CGO_ENABLED=0 | 빌드 도구와 실행 환경을 분리하는 2단계 빌드. 실행 이미지에 Git·Bash·OpenSSH 클라이언트 설치 |
.goreleaser.yml | Darwin·Linux·Windows, 여러 CPU 아키텍처tags: [gore2regex] | 릴리스 빌드는 대체 정규식 엔진 선택. 버전 문자열은 링커 플래그로 주입 |
.github/workflows/test.yml | Go 1.25, Ubuntu·Windowsgo test -json ./... --race | 경쟁 상태 검사와 운영체제 차이를 CI에서 확인 |
.github/workflows/release.yml | 릴리스가 게시되면 컨테이너 빌드linux/amd64,linux/arm64 | QEMU·Buildx로 다중 아키텍처 이미지 배포 |
Makefile | 설정 생성·포맷·모듈 정리·빌드 연결 | make build는 단순 컴파일 이상의 작업. 버전 계산에는 태그 조회도 포함 |
의존성 파일의 Go 버전, CI의 Go 버전, 배포 이미지의 Go 버전은 각각 다른 약속이므로 따로 읽어야 합니다.
Docker 빌드 명령은 gore2regex 태그를 지정하지 않습니다.
따라서 이 Dockerfile 경로는 regexp/stdlib_regex.go를,
GoReleaser 설정은 regexp/wasilibs_regex.go를 선택합니다.
Go 버전과 빌드 태그는 같은 요리를 만드는 조리 기구와 같습니다. 재료 목록이 같아도 조리 기구가 다르면 결과를 비교할 조건이 달라집니다. 성능을 논할 때는 실행 파일을 어떤 경로로 만들었는지까지 기록해야 합니다.
아래 그림은 일반적인 텍스트 검사 경로입니다. 압축 파일은 별도의 내부 파일 읽기를 거친 뒤 같은 경로로 합류합니다. 화살표는 데이터 흐름이며, 각 상자가 모두 별도 프로세스라는 뜻은 아닙니다.
sources/source.go의 핵심 계약은 메서드 하나입니다.
구현체는 조각을 준비할 때마다 콜백인 yield를 부릅니다.
채널 전체를 탐지기 인터페이스로 노출하지 않고, 읽는 쪽의 차이를 이 계약 뒤에 둡니다.
// sources/source.go의 인터페이스
type FragmentsFunc func(fragment Fragment, err error) error
type Source interface {
Fragments(ctx context.Context, yield FragmentsFunc) error
}
sources.Fragment에는 Raw, Bytes, FilePath,
StartLine, CommitInfo 등이 있습니다.
DetectSource는 이것을 받아 DetectContext에 넘깁니다.
새로운 입력원을 추가한다면 먼저 이 형식으로 바꾸는 방법을 생각하면 됩니다.
sources/git.go:Git.Fragments는 텍스트 패치에서
textFragment.Raw(gitdiff.OpAdd)로 추가된 행을 꺼냅니다.
기본 Git 명령은 git -C <저장소> log -p -U0 --full-history --all --diff-filter=tuxdb입니다.
-U0은 변경 주변의 문맥 행을 0으로 설정합니다.
저장된 모든 커밋의 파일 전체를 매번 다시 여는 방식과 다릅니다.
sources/file.go:fileFragments는 기본 100 * 1_000바이트 버퍼로 읽고,
readUntilSafeBoundary가 최대 25 * 1_000바이트를 더 읽어 빈 줄 경계(공백·탭·CR을 사이에 허용한 두 LF)를 찾습니다.
sources/common.go의 주석에는 10KB라고 적혀 있지만 상수의 실제 값은 25,000입니다.
경계를 완벽하게 보장하는 알고리즘이 아니라, 문자열을 중간에서 자를 가능성을 줄이는 절충입니다.
여러 나라의 전원 플러그를 하나의 콘센트 규격으로 바꾸는 어댑터와 같습니다. 탐지기를 Git 전용으로 만들지 않았기 때문에 파일·파이프 입력에도 같은 규칙을 적용할 수 있습니다.
NewDetectorContext는 모든 규칙의 키워드로 Aho-Corasick 트라이를 만듭니다.
DetectContext는 현재 검사 문자열을 소문자로 바꾸고 그 사전을 검색합니다.
키워드가 없는 규칙은 항상 검사하고, 키워드가 있는 규칙은 그중 하나라도 발견될 때 정규식을 실행합니다.
키워드는 정답을 판정하는 규칙이 아니라, 정규식을 실행할 대상을 줄이는 사전 필터입니다. 검사 조각에 없는 키워드만 규칙에 넣으면 정규식이 정확해도 검사를 건너뛸 수 있습니다.
// cmd/generate/config/rules/github.go:GitHubPat의 핵심 필드
// 설명 문자열과 예외 목록 등은 생략
RuleID: "github-pat",
Regex: regexp.MustCompile(`ghp_[0-9a-zA-Z]{36}`),
Entropy: 3,
Keywords: []string{"ghp_"},
detectRule는 정규식 일치 구간을 찾고 원문 위치를 계산한 뒤,
SecretGroup이 지정되어 있으면 해당 캡처 그룹을 비밀값으로 선택합니다.
지정하지 않았는데 캡처 그룹이 있다면 첫 번째 비어 있지 않은 그룹을 사용합니다.
그래서 정규식 전체에 일치한 Match와 실제 비밀값인 Secret은 다를 수 있습니다.
그다음 detect/utils.go:shannonEntropy로 문자 분포를 계산합니다.
임계값이 0이 아니면 entropy <= r.Entropy인 후보를 버립니다.
GitHubPat의 3은 확률이나 신뢰도 3점을 뜻하지 않습니다.
AAAAAAAAAAAA는 반복이 많아 낮고,
A1B2C3D4E5F6는 문자가 다양해 더 높습니다.
높다고 반드시 시크릿이 아니며 낮다고 안전하지도 않습니다.
원래 의미는 평균 정보량이고, 여기서는 일부 규칙의 오탐을 줄이는 보조 조건으로 씁니다.전역·규칙별 허용 목록과 gitleaks:allow 표시도 탐지 결과를 제외할 수 있습니다.
processRequiredRules는 보조 규칙이 같은 조각에서 함께 발견되는지 확인하며, 설정된 행·열 거리 조건도 적용합니다.
이 복합 규칙은 같은 조각에서 문맥을 찾으므로, 파일이 나뉘거나 패치에서 문맥이 빠지면 결과도 달라집니다.
filter는 같은 행에서 더 구체적인 규칙이 찾은 비밀값에 포함되는 범용 규칙 결과를 줄입니다.
모든 중복을 전역적으로 제거하는 만능 집합 연산은 아닙니다.
또한 검사 문자열을 디코딩하면 codec.AdjustMatchIndex로 원문 좌표에 맞춥니다.
detect/detect.go:NewDetectorContext의 초기화에는
semgroup.NewGroup(ctx, 40)이 있습니다.
sources.Files.Fragments와 sources.Git.Fragments는 이 그룹에 작업을 넣고
sync.WaitGroup으로 완료를 기다립니다. 파일 수만큼 작업을 무제한으로 동시에 실행하는 설계가 아닙니다.
40은 CPU 코어 요구량이 아닙니다. 이 세마포어 그룹에서 동시에 실행하는 작업 수의 상한입니다.
규칙마다 고루틴을 하나씩 만드는 것도 아닙니다.
DetectContext의 규칙 루프와, 입력 소스 쪽의 동시 실행 위치를 구분해 읽어야 합니다.
| 공유 자원 | 구현 | 왜 필요한가 |
|---|---|---|
| 발견 목록 | AddFinding의 findingMutex | 여러 작업이 같은 슬라이스에 동시에 추가하는 문제 방지 |
| 검사한 커밋 집합 | addCommit의 commitMutex | 여러 패치가 같은 커밋 정보를 다룰 수 있음 |
| 처리 바이트 수 | atomic.Uint64 | 공유 카운터의 갱신 보호 |
| 중단 요청 | context.Context | 시간 제한·취소를 입력 읽기와 검사 루프에 전달 |
뮤텍스가 있다고 결과 순서까지 고정되지는 않습니다. 규칙은 map에 담기고 여러 입력 작업이 함께 진행됩니다. JSON 보고서의 행 순서보다 규칙 ID·파일·위치·개수로 결과를 비교하는 편이 적절합니다.
config/gitleaks.toml은 생성된 기본 설정입니다.
원본 규칙은 cmd/generate/config/rules/의 Go 함수들입니다.
cmd/generate/config/main.go가 이 함수들을 모으고,
cmd/generate/config/rules/config.tmpl을 통해 TOML로 내보냅니다.
utils.Validate는 탐지되어야 하는 예제에서 결과가 나오는지,
오탐 예제에서는 결과가 나오지 않는지 실제 탐지기 호출로 확인하는 함수입니다.
생성기의 main은 규칙 검증과 ID 중복 검사도 수행합니다.
CI는 go generate ./... && git diff --exit-code로 생성 결과가 저장된 파일과 같은지 확인합니다.
실행할 때는 cmd/root.go:initConfig가 설정 출처를 선택하고,
config/config.go:ViperConfig.Translate가 문자열 정규식을 컴파일하며 키워드를 정규화합니다.
외부 설정 모양과 내부 실행 구조를 나눈 점도 배울 만합니다.
maxExtendDepth = 2로 설정 확장의 깊이를 제한합니다.
마지막 출력은 report.Reporter 인터페이스의 Write로 연결됩니다.
cmd/root.go:findingSummaryAndExit는 보고서 출력을 요청한 경우 보고서를 쓴 뒤, 발견 결과가 있으면 기본 종료 코드 1로 프로세스를 종료합니다.
검사 오류도 1이 될 수 있으므로, 자동화에서는 종료 코드와 로그를 함께 읽어야 합니다.
README의 도움말·디코딩 설명은 --max-decode-depth 기본값을 0이라고 적지만,
cmd/root.go는 5로 등록합니다.
반면 NewDetectorContext 자체는 이 필드를 설정하지 않으므로 라이브러리 생성 직후 값은 0입니다.
CLI 초기화와 라이브러리 초기화를 같은 것으로 생각하면 안 됩니다.
README는 dir 별칭을 files·directory라고 설명하지만,
cmd/directory.go에는 file·directory가 있습니다.
detect/codec/encodings.go에는 README의 목록에 빠진 Unicode 디코더도 있습니다.
| 순서 | 파일·함수 | 읽고 답할 질문 |
|---|---|---|
| 1 | main.go:main | 프로그램은 어디서 시작하고 어떤 명령 계층으로 넘기는가? |
| 2 | cmd/stdin.go:runStdIn | 가장 단순한 입력은 탐지기에 어떻게 연결되는가? |
| 3 | sources/source.go | 모든 입력이 지켜야 하는 공통 계약은 무엇인가? |
| 4 | sources/file.go:fileFragments | 입력은 어떤 크기와 위치 정보로 나뉘는가? |
| 5 | detect/detect.go:DetectContext | 키워드와 규칙 루프, 디코딩의 순서는 무엇인가? |
| 6 | report/json.go:JsonReporter.Write | 구조체가 파일로 바뀌는 경계는 어디인가? |
첫 회독에는 범위가 넓은 cmd/root.go를 통째로 외우려 하지 않아도 됩니다.
표준입력 한 줄이 JSON 결과가 되는 흐름을 먼저 그린 뒤 Git 입력으로 넓히면 됩니다.
detect/git.go 등에는 이전 API와의 호환 경로도 있으므로 현재 CLI가 실제로 호출하는 함수를 출발점으로 삼으세요.
| 범위 | 파일 수 | 물리적 줄 수 |
|---|---|---|
| 전체 Go 파일 | 214 | 21,579 |
| 런타임 비테스트 Go | 48 | 6,469 |
| 생성기 영역 Go | 139 | 9,418 |
| 테스트데이터 Go | 10 | 215 |
_test.go | 19 | 5,915 |
전체는 Git이 추적하는 Go 파일이며 빈 줄·주석을 포함합니다.
런타임 비테스트는 cmd/generate/와 testdata/, _test.go를 제외한 수치입니다.
생성기 영역에는 테스트 파일도 들어 있어 일부 행은 서로 겹칩니다.
이 표를 단순 합산하면 안 되며, 테스트 줄 수가 곧 테스트 커버리지도 아닙니다.
cmd/generate/config/rules/github.go:GitHubPat은 고정 접두사가 있는 규칙을,
cmd/generate/config/rules/generic.go:GenericCredential은 변수명과 문자열을 함께 보는 범용 규칙을 보여줍니다.
두 접근의 차이를 이해한 뒤 관심 있는 서비스 규칙으로 확장하면 됩니다.
Source와 Reporter는 각각 입력과 출력의 경계를 정합니다.
전자는 조각을 내보내고, 후자는 발견 목록을 기록합니다.
Go는 명시적으로 구현을 선언하지 않아도 필요한 메서드가 있으면 인터페이스를 만족합니다.
// report/json.go의 컴파일 시 인터페이스 만족 검사
var _ Reporter = (*JsonReporter)(nil)
// report/report.go의 출력 계약
type Reporter interface {
Write(w io.WriteCloser, findings []Finding) error
}
실습 아이디어: 파일을 열지 않는 가상의 입력 소스를 종이에 설계하세요.
문자열 배열을 순서대로 Fragment로 내보낼 때 어떤 필드를 채워야 할까요?
파일 위치가 없다는 사실을 결과에서 어떻게 표현할지도 함께 정합니다.
키워드 검색은 후보 규칙을 좁히고, 정규식은 모양을 확인하며, 엔트로피는 일부 단순한 값을 걸러냅니다. Go 정규식 공식 문서는 입력 크기에 선형인 실행 시간을 설명합니다. 그렇더라도 규칙 수·디코딩 횟수·매치 수가 늘면 전체 비용은 커집니다. 정규식 한 번의 성질과 프로그램 전체의 성능은 구분해야 합니다.
실습 아이디어: 규칙에 올바른 키워드, 엉뚱한 키워드, 빈 키워드 목록을 각각 넣었을 때를 비교하세요.
탐지 여부가 바뀌는 이유를 DetectContext의 분기와 연결하면 사전 필터의 책임이 분명해집니다.
H = -Σ p × log2(p)입니다.
열두 문자가 모두 다르면 ASCII 예제의 값은 log2(12) ≈ 3.585입니다.
길이와 문자 종류에 영향을 받으므로 모든 토큰에 같은 임계값을 적용할 근거는 없습니다.shannonEntropy는 문자 수를 map[rune]int로 세면서 분모는
len(data)를 씁니다. Go에서 문자열의 len은 바이트 수입니다.
ASCII 시크릿과 다중 바이트 문자열에서 의미가 어떻게 달라질지 손으로 계산해 보세요.
변수명만 보고 “문자 수”라고 읽지 않는 연습이 됩니다.
NewGitLogCmdContext는 exec.CommandContext로 Git을 실행합니다.
Git 저장 형식을 직접 구현하는 대신 Git의 패치 출력과 전용 파서를 조합한 것입니다.
반면 파일 입력은 io.Reader를 읽으므로 표준입력도 같은 타입으로 받을 수 있습니다.
실습 아이디어: 같은 가짜 토큰이 파일에 남은 경우, 커밋 후 파일에서 지운 경우, 이력까지 없는 얕은 저장소인 경우를 표로 비교하세요. “검사 결과가 0”이라는 숫자가 세 경우에서 같은 보장을 뜻하는지 설명해 봅니다.
컨텍스트는 종료 요청을 전파하고, 세마포어 그룹은 동시 작업량을 제한하며, 뮤텍스는 공유 상태를 보호합니다.
서로 다른 문제를 푸는 도구들입니다.
DetectSource는 조각 콜백으로 받은 오류를 기록하고 다음 조각으로 진행하는 경로도 있습니다.
실습 아이디어: 실제 실행 없이 오류 경로를 색연필 세 색으로 표시해 보세요. 즉시 종료하는 곳, 오류를 반환하는 곳, 로그만 남기고 계속하는 곳을 구분합니다. 종료 코드만으로 검사 완전성을 판단하기 어려운 이유를 찾을 수 있습니다.
config/config_test.go:TestTranslate는 입력 설정·기대 결과·기대 오류를 묶은 표 형식 테스트입니다.
sources/common_test.go:Test_readUntilSafeBoundary는 LF·CRLF·공백·안전 경계가 없는 경우를 다룹니다.
detect/codec/decoder_test.go:TestDecode는 인코딩과 중첩 변환을 확인합니다.
실습 아이디어: 새 규칙을 만든다고 가정하고 탐지할 예제 3개와 제외할 예제 3개를 먼저 적으세요.
같은 값이 JSON·YAML·환경변수 문법에 들어갔을 때를 포함하면 문자열 하나보다 풍부한 명세가 됩니다.
GenerateSampleSecrets가 여러 설정 문법의 예제를 만드는 이유도 여기에 있습니다.
AddFinding의 지문은 파일 경로·규칙 ID·시작 행을 연결한 문자열이며,
커밋이 있으면 앞에 커밋 ID도 붙입니다.
내용 자체를 암호학적 해시로 만든 값이 아닙니다.
줄이 이동하면 같은 비밀값이라도 식별 문자열이 달라질 수 있습니다.
# 파일 검사 지문 형태
fixtures/demo.txt:demo-token:1
# Git 검사 지문 형태 — COMMIT은 실제 커밋 ID 자리
COMMIT:demo.txt:demo-token:1
detect/baseline.go:IsNew는 지문 하나만 비교하지 않고 위치·메시지·엔트로피 등 여러 필드를 직접 비교합니다.
report/sarif.go:SarifReporter.Write는 같은 발견 결과를 SARIF 2.1.0으로 표현합니다.
내부 식별 정책과 외부 교환 형식이 각각 필요하다는 예입니다.
좋은 학습 목표는 함수 이름을 외우는 것이 아니라, 입력 범위·판정 조건·예외·출력 사이의 계약을 설명하는 것입니다. 같은 설계는 로그 검사기, 코드 린터, 데이터 검증 CLI에도 응용할 수 있습니다.
README와 빌드 설정에는 CPU·RAM의 공식 최소 수치가 없습니다. GPU·모델 가중치·데이터베이스 서버는 이 탐지 경로의 필수 구성요소가 아닙니다. 실제 자원 사용량은 검사하는 파일·패치·규칙·디코딩·압축 깊이·발견 개수에 따라 달라집니다.
| 항목 | 확인 가능한 요구·지원 | 초보자용 해석 |
|---|---|---|
| 운영체제 | GoReleaser 설정에 Linux·macOS·Windows | 배포 파일의 운영체제와 CPU 아키텍처를 맞춰야 함 |
| Git | git 모드는 외부 git 명령 호출 | Git 실행 파일과 읽을 수 있는 로컬 이력 필요 |
| Go | 소스 빌드 기준 go 1.24.11테스트 CI는 1.25 | 완성된 실행 파일을 사용할 때 Go 설치가 반드시 필요한 것은 아님 |
| CPU·RAM | 공식 최소 수치 미기재 | 작은 학습 저장소부터 검사하고, 입력 규모를 늘리며 자원 사용량 확인 |
| 디스크 | Git 객체·작업 파일·보고서·선택적 임시 파일 | 전체 이력 확보와 압축 검사에서 여유 공간 고려 |
| 네트워크 | 일반 탐지 경로는 로컬 내용을 검사 | 설치·이미지 받기·이력 가져오기·CI 서비스 연결은 별도 작업 |
| 설정 | 커밋 b58d3f1의 CLI 기본값 | 설정할 때 볼 점 |
|---|---|---|
--max-target-megabytes | 0 | 크기 제한 없음. 양수면 큰 파일·조각을 건너뛸 수 있으므로 검사 범위 기록 |
--max-decode-depth | 5 | 인코딩 해제의 재귀 깊이. 0이면 해제 없이 원문만 검사 |
--max-archive-depth | 0 | 기본적으로 압축 내부 탐색 안 함. 디코딩 깊이와 별개의 기능 |
--timeout | 0 | 초 단위 시간 제한 없음. 제한에 걸리면 완전한 검사 결과로 간주하지 않기 |
--follow-symlinks | false | dir 전용. 켜도 디렉터리를 가리키는 심볼릭 링크는 건너뜀 |
sources.Files는 파일 전체 크기를 바이트 단위로 확인하지만,
detectRule의 크기 확인은 현재 문자열 길이를 1,000,000으로 나눈 값을 사용합니다.
서로 다른 계층의 검사이므로 모든 모드에 같은 정밀도의 전역 메모리 상한이 생기는 것은 아닙니다.
sources/file.go:extractorFragments는 ZIP·7z 입력이 임의 위치 읽기를 지원하지 않으면
임시 파일에 복사하는 경로가 있습니다.
또 발견 결과는 보고서를 쓰기 전까지 슬라이스에 쌓입니다.
따라서 100KB 입력 버퍼 하나만 보고 전체 메모리가 고정이라고 판단하면 안 됩니다.
cmd/diagnostics.go는 CPU·메모리 프로파일과 실행 추적을 지원합니다.
먼저 같은 커밋·규칙·입력 범위·빌드 태그로 조건을 맞추고 한 항목씩 바꿔 측정하세요.
선택적 HTTP 진단 모드는 localhost:6060에 프로파일 서버를 엽니다.
CLI 제품에 웹 프론트엔드가 있다는 뜻은 아닙니다.
--redact를 생략하면 비밀값이 결과에 남을 수 있습니다.
여기의 실습은 값을 가리지만 파일명·작성자·커밋 메시지 같은 메타데이터까지 전부 없애지는 않습니다.
실습 보고서는 가짜 데이터로 만들고, 실제 보고서를 공개 저장소에 올리지 마세요.
다음 명령은 Gitleaks 실행 파일이 준비된 독자를 위한 실습입니다.
Bash·zsh 계열 셸을 기준으로 하며, 보조 파일 생성에는 Python 3을 사용합니다.
실제 계정의 키는 필요 없습니다. 새로 만든 gitleaks-lab 안에서 순서대로 진행하고,
모든 예제는 임의로 정한 가짜 형식 DEMO_를 검사합니다.
발견 결과가 있으면 기본 종료 코드는 1입니다.
자동 종료 옵션을 켠 셸에서는 예상된 탐지 때문에 다음 줄이 실행되지 않을 수 있으므로 명령별로 확인하세요.
아래의 예상 결과는 규칙과 소스 흐름에서 도출한 것이며, 배포 버전 차이는 먼저 gitleaks version으로 기록합니다.
목표: 기본 규칙과 섞이지 않는 작은 실험 환경을 만듭니다.
demo.toml에는 기본 설정 확장을 넣지 않으므로 이 규칙 하나만 사용합니다.
mkdir -p gitleaks-lab/fixtures gitleaks-lab/reports
cd gitleaks-lab
gitleaks version
cat > demo.toml <<'EOF'
title = "학습용 가짜 토큰 검사"
[[rules]]
id = "demo-token"
description = "학습용 DEMO 토큰"
regex = '''DEMO_([A-Z0-9]{12})'''
secretGroup = 1
keywords = ["DEMO_"]
EOF
printf '%s\n' 'DEMO_A1B2C3D4E5F6' > fixtures/demo.txt
gitleaks dir fixtures --config demo.toml --redact \
--max-decode-depth 0 --report-format json --report-path reports/first.json
# 바로 직전 검사의 종료 코드를 확인한다. 예상: 1
printf '종료 코드: %s\n' "$?"
# 동일한 가짜 값을 표준입력으로 검사한다
printf '%s\n' 'DEMO_A1B2C3D4E5F6' | \
gitleaks stdin --config demo.toml --redact --max-decode-depth 0
완료 기준: 파일 검사에서 RuleID가 demo-token인 결과 1개,
StartLine 1, 가려진 Secret을 확인합니다.
표준입력도 결과가 나오지만 파일·커밋 메타데이터는 같은 방식으로 기대하지 않습니다.
생각할 질문: 정규식 전체는 접두사를 포함하는데 비밀값은 왜 열두 글자일까요?
detectRule의 캡처 그룹 선택 부분과 연결해 설명하세요.
목표: 사전 필터와 엔트로피 조건을 분리해서 이해합니다.
원본 demo.toml은 보존하고 복사본 두 개로 실험하세요.
keyword.toml을 만들고 keywords = ["NEVER_PRESENT_"]로 바꿉니다.entropy.toml을 만들고 원래 키워드는 유지한 채 entropy = 4.0을 규칙에 추가합니다.gitleaks dir fixtures --config keyword.toml --redact --max-decode-depth 0
gitleaks dir fixtures --config entropy.toml --redact --max-decode-depth 0
완료 기준: 첫 번째는 키워드 불일치로 정규식 실행 전에 제외됩니다. 두 번째는 캡처한 열두 글자의 엔트로피가 약 3.585이므로 4.0을 넘지 못합니다. 다른 오류가 없다면 두 검사 모두 발견 0개·종료 코드 0을 예상합니다.
추가 질문: 엔트로피 임계값을 낮추는 것이 언제 오탐을 늘리고 언제 미탐을 줄일까요? 높은 수치가 항상 좋은 설정이라는 생각을 버리고, 예제 집합으로 설명해 보세요.
목표: 현재 디렉터리 검사와 이력 검사의 보장 범위를 구분합니다. 다음 명령은 실습용 저장소에만 작성자 설정과 두 커밋을 만듭니다.
mkdir history-lab
cd history-lab
git init -q
git config user.name '학습자'
git config user.email 'learner@example.invalid'
printf '%s\n' 'DEMO_A1B2C3D4E5F6' > demo.txt
git add demo.txt
git commit -q -m '학습용 가짜 토큰 추가'
printf '%s\n' '공개 가능한 예제' > demo.txt
git add demo.txt
git commit -q -m '현재 파일에서 가짜 토큰 제거'
cd ..
gitleaks dir history-lab --config demo.toml --redact --max-decode-depth 0
gitleaks git history-lab --config demo.toml --redact --max-decode-depth 0
완료 기준: 현재 파일 검사에서는 0개, Git 이력 검사에서는 최초 추가 커밋의 결과 1개를 예상합니다. 삭제·변경한 두 번째 커밋이 과거의 Git 객체를 없애지 않았기 때문입니다.
생각할 질문: 깊이 1로 최신 커밋만 가진 저장소에서도 같은 결과를 기대할 수 있을까요? 보이지 않는 과거 이력까지 검사했다고 말할 수는 없습니다. 실제 자격 증명이었다면 파일 수정 외에 키 폐기·재발급 등 권한 자체의 처리도 필요합니다.
목표: 문자열을 풀어 읽는 일과 압축 내부 파일을 여는 일을 구분합니다.
gitleaks-lab에서 두 종류의 입력을 만듭니다.
python3 - <<'PY'
from pathlib import Path
import base64
import zipfile
fake = b'DEMO_A1B2C3D4E5F6'
Path('encoded.txt').write_text(base64.b64encode(fake).decode() + '\n')
with zipfile.ZipFile('bundle.zip', 'w', compression=zipfile.ZIP_DEFLATED) as z:
z.writestr('demo.txt', fake + b'\n')
PY
# Base64 원문만 볼 때와 한 번 해제할 때
gitleaks dir encoded.txt --config demo.toml --redact --max-decode-depth 0
gitleaks dir encoded.txt --config demo.toml --redact --max-decode-depth 1 \
--report-format json --report-path reports/decoded.json
# 압축 내부 탐색은 별도의 옵션이다
gitleaks dir bundle.zip --config demo.toml --redact --max-archive-depth 0
gitleaks dir bundle.zip --config demo.toml --redact --max-archive-depth 1 --verbose
완료 기준: Base64는 깊이 0에서 0개, 깊이 1에서 1개를 예상합니다.
ZIP은 압축 깊이 0에서 내부를 건너뛰고, 1에서 bundle.zip!demo.txt의 결과가 나옵니다.
디코딩 결과의 태그와 원문 좌표는 reports/decoded.json의 Tags·StartLine·StartColumn·EndColumn에서 확인하세요.
생각할 질문: 인코딩을 알아보는 정규식 자체에도 길이·문자 조건이 있습니다. 임의의 모든 암호문을 복원하는 기능이라고 해석하면 안 되는 이유를 설명하세요.
목표: “이전에 본 결과”와 “안전한 값”을 구분합니다.
과제 1의 fixtures/demo.txt만 검사하며 보고서는 reports/에 둡니다.
# 최초 결과 1개를 기준선으로 기록. 예상 종료 코드: 1
gitleaks dir fixtures --config demo.toml --redact --max-decode-depth 0 \
--report-format json --report-path reports/base.json
# 같은 입력과 같은 가림 정책. 예상 발견: 0개
gitleaks dir fixtures --config demo.toml --redact --max-decode-depth 0 \
--baseline-path reports/base.json
# 새 행에 다른 가짜 토큰 추가
printf '%s\n' 'DEMO_Z9Y8X7W6V5U4' >> fixtures/demo.txt
# 신규 결과만 보고서에 기록. 예상 발견: 1개
gitleaks dir fixtures --config demo.toml --redact --max-decode-depth 0 \
--baseline-path reports/base.json \
--report-format json --report-path reports/new.json
완료 기준: 신규 보고서에 두 번째 행만 남는 이유를 IsNew의 필드 비교로 설명합니다.
가린 상태에서는 비밀값 비교를 생략하는 조건도 읽어보세요.
같은 위치의 값만 바꾸는 실험을 더하면 베이스라인의 의미를 더 정확히 이해할 수 있습니다.
심화 설계: .gitleaksignore 지문, 허용 목록, 베이스라인 중 무엇을 사용할지 상황별로 표를 만드세요.
예외의 근거·적용 범위·재검토 시점을 적되, 신규 기능 기여 여부는 현재 유지 방침과 따로 판단합니다.
실습에서 가장 중요한 기록은 발견 개수와 함께 남긴 검사 범위·설정·버전입니다. 결과가 예상과 다르면 기본값, 설정 출처, 경로, 이력 깊이, 허용 목록 순서로 확인하세요.
주당 3~5시간을 가정한 학습 제안입니다. 공식 소요 시간이 아닙니다. Go 문법을 처음 접한다면 1주차를 늘리고, 이미 CLI를 만들어 봤다면 3주차부터 시작해도 좋습니다.
| 주차 | 배울 기술·읽을 위치 | 남길 결과물 |
|---|---|---|
| 1주차 | Go 구조체·슬라이스·인터페이스main.go, sources/source.go, report/finding.go | 표준입력 한 줄이 발견 구조체가 되는 흐름 그림과 필드 설명 |
| 2주차 | Cobra 명령·플래그, Viper와 TOMLcmd/root.go, config/config.go | 설정 우선순위 표와 과제 1의 전용 규칙 |
| 3주차 | 정규식·캡처 그룹·키워드 사전·정보량detectRule, cmd/generate/config/rules/github.go, cmd/generate/config/rules/generic.go | 탐지·오탐·미탐 예제표와 과제 2의 원인 설명 |
| 4주차 | Git 패치·스트리밍·디코딩 좌표sources/git.go, sources/file.go, detect/codec/ | 현재 파일과 이력의 비교표, 과제 3·4의 관찰 기록 |
| 5주차 | 컨텍스트·동시성 제한·뮤텍스·테스트NewDetectorContext, sources/common_test.go | 공유 상태 목록과 오류·취소 경로 그림 |
| 6주차 | 베이스라인·SARIF·생성 코드·CIdetect/baseline.go, report/sarif.go, .github/workflows/test.yml | 과제 5의 비교 결과와 팀에 적용할 검사 정책 초안 |
Gitleaks 전체를 복제하기보다, 학습용 로그에서 금지된 패턴을 찾는 CLI를 설계해 보세요. 입력은 파일·표준입력 두 가지, 결과는 JSON 하나로 시작합니다. 규칙·입력·보고서를 서로 분리하고, 오류와 발견을 구분하는 계약을 먼저 적습니다.
“명령을 실행했다”보다 “왜 이 행은 탐지되고 저 행은 제외되는가”를 설명해 보세요. 함수·설정값·테스트 예제를 근거로 답할 수 있으면 다음 주제로 넘어갈 준비가 된 것입니다.
| 용어 | 쉬운 설명 |
|---|---|
| 시크릿 | 공개되면 안 되는 값. API 키·비밀번호·토큰 등이 대표적입니다. |
| 하드코딩 | 값을 프로그램이나 설정 파일 안에 직접 적는 일입니다. |
| CLI | 터미널의 명령과 옵션으로 사용하는 프로그램입니다. |
| 표준입력 | 프로세스가 입력을 받는 기본 통로. 파일이나 다른 명령 출력을 연결할 수 있습니다. |
| 스테이징 | 다음 커밋에 넣을 변경을 Git 인덱스에 골라 놓는 단계입니다. |
| 패치·diff | 파일 사이의 차이. Git 모드는 패치에서 추가된 텍스트를 검사합니다. |
| 얕은 이력 | 최근 일부 커밋만 가진 이력. 없는 과거까지 검사할 수는 없습니다. |
| Fragment | 본문과 경로·시작 행·커밋 정보를 묶은 검사 조각입니다. |
| 규칙 ID | 어떤 탐지 조건에서 결과가 나왔는지 식별하는 이름입니다. |
| 정규식 | 문자열의 모양을 표현하는 문법. 접두사·길이·허용 문자를 검사합니다. |
| 캡처 그룹 | 정규식의 괄호로 뽑는 부분. 전체 일치에서 비밀값만 고를 때 씁니다. |
| Aho-Corasick | 여러 키워드를 함께 찾는 알고리즘. 여기서는 검사할 규칙을 줄입니다. |
| 트라이 | 문자열의 접두사를 공유하는 나무 형태의 자료구조입니다. |
| 엔트로피 | 문자의 분포를 정보량으로 요약한 수치. 일부 규칙의 보조 조건입니다. |
| 오탐·미탐 | 시크릿이 아닌데 경고하면 오탐, 실제 시크릿을 놓치면 미탐입니다. |
| 허용 목록 | 특정 경로·값·커밋 등을 검사나 결과에서 제외하는 정책입니다. |
| 베이스라인 | 이전에 발견한 결과의 기준 목록. 기존 결과를 신규 보고에서 빼는 데 씁니다. |
| 지문 | 파일·규칙·행·선택적 커밋으로 만든 식별 문자열. 비밀값의 해시는 아닙니다. |
| Redact | 결과에서 비밀값을 가리는 처리. 해당 키를 폐기하는 작업과 다릅니다. |
| 고루틴 | Go의 동시 실행 단위. CPU 코어 하나와 일대일로 대응하지 않습니다. |
| 뮤텍스 | 여러 작업이 같은 데이터를 동시에 바꾸지 못하도록 잠그는 장치입니다. |
| 컨텍스트 | 시간 제한·취소 같은 제어 정보를 함수 호출 사이에 전달하는 값입니다. |
| 빌드 태그 | 컴파일할 파일을 조건에 따라 선택하는 표시. 정규식 구현 교체에 사용합니다. |
| go:embed | 파일 내용을 컴파일한 실행 파일 안에 포함하는 Go 지시문입니다. |
| SARIF | 정적 분석 결과를 도구끼리 주고받기 위한 표준 형식. 이 구현은 2.1.0을 출력합니다. |
| CI·생성물 검증 | 변경마다 검사를 자동 수행하고, 생성한 설정과 저장된 설정의 차이도 확인하는 과정입니다. |
영어 식별자를 모두 번역해서 기억할 필요는 없습니다.
Source는 조각을 만들고, Detector는 발견을 만들며,
Reporter는 결과를 기록한다는 세 문장을 출발점으로 삼으세요.
| 항목 | 값과 기준 |
|---|---|
| 저장소 | gitleaks/gitleaks · 설명: Find secrets with Gitleaks 🔑 |
| 트렌딩 | GitHub Trending Go Weekly #3 · 2026-10-23 |
| 스타 / 포크 | 30,253 / 2,311 · 2026-10-23 GitHub 기준 |
| 생성일 | 2018-01-27 |
| 주 언어 / 라이선스 | Go / MIT |
| 최신 릴리스 | v8.30.1 · 2026-03-21 · 2026-10-23 GitHub 기준 |
| 최근 push | 2026-09-30 · 2026-10-23 GitHub 기준 |
| 코드 기준 커밋 | b58d3f1 · 2026-07-22GitHub Actions 의존성 갱신 PR #2197 병합. 10월의 기본 브랜치 최신 커밋이라는 뜻은 아님 |
| 전체 추적 파일 | 454개 · 분석 커밋의 파일 목록 기준 |
| Go 코드 규모 | 214개 파일 / 21,579줄. 비테스트 195개 / 15,664줄, 테스트 19개 / 5,915줄 |
| 설정·문서 규모 | TOML 52개 / 4,008줄 · YAML 8개 / 265줄 · Markdown 13개 / 902줄 |
| 보조 코드·템플릿 | Python 1개 / 27줄 · 셸 1개 / 80줄 · 보고서 템플릿 5개 / 3,890줄 |
| 기본 탐지 규칙 | config/gitleaks.toml의 [[rules]] 222개. 서비스 수와 같지 않음 |
| 빌드 기준 | go.mod: 1.24.11 · 테스트 CI: 1.25 · Docker 빌드: 1.24 |
| 유지 방침 | README 기준 신규 기능 병합 종료·보안 패치 중심, Betterleaks로 개발 초점 이동 |
줄 수는 해당 확장자의 추적 파일을 대상으로 빈 줄·주석을 포함해 센 물리적 줄 수이며, 끝에 개행이 없어도 마지막 행을 포함합니다.
.git 내부 데이터는 제외하며, Go에는 생성기와 테스트데이터가 포함됩니다.
보고서 템플릿은 HTML·CSS 등의 혼합 내용이므로 순수 HTML 또는 JavaScript 줄 수로 분리하지 않았습니다.
전체 파일 수에는 위 표에서 따로 집계하지 않은 확장자의 파일도 포함됩니다.
이 글의 코드는 커밋 b58d3f1 기준이며, 2026-10-23 GitHub 메타데이터와 시점이 다릅니다.
릴리스 실행 파일·이후 코드가 같은 기본값과 동작을 가진다고 보장하지 않습니다.
신규 기능 종료 안내와 README의 기본값 불일치도 함께 고려하세요.
키워드·정규식·엔트로피 기반 검사는 오탐과 미탐이 있습니다. 서비스 API로 자격 증명의 활성 여부를 확인하거나, 발견한 키를 자동 폐기하는 경로는 여기서 설명한 탐지 흐름에 없습니다. 압축 깊이·파일 크기 제한·허용 목록·조각 경계·얕은 Git 이력은 검사 범위를 바꿀 수 있습니다.
베이스라인은 과거 결과를 숨기는 기준이지 안전 판정이 아닙니다. 오류 로그와 부분 검사 여부를 함께 읽어야 하며, 발견 0개만으로 유출이 전혀 없었다고 결론 내릴 수 없습니다. 제시한 실습의 예상 결과는 소스와 규칙에서 도출한 값으로, 성능·메모리·테스트 커버리지의 실측 결과는 아닙니다.