gitleaks/gitleaks
detect
Active contributors: Zachary Rice, Richard Gomez
Purpose
detect/ is the heart of Gitleaks. It owns the Detector type, the per-fragment scan loop, allowlist evaluation, baseline filtering, the recursive-decoding loop, and composite-rule (multi-part) handling.
Directory layout
detect/
├── detect.go # Detector type, DetectSource/DetectContext/Detect, rule loop, allowlist eval
├── baseline.go # Load + filter against an existing JSON report
├── codec/ # Decoder for base64/hex/percent (separate page)
├── files.go # Deprecated wrapper over sources.Files (kept for v8 API compat)
├── git.go # Deprecated wrapper for legacy callers
├── location.go # Byte-offset → line/column resolver
├── reader.go # io.Reader → Detector helper
└── utils.go # Shannon entropy, SCM link generation, terminal printingKey abstractions
| Symbol | File | Description |
|---|---|---|
Detector |
detect/detect.go |
Holds compiled config, prefilter trie, semaphore, accumulated findings |
NewDetector(cfg) / NewDetectorContext(ctx, cfg) |
detect/detect.go |
Construct a detector |
NewDetectorDefaultConfig() |
detect/detect.go |
Detector preloaded with the embedded default config |
Detector.DetectSource(ctx, source) |
detect/detect.go |
Scan every fragment a source yields, return findings |
Detector.DetectContext(ctx, fragment) |
detect/detect.go |
The main per-fragment scan loop |
Detector.detectRule(...) |
detect/detect.go |
Apply one config.Rule to one fragment |
Detector.processRequiredRules(...) |
detect/detect.go |
Composite-rule logic |
Detector.AddFinding(f) |
detect/detect.go |
Compute fingerprint, apply .gitleaksignore/baseline filters, append |
Detector.AddGitleaksIgnore(path) |
detect/detect.go |
Load a .gitleaksignore file |
Detector.AddBaseline(path, source) |
detect/baseline.go |
Load + remember a JSON baseline |
IsNew(finding, redact, baseline) |
detect/baseline.go |
Field-level equality check used to filter baseline-known findings |
shannonEntropy(s) |
detect/utils.go |
Per-secret entropy computation |
createScmLink(remote, finding) |
detect/utils.go |
Build a clickable URL into the offending line on GitHub/GitLab/etc. |
filter(findings, redact) |
detect/utils.go |
Dedupe overlapping generic vs specific rules and apply redaction |
printFinding(f, noColor) |
detect/utils.go |
Verbose-mode pretty printer |
Fragment (deprecated alias) |
detect/detect.go |
Type alias for sources.Fragment; v9 removes |
How it works
The scan pipeline funnels every input through DetectContext. The high-level lifecycle:
sequenceDiagram
participant Caller as cmd/git.go
participant Det as Detector
participant Src as sources.Source
participant Yield as DetectContext
Caller->>Det: DetectSource(ctx, source)
Det->>Src: source.Fragments(ctx, yield)
loop per fragment
Src->>Det: yield(fragment, err)
Det->>Yield: DetectContext(ctx, fragment)
Yield->>Det: []report.Finding
Det->>Det: AddFinding(each)
end
Det-->>Caller: []report.Finding, errThe main loop in DetectContext
The function in detect/detect.go does this for every fragment:
- Skip the fragment if its file path matches
Config.PathorbaselinePath(so the config and baseline files don't trigger themselves). - Skip if any global allowlist matches the path/commit.
- Build a keyword presence map by running the global Aho-Corasick
prefilterover the fragment's lowercased content. The trie was built once atDetectorconstruction time from every keyword across every rule. - For each rule:
- If the rule has zero keywords, run it unconditionally.
- Otherwise, run it only if at least one of its keywords is in the presence map.
- After all rules have run on the current text, optionally decode encoded segments (base64/hex/percent) and recurse — up to
MaxDecodeDepthpasses. Decoded values are spliced in place; rule findings against decoded segments are tagged withdecoded:<encoding>anddecode-depth:<depth>(see features/decoding). - Pass the final findings through
filter(), which dedupes a generic-rule finding when a more specific rule matched the same line+secret, and applies redaction.
detectRule
For a single rule on a single fragment (detect/detect.go):
- Honor
SkipReport(used by composite rules' auxiliaries). - Run rule allowlist commit/path checks; return early if the rule is allowlisted for this commit/path.
- If
Pathis set:- Path-only rule (no
Regex): match the path; produce afile detected:finding. - Path + Regex: skip if the path doesn't match.
- Path-only rule (no
- Skip if
MaxTargetMegaBytesis set and the fragment exceeds it. - Run
Regex.FindAllStringIndexover the (possibly decoded) text. - For each match:
- Compute line/column via
location()(detect/location.go). - Apply encoded-segment overlap logic (
codec.SegmentsWithDecodedOverlap,codec.AdjustMatchIndex). - Build a
report.Finding. - Honor inline
gitleaks:allowmarkers (unlessIgnoreGitleaksAllow). - Extract the secret from the configured
SecretGroup(or the first non-empty capture group). - Compute Shannon entropy and apply the rule's
Entropythreshold if set. - Run the rule's content allowlists (regex, stopwords) via
checkFindingAllowed.
- Compute line/column via
- If the rule has
RequiredRules, hand the primary findings toprocessRequiredRules.
Allowlist evaluation
Two helpers split the work:
checkCommitOrPathAllowedruns at fragment intake, before any regex evaluation. It checks the commit SHA and the file path; withMatchCondition=AND, regex/stopword conditions on the same allowlist are skipped here and re-evaluated later.checkFindingAllowedruns against an extracted finding. It checks the rule's content allowlists (regex againstmatch/line/secret, plus stopwords). ForANDallowlists, it also re-evaluates commit/path so the full conjunction is checked.
Composite (required) rules
processRequiredRules (detect/detect.go) runs once per primary rule that has RequiredRules. It:
- Collects findings for every required rule against the same fragment, marking them as
InheritedFromFindingto prevent infinite recursion. - For each primary finding, checks
withinProximityagainst the collected required findings using the rule'sWithinLines/WithinColumnsconstraints. - Drops any primary finding that doesn't have at least one required finding for every required rule.
- Attaches the satisfied required findings to the surviving primary finding via
Finding.AddRequiredFindings.
See features/composite-rules for the user-facing semantics.
Fingerprints, .gitleaksignore, and baseline
AddFinding does three filters in order:
- Compute the global fingerprint
<file>:<rule-id>:<line>and (if commit is present) the commit fingerprint<commit>:<file>:<rule-id>:<line>. Skip if either matches the loaded.gitleaksignoreset. - If a baseline was loaded, run
IsNew(finding, redact, baseline)and skip if equal to a baseline entry. - Append under
findingMutex. IfVerboseis on, pretty-print to stdout.
Integration points
- Receives:
sources.Fragmentfromsources/. Thesources.Sourceinterface is the only intake. - Reads:
config.Config(rules, allowlists, ordered rule list, keyword set). - Writes: appends to internal
findingsslice; the caller drains it viaDetector.Findings()orDetectSource's return value. - Calls into
codec/:codec.NewDecoder,codec.SegmentsWithDecodedOverlap,codec.AdjustMatchIndex,codec.Tags,codec.CurrentLine. - Calls into
cmd/scm/:createScmLinkswitches onscm.Platformto render finding URLs. - Concurrency: exposes
Detector.Sema(a*semgroup.Groupwith limit 40). Sources schedule fragment work onto it.
Entry points for modification
- Adding a new per-fragment filter or transformation: insert it into
DetectContextbefore the rule loop or between rules and the decode pass. - Changing how a finding is built:
detectRuleis the place. Be careful withlocboundaries whencurrentRawdiffers fromfragment.Rawdue to decoding. - Changing fingerprint format:
AddFindingdefines both fingerprint shapes. Be aware that changing the format silently invalidates every user's baseline and.gitleaksignore; the existing code carefully pre-computes both shapes for compatibility. - Changing concurrency: the sema is constructed in
NewDetectorContextwith hardcoded limit 40. If you need a different value, parameterize it. - Adding a new finding output channel (e.g., a streaming hook): currently the only path is
findingsslice + verbose stdout. A streaming callback would slot in next toAddFinding.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.