Add a Local Gitleaks Pre-Commit Hook (No Frameworks)

Add a Local Gitleaks Pre-Commit Hook (No Frameworks)

~ 4 min read

Why a local .git hook?

Secrets are usually committed by accident: a quick debug key, a copied .env line, or a local token that never should have been pasted. A local .git/hooks/pre-commit hook is the last stop before Git records a commit, so it prevents secrets from ever entering history.

This post shows a minimal setup that uses only files inside .git/, no extra frameworks or repo config. It is fast, easy to remove, and perfect for personal repos or a quick safety net.

What it does not do: .git/hooks/ is not tracked in Git, so every teammate must install it themselves. For team-wide enforcement, pair this with CI (see the last section).


Step 1: Install Gitleaks

Pick the method that fits your OS and environment. The official README covers Homebrew, Docker, and building from source, and the releases page has prebuilt binaries.

Releases page:

https://github.com/gitleaks/gitleaks/releases

macOS

Homebrew (officially documented):

brew install gitleaks

Or download the macOS release archive from the releases page and place the gitleaks binary on your PATH.

Windows

Download the Windows .zip from the releases page, extract gitleaks.exe, and add its folder to your PATH.

Linux

Download the Linux release archive, extract gitleaks, and move it to a directory like /usr/local/bin.

If you prefer containers, the official Docker image is documented in the README.

Verify the install:

gitleaks version

Step 2: Create the hook

Gitleaks ships an example pre-commit.py you can copy into .git/hooks/ for a local hook.

Example location in the repo:

https://github.com/gitleaks/gitleaks/blob/master/scripts/pre-commit.py

Copy it into place and make it executable:

cp /path/to/gitleaks/scripts/pre-commit.py .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

If you want a more robust path that works even when .git/ is a file (like in submodules or worktrees), resolve the hook directory first:

HOOK_DIR="$(git rev-parse --git-dir)/hooks"
cp /path/to/gitleaks/scripts/pre-commit.py "$HOOK_DIR/pre-commit"
chmod +x "$HOOK_DIR/pre-commit"

This python script can disable gitleaks checks with git config set hooks.gitleaks false and re-enable them with either git config set hooks.gitleaks true Or git config unset hooks.gitleaks.

Already have a pre-commit hook? Open it and append the Gitleaks call from the sample script instead of overwriting.

Alternatively, just want something simple? You can go with running the gitleaks command directly in your pre-commit hook, thus avoiding the python wrapper and ensure the check always runs:

#!/usr/bin/env bash
set -e
gitleaks protect --staged --redact

Step 3: Verify it works

Stage a file with a test secret (like a dummy API key), then try to commit to see the hook in action. If the hook is working, the commit is blocked and Gitleaks prints a finding. Remove the test secret afterward.

API_KEY=12345
  • Add a .gitleaks.toml to customize rules for your project.
  • Mirror the scan in CI so server-side checks back up local hooks.
  • If a secret does slip through, rotate it immediately and remove it from history.

This approach is lightweight and fast, but remember it’s local to your machine. If you want team-wide enforcement, pair it with CI.


Troubleshooting

If the hook does not fire, start with these quick checks:

  • Make sure the hook is executable: ls -l .git/hooks/pre-commit should show -rwx.
  • Confirm the hook file is actually being used: git rev-parse --git-dir tells you where Git is reading hooks from.
  • Verify gitleaks is on your PATH inside Git hooks. Hooks often run with a minimal PATH, so use an absolute path in the script if needed (for example, /usr/local/bin/gitleaks or the output of which gitleaks).
  • If you see Python errors, you may be using a system without python on PATH. Run the script with python3 or change the shebang to #!/usr/bin/env python3.

More on gitleaks.toml

The official Gitleaks docs include the config format, examples, and rule/allowlist options.

https://github.com/gitleaks/gitleaks#configuration

Here is a recommended starter config that keeps the default rules, scopes the scan to the Git history, and adds an allowlist for common non-secrets:

title = "gitleaks config"

[extend]
useDefault = true

[allowlist]
description = "Allowlist common non-secrets"
paths = [
    '''^node_modules/''',
    '''^dist/''',
    '''^public/''',
    '''^\.git/''',
]
regexes = [
    '''^-----BEGIN (PUBLIC|PRIVATE) KEY-----$''',
    '''^AKIAIOSFODNN7EXAMPLE$''',
]

If you store test fixtures with fake secrets, add a paths entry for that folder or add a regexes entry that matches the fake value.


Add a GitHub Actions check (team-wide enforcement)

Add a GitHub Actions workflow to run the scan on pushes and pull requests. The official Gitleaks Action docs include a workflow example and document GITLEAKS_CONFIG for custom config paths.

Create .github/workflows/gitleaks.yml:

Minimum viable workflow (no custom config):

name: gitleaks

on:
    push:
        branches: ["**"]
    pull_request:
        branches: ["**"]

permissions:
    contents: read

jobs:
    scan:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4
              with:
                  fetch-depth: 0
            - uses: gitleaks/gitleaks-action@v2
              env:
                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

If you have a .gitleaks.toml, add GITLEAKS_CONFIG:

name: gitleaks

on:
    push:
        branches: ["**"]
    pull_request:
        branches: ["**"]

permissions:
    contents: read

jobs:
    scan:
        runs-on: ubuntu-latest
        steps:
            - uses: actions/checkout@v4
              with:
                  fetch-depth: 0
            - uses: gitleaks/gitleaks-action@v2
              env:
                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
                  GITLEAKS_CONFIG: .gitleaks.toml

all posts →