gitleaks
On this page
- Control:
secrets - Tool: Gitleaks, https://github.com/gitleaks/gitleaks
- Status: ✅ implemented
- Target: source repository (
RepositoryTarget), checked out at the scanned revision - License / terms: MIT (permissive). Run via exec.
What it does
Checks out the component's repository, then runs gitleaks dir <dir> --report-format sarif --report-path <file> --exit-code 0 --no-banner --log-level trace to find leaked credentials, API
keys, tokens, private keys, in the working tree. See the secret-detection glossary
entry. A second run with
--ignore-gitleaks-allow records what gitleaks:allow comments set aside; see
Exclusions. The trace level is what names the files Gitleaks skipped; see
Skipped.
--exit-code 0 keeps the process successful even when secrets are found; findings live in
the SARIF report, not the exit code. The secrets controller
decides severity.
Saga options
config:
controls:
secrets:
gitleaks:
config: security/gitleaks.toml # relative to where Draugr runs
history: true # the commit history as well as the tree
| Option | What it does |
|---|---|
config | Path to a Gitleaks TOML rules file, for a ruleset shared across repositories. It replaces a repository's own .gitleaks.toml; Draugr's own rules are still added. See Repository rules. |
history | Scan the commit history as well as the tree. Off by default. |
history: true adds a second gitleaks git pass alongside the gitleaks dir pass, and
switches the checkout from a shallow clone to a full one. Both, together: gitleaks git over a
shallow clone walks a single commit and reports clean, which is indistinguishable from a repository
whose history holds nothing. It also turns off the sparse and partial-clone optimizations, because
those leave historical blobs unfetched, a scan that walks commits it cannot read finds nothing in
them.
The tree pass is kept rather than replaced. gitleaks git reports the path a secret had in the
commit that introduced it, so a file since renamed is reported under a directory that no longer
exists. Findings from the history pass are marked historical in the report, and the tree pass is
what names the path a live secret is at now. A history finding keeps the commit it was found at;
the commit's author, email, date and message, which Gitleaks reports beside it, are left out.
A secret still in the tree is found by both passes and reported once, as the tree finding. A history finding is the same secret as a tree finding when both have the same rule, the same path and the same secret value. The line is not compared, so a secret that has moved within the file still matches. A secret at another path, or an older value replaced at the same path, is still reported from history.
The history pass reads every commit in the repository, because git history cannot be checked out
by subtree. A history finding is kept only when the path it names would have been checked out
under the component's
paths and ignore. Two components
sharing a repository each see the secrets committed under their own paths, and a secret committed
under another component's paths is reported there.
Repository rules
A .gitleaks.toml at the root of a scanned repository applies to that repository's tree and
history scans: its rules, its allowlist, and whether it extends Gitleaks' built-in rules
([extend] useDefault = true). Without that setting the built-in rules are not used. The file
applies even when the component's paths do not include it. Each repository in a run is scanned
with its own file, or with the built-in rules where it has none.
config replaces the repository's file, as --config does when Gitleaks runs by hand. Use it for
an organization-wide ruleset that lives outside every repository.
A .gitleaks.toml that is a link is read only when it resolves to a file inside the repository. A
link that points outside it or to nothing, or a directory, is not read; the repository is scanned
with the built-in rules and the run logs a warning.
Draugr's own rules
Every scan runs a ruleset Draugr composes, which extends whatever the scan would otherwise have
used: the file config names, the repository's .gitleaks.toml, or Gitleaks' built-in rules.
Draugr's own rules are added to whichever it is.
Today that is one rule, for Draugr's ingest token: the credential a pipeline presents to publish a run. A tool that scans for other people's leaked credentials while issuing unrecognizable ones of its own is holding itself to the lower standard, so the token carries a fixed marker and this recognizes it. A match is reported only above 3.5 bits of entropy per character, so one character repeated after the marker is not reported.
Extended rather than replaced, in both directions. Replacing your ruleset would lose your rules; skipping ours when you have one would leave the token undetected in exactly the repositories that have been configured most carefully.
The composed file is written under ~/.draugr/data/gitleaks/. If it cannot be written, a machine
with no writable home, the scan runs with the ruleset it would have extended. One rule is worth
losing; the secrets control is not.
Exclusions
A secret on a line carrying a gitleaks:allow comment is reported suppressed with origin: tool.
Gitleaks leaves such a line out of every report format, so each pass runs a second time with
--ignore-gitleaks-allow, and a secret only the second run finds is the one the comment set aside.
On a Gitleaks older than 8.18.1, which has no such flag, each pass runs once and the comment leaves
no record.
A comment added to a line after its secret was committed covers the tree. The commit that introduced the secret without it is still reported by the history pass.
.gitleaksignore leaves no record. On the history pass, Gitleaks applies it and reports nothing of
what it excluded. On the tree pass, the checkout is named by its absolute path and the file holds
repository-relative fingerprints, so no entry matches and a secret it names is still reported.
config.exclude keeps the finding and the reason.
Skipped
Gitleaks judges by a file's name, not its content, whether the file is an archive, and skips every
file so named without reading it. A name counts when it contains an archive extension such as
.zip, .tar, .gz or .br anywhere, so notes.zip.txt is skipped, and so is
release.brief.md, for .br. A secret in either is not looked for, and the run reports no leaks.
The rule is in Gitleaks' source
at the version Draugr installs.
The SARIF report does not list these files. Gitleaks names each one in its log at trace level, so
the tree pass runs with --log-level trace and each file named gets an unread-files caveat with
the reason name has an archive extension. Where Gitleaks reports scanning 0 bytes of a tree that
holds a file with content, and names no file it skipped, the repository root . gets
no files read. The secrets control shows the caveat.
Rename notes.zip.txt to notes.txt and it is read. To fail the run on a skipped file, set
config.gate.failOnCaveats: [unread-files].
A real archive such as bundle.zip is not opened either, so it is named too. A real archive whose
name holds no archive extension is skipped as a binary file and is not named.
The history pass reads the text of each commit whatever the file is called, so with history: true
the commits of a text file such as notes.zip.txt are searched. The tree pass still did not read
the file, and the caveat stays. A binary file named as an archive is skipped by both passes.
Draugr does not relay Gitleaks' trace or debug lines, which can hold candidate secret values.
Links
- Gitleaks: https://github.com/gitleaks/gitleaks
- SARIF report format: https://github.com/gitleaks/gitleaks#sarif
Notes
- Integration mode: exec over a local checkout; Gitleaks +
gitmust be onPATH. - Gitleaks' SARIF output omits
levelon results. Draugr's SARIF parser defaults an absent level towarning(per the SARIF 2.1.0 spec). Thesecretscontroller reports every finding aterrorand keeps thatwarningas the finding's rating, so its severity readsmediumand the control's minimum severity holds it athigh. A leaked secret is always gate-failing. - Module path caveat: install via
github.com/zricethezav/gitleaks/v8(the module's declared path), notgithub.com/gitleaks/gitleaks/v8.
Data
Nothing. Gitleaks' rules are compiled into its binary, so an upgrade of the tool is the only thing that changes what it finds, and that is what the cache key records.