Catch secrets before commit with gitleaks and pre-commit
Block credentials at the git hook, scan history for what already leaked, and wire the same check into CI.
Researchers who plant canary AWS keys in a public GitHub commit see unauthorized API calls in under ten minutes. Your private repo is not magically slower — it is just less watched. The cheapest leak to fix is the one that never enters history. gitleaks scans for key patterns (AWS AKIA…, GitHub ghp_…, Slack tokens, private keys) and exits non-zero when it finds a hit. Wire it into pre-commit and the commit aborts before the secret is immortalized.
This walkthrough installs pre-commit, adds the official gitleaks hook, shows what a blocked commit looks like, scans full history for what already leaked, and duplicates the check in CI so --no-verify bypasses do not win. For the broader secrets lifecycle — rotation, honeytokens, org-wide sweeps — continue in Advanced secrets.
Pre-commit catches staged changes. CI catches bypassed hooks and forked PRs. Scheduled org scans catch repos that predates your controls.
Install pre-commit and add the gitleaks hook
Pin the gitleaks hook to a release tag — @main on a security gate is how you get surprised on a Friday. Run pre-commit install once per clone; hooks live in .git/hooks/pre-commit and call the versions pinned in .pre-commit-config.yaml.
repos:- repo: https://github.com/gitleaks/gitleaksrev: v8.18.4hooks:- id: gitleaksargs: ['protect', '--staged', '--redact', '--verbose']
pip install pre-commit # or brew install pre-commitpre-commit installgit commit -m "add prod config"gitleaks.............................................Failed Finding: AWS Access Key File: config/prod.env:4commit aborted — key never enters historyScan what already happened — and act on it
Pre-commit only protects forward motion. Run gitleaks detect against the full repository to find keys committed before the hook existed — including deleted files still reachable via git log. Use --redact in shared logs and --report-path in CI so security can triage without pasting live credentials into Slack.
gitleaks detect --source . --redact --exit-code 13 leaks found across 1,204 commitsgitleaks detect --source . --log-opts="main..feature" --redactscan only commits on a feature branch vs maingit log -p --all -S "AKIA" -- "*.env" | head -40manual spot-check for context around a hitBackstop in CI — hooks are not enforcement
Anyone can run git commit --no-verify. Protected branches should require CI secret scanning to pass before merge, and org policy should block force-push to default branches without review. The same gitleaks version and config in pre-commit and CI keeps false-positive rates stable.
Add a .gitleaks.toml when defaults false-positive on test fixtures — allowlist paths like testdata/ with a comment explaining why, never a blanket allowlist.regexes = ['.*']. Review allowlists in pull requests the same way you review firewall rules: every exception should have an owner and a expiry.
secret_scan:stage: testimage: zricethezav/gitleaks:v8.18.4script:- gitleaks detect --source . --no-git --redact --exit-code 1 --report-path gl.jsonartifacts:when: on_failurepaths: [gl.json]
Where this goes next
Scanning is detection, not storage policy. Pair gitleaks with SOPS + age so secrets that belong in Git arrive encrypted, push protection on your Git host where available, and honeytokens that alert when touched. The Secrets management foundations course covers where secrets should live — and why Git is never that place in plaintext.
Go deeper in a courseAdvanced secretsScanning at scale, rotation, honeytokens, and Kubernetes delivery patterns.View course