gitleaks/gitleaks
Architecture
Gitleaks is a single-binary CLI built on Cobra. All scan modes share one detection pipeline: a sources.Source produces sources.Fragment values, the detect.Detector runs every configured rule against each fragment, and a report.Reporter writes the findings.
Top-level layout
gitleaks/
├── main.go # entry point, calls cmd.Execute()
├── cmd/ # Cobra commands: git, dir, stdin, detect, protect, version
│ ├── root.go # persistent flags, Detector construction
│ ├── git.go # `gitleaks git ...`
│ ├── directory.go # `gitleaks dir ...`
│ ├── stdin.go # `gitleaks stdin`
│ ├── detect.go # legacy hidden command
│ ├── diagnostics.go # CPU/mem/trace pprof, http pprof
│ ├── scm/ # SCM platforms (GitHub, GitLab, …) for finding links
│ └── generate/ # offline tool that regenerates config/gitleaks.toml
├── config/ # rule + allowlist parsing, default config
│ └── gitleaks.toml # embedded default rules (~220 rules)
├── detect/ # detection engine
│ ├── detect.go # Detector type, DetectContext, rule loop
│ ├── baseline.go # ignore findings already in a baseline report
│ ├── codec/ # base64/hex/percent decoding for nested secrets
│ ├── files.go, git.go # deprecated wrappers (kept for v8 API stability)
│ ├── reader.go # streaming reader → Detector
│ └── utils.go # entropy, link generation, terminal printing
├── sources/ # sources of fragments
│ ├── git.go # `git log -p` / `git diff` parsing
│ ├── files.go # filesystem walker
│ ├── file.go # single file/stream + archive extraction
│ └── fragment.go # the Fragment type
├── report/ # output formats: json, csv, junit, sarif, template
├── regexp/ # build-tag selectable regex backend (stdlib vs go-re2)
├── logging/ # zerolog wrapper
└── version/ # ldflags-injected version stringData flow
The scan pipeline is simple: a Source yields Fragments, and the Detector checks each fragment against every rule.
graph LR
User["gitleaks git/dir/stdin"] --> Cmd["cmd/*.go"]
Cmd -->|builds| Cfg[config.Config]
Cmd -->|builds| Det[detect.Detector]
Cmd -->|chooses| Src[sources.Source]
Src -->|sources.Fragment| Det
Det -->|rules + allowlists| Det
Det -->|report.Finding| Out[report.Reporter]
Out --> File[report file<br/>json/csv/sarif/...]
Det -->|verbose| TUI[stdout pretty-print]sources.Source is a one-method interface (sources/source.go):
type Source interface {
Fragments(ctx context.Context, yield FragmentsFunc) error
}Three concrete implementations exist:
sources.Git(sources/git.go) parses the streaming output ofgit log -p(orgit diff) usinggitleaks/go-gitdiff. For binary blobs that look like archives, it streams the blob viagit cat-file bloband feeds it back throughsources.File.sources.Files(sources/files.go) does afilepath.WalkDirand turns every readable file into asources.File.sources.File(sources/file.go) reads a singleio.Readerin 100KB chunks, splitting on whitespace boundaries to avoid cutting secrets in half. It also detects archive types viamholt/archivesand recurses into them up to--max-archive-depth.
Detection loop
Detector.DetectContext (detect/detect.go) does the actual work for one fragment:
graph TD
F[Fragment] --> A{File path or commit<br/>in global allowlist?}
A -->|yes| Skip[return]
A -->|no| K[Aho-Corasick prefilter:<br/>which rule keywords appear?]
K --> R[for each rule]
R --> M{Rule keywords match<br/>or rule has none?}
M -->|no| R
M -->|yes| Re[apply rule.Regex]
Re --> S{secretGroup, entropy,<br/>allowlists pass?}
S -->|no| R
S -->|yes| Add[append finding]
Add --> R
R -->|done| D{depth < MaxDecodeDepth<br/>and segments found?}
D -->|yes| Dec[codec.Decode<br/>recurse]
Dec --> R
D -->|no| End[filter & redact]Two performance choices show up here:
- Keyword pre-filter. All rules' keywords are compiled into a single Aho-Corasick trie at
Detectorconstruction time (prefilterindetect/detect.go). Each fragment is matched against the trie once, and a rule's regex only runs if at least one of its keywords matched. This avoids running ~220 regexes against every line. - Concurrency. A
semgroup.Group(fatih/semgroup) caps fragment-level concurrency at 40. Eachsources.Sourceschedules its work onto this shared semaphore.
Build-time pluggable regex engine
The regexp/ package picks a regex backend at compile time using a build tag:
| Build tag | Backend | File |
|---|---|---|
| (default) | Go's standard regexp (RE2-based, pure Go) |
regexp/stdlib_regex.go |
gore2regex |
wasilibs/go-re2 — RE2 via WebAssembly for higher throughput |
regexp/wasilibs_regex.go |
Every package that compiles regexes does so through regexp.MustCompile from this internal package, never the standard library directly.
Config lifecycle
graph LR
Default[config/gitleaks.toml<br/>go:embed in config/config.go]
User[user-supplied .gitleaks.toml]
Env[GITLEAKS_CONFIG_TOML env var]
Default --> Viper[viper.ReadConfig]
User --> Viper
Env --> Viper
Viper --> VC[ViperConfig struct]
VC -->|.Translate| Cfg[config.Config<br/>compiled regexes,<br/>Aho-Corasick trie of keywords]
Cfg --> Det[detect.Detector]The default ruleset is generated offline by cmd/generate/config/main.go, which assembles ~220 individual rule files from cmd/generate/config/rules/*.go into the embedded config/gitleaks.toml. See Rule generation for how to add a new rule.
Outputs
report.Reporter is implemented by:
report.JsonReporter— default for many usersreport.CsvReporterreport.JunitReporterreport.SarifReporter— the format consumed by GitHub code scanningreport.NewTemplateReporter— Gotext/template(withMasterminds/sprig) for arbitrary output
The reporter is selected from --report-format (or inferred from --report-path extension). See systems/report.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.