
Gitleaks detects the secrets exposed in your Git repositories, files and archives. Fast and highly configurable, it is the reference tool for pre-commit hooks and CI/CD pipelines. With more than 220 built-in rules (222 in the default configuration of v8.30.1), it identifies AWS keys, GitHub, Stripe and Slack tokens, and many more.
What you will learn
- Understand how Gitleaks detects secrets with regex rules
- Scan different sources: Git repositories, files, archives, standard input
- Configure custom rules and exclusions
- Integrate Gitleaks into your CI/CD pipelines (GitHub Actions, GitLab CI)
- Use the advanced features: decoding, archives, baseline
The problem of secrets in code
A secret pushed to a public repository does not stay confidential long enough for you to notice. Crawling bots continuously watch GitHub's public events and test the credentials they find, often before the author of the commit has even finished their day. GitHub has industrialised the response with secret scanning and automatic revocation partnerships with cloud providers, which says a lot about how often this happens.
Secrets end up exposed for several reasons:
| Situation | Example | Risk |
|---|---|---|
| Committed config file | .env missing from .gitignore | Plaintext API keys in the Git history |
| Hardcoded password | password = "admin123" in the code | Access to the database |
| Token in the logs | Authorization: Bearer sk_live_... | Account compromise |
| Base64-encoded secret | YXdzX3NlY3JldF9hY2Nlc3Nfa2V5... | False sense of security |
| Git history | Secret deleted but present in an old commit | Still reachable through git log |
The problem: even if you delete a secret, it stays in the Git history. And malicious scanners analyse the whole history, not just the main branch.
How Gitleaks solves this problem
Gitleaks uses an approach based on regex rules and keywords to detect secrets. Unlike other tools, it does not check whether secrets are active: it focuses on speed and configurability.
How it works, in three steps
The classification step is the one that determines the quality of the results, and it relies on two stacked mechanisms. Gitleaks first looks for keywords that are cheap to spot (AKIA, sk_live_, ghp_), then applies the complete regular expression only to the lines that passed this first filter. This pre-filtering explains the tool's speed, and it is also why a custom rule without a keywords field noticeably slows down a large repository.
- Discovery: Gitleaks walks through the sources (Git, files, archives, stdin)
- Classification: each line is compared with the 220+ built-in rules
- Report: the secrets found are displayed with their exact location
Gitleaks strengths
Two rows of this table are real differentiators compared with other scanners; the others are about convenience. Automatic decoding catches base64-encoded secrets that most tools let through, and archive scanning covers build artifacts shipped as .tar.gz where configuration files linger. On the other hand, TOML configurability has a downside: without a team convention, every repository ends up with its own .gitleaks.toml and allowlists that drift apart.
| Feature | Detail |
|---|---|
| Speed | Written in Go, very fast |
| Configurability | Customisable TOML rules |
| Decoding | Base64, hex, URL encoding (v8.26+) |
| Archives | ZIP, tar.gz, 7z... (v8.27+) |
| Composite rules | Multi-part secrets (v8.28+) |
| Integration | Pre-commit, GitHub Actions, GitLab CI |
What Gitleaks detects
Coverage is broad on recognisable token formats, that is, those with a fixed prefix and a constant length. It is much weaker on secrets with no identifiable shape: a database password or a home-made internal key will only be detected by the generic rule generic-api-key, which relies on entropy and generates many false positives. That is precisely the case where you need to write your own rules.
Gitleaks ships more than 220 rules to detect:
- Cloud providers: AWS, GCP, Azure, DigitalOcean
- Payments: Stripe, PayPal, Square
- Authentication: GitHub, GitLab, Slack, Discord
- Databases: PostgreSQL, MySQL, MongoDB in URLs
- Private keys: RSA, DSA, ECDSA, OpenSSH
- AI/ML: OpenAI, Anthropic, HuggingFace, Perplexity
Installation
Gitleaks is a static Go binary with no dependency, which makes the four methods below equivalent in their result. The choice criterion is traceability: on a development workstation, a version manager such as asdf or mise lets the whole team align on the same version. In CI, the container image pinned by digest is preferable, otherwise an upstream version change can break a pipeline overnight on newly added rules.
Download the binary from the GitHub releases. The project publishes a checksums file next to each archive: fetch both and verify the checksum before extracting anything.
VERSION="8.30.1"BASE="https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}"
# Archive + checksums file published by the projectcurl -fsSLO "${BASE}/gitleaks_${VERSION}_linux_x64.tar.gz"curl -fsSLO "${BASE}/gitleaks_${VERSION}_checksums.txt"
# Checksum verification: must print "... : OK"sha256sum --check --ignore-missing gitleaks_${VERSION}_checksums.txt
# Extract and install only if the check passedtar xzf gitleaks_${VERSION}_linux_x64.tar.gz gitleakssudo install -m 0755 gitleaks /usr/local/bin/gitleaks
# Verificationgitleaks version# 8.30.1With Homebrew:
brew install gitleaks
# Verificationgitleaks versionThe Docker image is ideal for CI/CD environments:
Do not use the latest tag: it changes without notice and makes your pipelines non-reproducible. Pin the version, and the digest as soon as the scan drives a build block.
# GitHub Container Registry, pinned by digestIMG="ghcr.io/gitleaks/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f"docker pull "$IMG"
# Scan a local directory read-onlydocker run --rm -v "$PWD:/path:ro" "$IMG" dir /path
# The same content is published on Docker Hub as zricethezav/gitleaksFor flexible version management, with the version recorded in a versioned file of the repository. The asdf set syntax below applies to asdf 0.16 and later; on earlier versions, the equivalent command is asdf global gitleaks <version>.
# Add the pluginasdf plugin add gitleaks
# Install a specific versionasdf install gitleaks 8.30.1
# Set it as the global versionasdf set --home gitleaks 8.30.1Main commands
Since version 8.19.0, Gitleaks uses three main commands: git, dir and stdin. The old detect and protect commands are deprecated but still functional.
Scanning a Git repository
The git command scans the full history of a Git repository, including all branches.
# Scan the repository in the current directorygitleaks git
# Scan a specific repositorygitleaks git /path/to/my-repo
# Show the details (verbose mode)gitleaks git -v
# Hide the bannergitleaks git --no-banner
# Mask the value of secrets in the output (use it in CI)gitleaks git -v --redactSample output, captured with Gitleaks 8.30.1 on a test repository containing a GitHub token, value masked by --redact:
Finding: token = REDACTEDSecret: REDACTEDRuleID: github-patEntropy: 4.771928File: config/app.cfgLine: 1Commit: 343f04f811221ca91b4f3a52eee9355b6cef8e5cAuthor: devEmail: dev@example.comDate: 2026-09-30T19:41:32ZFingerprint: 343f04f811221ca91b4f3a52eee9355b6cef8e5c:config/app.cfg:github-pat:1This output tells you:
- The secret type:
github-pat, a GitHub personal access token - The location: file
config/app.cfg, line 1 - The commit: hash, author, date
- The fingerprint (
Fingerprint): the identifier to reuse in a.gitleaksignore
Without --redact, the full value of the secret is displayed in plaintext: in a CI log, kept and readable by the whole team, that is a second leak. To test your hooks, do not use a fake AWS key: measured on this version, an isolated AKIA key is no longer detected, whereas a ghp_ token or a PEM private key still are.
Filtering by commit range
--log-opts passes its value as is to git log, without validation by Gitleaks. A misspelled option therefore makes git fail, not the scanner, and the error message you get is Git's. It is the main lever to make a scan bearable on an old repository: limiting the range divides the scan time accordingly, at the cost of a blind spot on the history that is not covered. Keep this reduction for pull request CI, and keep a scheduled full scan alongside it.
# Scan only the last 10 commitsgitleaks git --log-opts="--max-count=10"
# Scan a range of commitsgitleaks git --log-opts="HEAD~20..HEAD"
# Scan only a specific branchgitleaks git --log-opts="--all develop..main"Scanning before a commit (pre-commit)
These two options do not cover the same scope. --staged analyses what is in the Git index, that is, exactly what the commit is going to record. --pre-commit relies on a git diff and serves the project's official hook, which actually combines both. One point to watch: a secret already present in an earlier commit will be seen by neither of them; only gitleaks git on the full history will find it.
# Scan the staged filesgitleaks git --staged
# Scan with git diff (pre-commit mode)gitleaks git --pre-commitScanning files or directories
dir ignores Git entirely: no history, no commit, no author in the report, only the path and the line. It is the command to use on a build artifact, an exported configuration folder or a directory extracted from a container image. Two behaviours to know: the .git/ directory is excluded from the walk, but hidden files and files listed in .gitignore are scanned, which often surfaces a local .env that gitleaks git would never see. The file and directory aliases designate the same command.
# Scan a directorygitleaks dir /path/to/folder
# Scan a specific filegitleaks dir config.env
# Scan several pathsgitleaks dir file1.log file2.env /etc/config/Scanning standard input (stdin)
stdin opens the scan to anything that produces text, and that is where the leaks nobody looks for live: application logs, kubectl describe outputs, environment variable exports. The report obviously cannot point to a file or a commit, only to the line number in the stream. Be careful, however, with the command that feeds the pipe: kubectl logs on a chatty pod can send several gigabytes.
# Scan a file through a pipecat config.env | gitleaks stdin
# Scan the output of a commandkubectl logs deployment/my-app | gitleaks stdin
# Scan a file decompressed on the flygunzip -c logs.gz | gitleaks stdinAdvanced features
The four features that follow serve different needs but share one logic: they widen or narrow the scan scope. Decoding and archive scanning reveal secrets invisible to a naive scan; the baseline and the output formats make the results usable day to day without drowning teams in alerts that are already known.
Automatic decoding (v8.26+)
Gitleaks can automatically decode secrets encoded in base64, hexadecimal or URL encoding. By default, decoding is enabled with a depth of 5.
# Limit recursive decoding to depth 2gitleaks dir /path --max-decode-depth=2
# Disable decodinggitleaks dir /path --max-decode-depth=0Example: a base64-encoded Stripe token will be detected:
# File containing: c2tfbGl2ZV80ZUMzOUhxTHlqV0Rhcmp0VDF6ZHA3ZGMK
Finding: sk_live_4eC39HqLyjWDarjtT1zdp7dcSecret: sk_live_4eC39HqLyjWDarjtT1zdp7dcRuleID: stripe-access-tokenTags: [decoded:base64 decode-depth:1]The decoded:base64 and decode-depth:1 tags indicate that the secret was found after decoding.
Archive scanning (v8.27+)
Archive scanning is disabled by default (--max-archive-depth is 0), which surprises many users who believe their .tar.gz files are covered. You have to enable it explicitly. Raise the depth sparingly: each additional level means an extraction to disk, and a booby-trapped archive containing thousands of nested files turns the scan into a saturation of the runner's temporary space.
# Scan an archive with depth 1gitleaks dir archive.tar.gz --max-archive-depth=1
# Scan nested archives (depth 3)gitleaks dir archives/ --max-archive-depth=3Supported formats: zip, tar, tar.gz, tar.bz2, tar.xz, 7z, rar, and more.
The path in the results shows the location inside the archive:
File: nested.tar.gz!inner/files.tar!config/.envThis means: .env is in files.tar, which is in nested.tar.gz.
Baseline: ignoring known secrets
The baseline lets you ignore secrets already identified in later scans. Useful for projects whose history contains secrets that have already been dealt with.
-
Create the baseline by generating an initial report
Terminal window gitleaks git --report-path baseline.json -
Use the baseline for the following scans
Terminal window gitleaks git --baseline-path baseline.json --report-path findings.jsonOnly new secrets will be reported.
Output formats
The choice of format depends on who reads the report. SARIF is the only one that integrates natively with the GitHub and GitLab security dashboards, JUnit makes leaks appear as failed tests in the interface of most CI systems, and JSON is the format to keep since it holds the fingerprint values you can reuse in a .gitleaksignore. The template format is reserved for internal reports that must follow an in-house template.
# JSON (default when --report-path is used)gitleaks git -f json -r report.json
# CSVgitleaks git -f csv -r report.csv
# SARIF (for GitHub Code Scanning)gitleaks git -f sarif -r report.sarif
# JUnit (for CI/CD)gitleaks git -f junit -r report.xml
# Custom templategitleaks git -f template --report-template custom.tmpl -r report.txt
# JSON output to stdoutgitleaks git -f json -r -Configuration
Gitleaks is configured through a .gitleaks.toml file versioned with the code. Two mistakes are costly here. The first is writing a configuration without useDefault = true, which silently disables the 222 built-in rules. The second is piling up exclusions until the scan returns nothing, which gives you a green pipeline and no security. Treat this file as production code: mandatory review, and a written justification for each exclusion.
Order of precedence
This order explains the most frequent failure in CI: a correctly written .gitleaks.toml that is ignored, because a GITLEAKS_CONFIG variable lingers in the runner's environment and takes over. Note point 4 as well: the file is looked for in the scanned directory, not in the current directory. Scanning /opt/app from your $HOME therefore does not load the .gitleaks.toml in your $HOME. When in doubt, --log-level=debug shows the configuration actually used.
--configor-coptionGITLEAKS_CONFIGenvironment variable (file path)GITLEAKS_CONFIG_TOMLenvironment variable (TOML content).gitleaks.tomlfile in the scanned directory
If no configuration is found, Gitleaks uses the default rules.
Extending the default configuration
The reflex to adopt is useDefault = true. Without this line, your file replaces the built-in rule set entirely instead of complementing it, and you end up with a scanner that only looks for your own patterns. disabledRules then lets you surgically remove a rule that is too noisy, generic-api-key being by far the first candidate. Disable it only after writing your own rules, otherwise you lose the only detection that covers secrets with no known format.
# Configuration titletitle = "Custom configuration"
# Extend the default configuration[extend]useDefault = true
# Disable some default rulesdisabledRules = ["generic-api-key"]Creating a custom rule
Two fields drive the result. keywords is not decorative: it is the pre-filter that avoids applying the regex to every line of the repository, and forgetting it can multiply the scan time. entropy sets a disorder threshold below which a match is rejected, which discards test values such as int_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa. Finally, the regex follows Go's RE2 syntax, which supports neither lookbehind nor lookahead: an expression copied from PCRE can be rejected on load.
[[rules]]# Unique identifierid = "internal-api-key"
# Descriptiondescription = "Company internal API key"
# Regular expression (Go syntax)regex = '''int_[a-zA-Z0-9]{32}'''
# Keywords for pre-filtering (optimisation)keywords = ["int_", "internal_api"]
# Minimum entropy (optional)entropy = 3.5
# Tags for reportingtags = ["internal", "api-key"]Allowlists: excluding false positives
An allowlist is an accepted risk, not a fix: everything it covers stops being checked, including real secrets that might later land in the same place. Prefer the narrowest criteria possible. Excluding tests/ wholesale is convenient but broad; targeting a specific fixtures file or a known value pattern, through regexes and stopwords, keeps the rest of the directory under watch. Document each entry with description: that is the field you reread six months later when wondering why a rule is silent.
# Global allowlist (applies to all rules)[[allowlists]]description = "Test files and documentation"paths = [ '''tests/''', '''fixtures/''', '''\.md$''',]
# Allowlist for a specific rule[[rules]]id = "aws-access-token"
[[rules.allowlists]] description = "AWS test keys" regexes = ['''AKIAIOSFODNN7EXAMPLE'''] stopwords = ["example", "test", "demo"]Shared allowlists (v8.25+)
targetRules solves a concrete problem: before v8.25, excluding the same test directory for ten rules meant duplicating ten allowlist blocks, with the drift that follows when one is forgotten. The scope stays explicit, only the named rules are affected, which avoids the side effect of a global allowlist that also blinds the rules added later.
[[allowlists]]# Apply to specific rulestargetRules = ["aws-access-token", "generic-api-key"]description = "Test files"paths = ['''tests/.*\.json$''']Composite rules (v8.28+)
Composite rules address the case where one half of the secret is worthless on its own. An AWS identifier AKIA... alone allows nothing; combined with its secret key a few lines below, it gives full access. The withinLines parameter defines the accepted proximity window. A welcome side effect: by requiring both parts, this kind of rule strongly reduces false positives on public identifiers quoted in documentation.
[[rules]]id = "aws-keypair"description = "AWS key/secret pair"regex = '''AKIA[0-9A-Z]{16}'''
# Required rule: the secret must be close by [[rules.required]] id = "aws-secret-pattern" withinLines = 5 # The secret must be within the next 5 linesIgnoring a specific secret in the code
This annotation lives in the code, which makes it visible in code review, unlike an allowlist buried in a configuration file. That is its main benefit. It is also its main danger: anyone can stick it on a real leak to get a pipeline through. On a sensitive repository, the --ignore-gitleaks-allow option lets you run an audit scan that neutralises all these annotations and reveals what they hide.
# This key is an example for the documentationAWS_KEY = "AKIAIOSFODNN7EXAMPLE" # gitleaks:allowThe .gitleaksignore file
The fingerprint combines the commit, the file, the rule identifier and the line number. This precision is a strength, each exception is surgical, but also a weakness: rewriting the history or moving the line changes the fingerprint, and the exception silently stops applying. Always take these values from a JSON report rather than composing them by hand, and remember that an entry here never exempts you from revoking the secret concerned.
# Format: fingerprint (from the JSON report)fbc14303ffbf8fb1c2c1914e8dda7d0121633aca:config.env:aws-access-token:477b2a3e56973785a52ba4ae4b8dac61d4bac016f:keys:private-key:6CI/CD integration
The three integrations below do not replace each other, they complement each other. The pre-commit hook is fast and local but can be bypassed with a --no-verify; the CI cannot be bypassed but only acts after the push. A healthy chain combines both: the hook for developer comfort, the CI for the guarantee.
Pre-commit hook
Block secrets before they are committed. The Gitleaks repository publishes three hook identifiers: gitleaks compiles the binary in Go, gitleaks-docker goes through the container image, gitleaks-system reuses the binary already installed on the workstation. The first is the simplest, the third the fastest if your team already manages the version through asdf or mise.
repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.30.1 hooks: - id: gitleaks-
Install pre-commit
Terminal window pip install pre-commit -
Create the configuration file
Terminal window cat > .pre-commit-config.yaml << 'EOF'repos:- repo: https://github.com/gitleaks/gitleaksrev: v8.30.1hooks:- id: gitleaksEOF -
Install the hooks
Terminal window pre-commit install -
Test
Terminal window git add . && git commit -m "test"# If a secret is detected, the commit will be blocked
To disable the hook temporarily:
SKIP=gitleaks git commit -m "skip check"GitHub Actions
Use the official Gitleaks action. Three points of this workflow deserve your attention. fetch-depth: 0 is mandatory: without it, the checkout fetches only one commit and the history scan sees nothing. The actions are pinned by SHA rather than by tag, because a Git tag can be moved to another commit by the maintainer or by an attacker who took over the repository. Finally, permissions: {} at the workflow level removes all default permissions, and each job gets back only what it needs: contents: read to clone, pull-requests: write because the action comments on pull requests.
name: Gitleaks
on: push: branches: [main] pull_request:
permissions: {}
jobs: gitleaks: runs-on: ubuntu-latest permissions: contents: read pull-requests: write steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 # Important: fetch the whole history persist-credentials: false
- uses: gitleaks/gitleaks-action@e0c47f4f8be36e29cdc102c57e68cb5cbf0e8d1e # v3.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Only for repositories owned by an organisation GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}persist-credentials: false prevents actions/checkout from leaving the authentication token in the runner's .git/config file, where any later step of the job could read it back and exfiltrate it.
GitLab CI
Integrate Gitleaks into your merge requests. Two details determine whether the job works. First, the path to scan is a positional argument of the git command, not an option: the old --source flag has disappeared and now returns unknown flag. Second, GIT_DEPTH: 0 is essential, because GitLab clones with a limited depth by default, and a history scan on a truncated clone proves nothing.
stages: - security
gitleaks: stage: security image: ghcr.io/gitleaks/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f variables: GIT_DEPTH: 0 # full history, otherwise the scan is blind script: - gitleaks git "$CI_PROJECT_DIR" --verbose rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' - if: '$CI_COMMIT_BRANCH == "main"' allow_failure: falseThe exit code is 1 by default when a leak is found, so --exit-code is only useful to change that value. Combined with allow_failure: false, the pipeline does stop on detection.
To generate a SARIF report compatible with the GitLab Security Dashboard, add the report format and declare the artifact. Keep the paths key in addition to reports: without it, the file cannot be downloaded from the interface, which complicates analysis when the dashboard is not available in your GitLab edition.
gitleaks: stage: security image: ghcr.io/gitleaks/gitleaks:v8.30.1@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f variables: GIT_DEPTH: 0 script: - gitleaks git "$CI_PROJECT_DIR" -f sarif -r gl-sast-report.sarif artifacts: reports: sast: gl-sast-report.sarif paths: - gl-sast-report.sarifTroubleshooting
One point before reading this table: "no results" is not good news in itself. Before concluding that a repository is clean, check that the scan actually saw something: the scanned ~N bytes line of the output must show a consistent volume for the size of the repository. A 0 bytes almost always signals a truncated clone in CI or a wrong starting path, not the absence of secrets.
| Symptom | Likely cause | Solution |
|---|---|---|
| No result on a test AWS key | v8.30.1 no longer detects an isolated AKIA key | Test with a ghp_ token or a PEM private key |
No result in CI, scanned ~0 bytes | Truncated clone or wrong starting path | fetch-depth: 0 or GIT_DEPTH: 0, and check the scanned path |
| Many false positives | Test secrets, examples | Use .gitleaksignore or allowlists |
| Very slow scan | Large Git history | Use --log-opts="--max-count=100" |
| "no such file" error | Remote URL not supported | Clone the repo locally first |
| Encoded secret not detected | Decoding disabled | Check --max-decode-depth |
| Archive not scanned | Archive scanning disabled | Enable --max-archive-depth=1 |
Debug logs
The debug level answers the most useful question when behaviour is unexpected: which configuration was loaded and which rules are active. The trace level goes down to the detail of every match tested and quickly floods a terminal; keep it for analysing a custom rule that does not fire, redirecting the output to a file.
# Debug levelgitleaks git --log-level=debug
# Trace level (very verbose)gitleaks git --log-level=traceSlow file to scan
A scan that drags is almost always caused by a few specific files, not by general slowness: large archives, single-line minified files, datasets committed by mistake. Since v8.26, Gitleaks reports them in the debug logs, which lets you address the cause rather than lowering the scan coverage.
gitleaks git --log-level=debug 2>&1 | grep "slow"Add these files to an allowlist or use --max-target-megabytes:
# Ignore files > 10 MBgitleaks git --max-target-megabytes=10Best practices
Detecting a secret always costs more than preventing it from existing. The three areas below therefore follow this order of value: prevention first, detection next, remediation as a last resort because at that stage the secret is already compromised.
Prevention: keep secrets out of Git
These four measures reinforce one another, but only one really changes the game: the secrets manager. As long as a secret exists as a string in a file on the development workstation, it will end up committed by accident. The other three measures reduce the probability; the fourth removes the cause.
- Pre-commit hooks: block commits that contain secrets
.gitignorefiles: exclude.env,*.pem,credentials.json- Secrets manager: use OpenBao, Vault, AWS Secrets Manager or Passbolt
- Environment variables: inject secrets at runtime
Detection: scan regularly
The pull request scan and the periodic scan do not look for the same thing. The first works on a diff and must stay fast so as not to slow teams down. The second goes over the full history again and catches what yesterday's rules could not detect: every version upgrade of Gitleaks adds rules, so a repository declared clean last year is not necessarily clean today.
- CI/CD: scan every pull request and every push
- Periodic scans: audit the whole organisation every week
- New repositories: add Gitleaks from day one
Remediation: act on findings
The order of this list is an order of priority, not a set of options. Rotation comes before everything else, including before cleaning the history: as long as the secret remains valid, rewriting the commits protects you from nothing, since clones may have been made in the meantime. For step 3, git filter-branch is deprecated by the Git project itself in favour of git-filter-repo, faster and less error-prone; BFG Repo-Cleaner remains a valid alternative.
- Immediate rotation: revoke and recreate the exposed secret
- Access audit: check the usage logs of the compromised secret
- History cleanup: use
git filter-repoor BFG Repo-Cleaner - Post-mortem: understand how the secret was exposed
Comparison with TruffleHog
Gitleaks and TruffleHog are the two most popular open source tools for secret detection:
| Criterion | Gitleaks | TruffleHog |
|---|---|---|
| Speed | Very fast | Slower (verification) |
| Automatic verification | No | Yes (800+ APIs) |
| Supported sources | 3 (Git, dir, stdin) | 18 (Git, Docker, S3, Jenkins...) |
| Number of rules | 220+ | 800+ |
| Configuration | Flexible TOML | Simple YAML |
| Decoding | Yes: base64, hex, URL | Yes: base64, UTF-16 |
| Archives | Yes: ZIP, tar... | Yes, through --archive-max-depth |
| Composite rules | Yes (v8.28+) | No |
Recommendation:
- Gitleaks: ideal for pre-commit hooks and fast CI/CD scans
- TruffleHog: preferable for security audits with verification of active secrets
A clarification on the last two rows of the table, since both projects evolve quickly: TruffleHog also has decoders (base64, UTF-16, escapes) and can go down into archives through its --archive-max-depth and --archive-max-size options. The real differentiator lies elsewhere: Gitleaks favours speed and fine-grained control of rules, TruffleHog the active verification of the credentials it finds against the APIs concerned.
You can use both: Gitleaks in pre-commit for speed, TruffleHog in a periodic scan for verification.
Key points
- Three commands:
git(history),dir(files),stdin(pipeline) - 220+ built-in rules: AWS, Stripe, GitHub, Slack, and much more
- Automatic decoding: base64, hex and URL encoding, active by default at depth 5
- Archive scanning: disabled by default, enable it with
--max-archive-depth - Baseline: ignore the secrets already dealt with
- Pre-commit: block secrets before they reach Git
- Immediate rotation: an exposed secret is a compromised secret
- In CI: action pinned by SHA,
permissions: {}, full history, image pinned by digest
Knowledge check
Check that you have the essentials of this guide. The questions only cover what has just been explained here.
Knowledge check
Check what you have learned with this interactive quiz
How it works
- The timer starts when you click Start
- Multiple choice, true/false and short answer questions
- You can move between questions
- Detailed results are shown at the end
Starts the quiz and the timer
Review
(0/0)Score by domain
A domain groups several guides and gets several questions, so its score is more reliable than a per-topic score, which often rests on a single question.
Summary
Strengths
No topic passed at 80% on at least two questions yet.
Weak areas
No topic below the pass mark.
Recommended next
Hints before you try again?
Continue the course: SAST: analysing source code to find flaws
A new full quiz with random questions
Work again only on the questions you missed
Resources
- Official site: github.com/gitleaks/gitleaks
- Playground: gitleaks.io/playground
- Default configuration: gitleaks.toml
- GitHub Action: gitleaks-action