Skip to content

DevSecOps: Pre-commit Hooks for Automated Checks

A secret committed to Git leaks to anyone with read access to the repo. Pre-commit hooks are a chance to catch those mistakes before they're written to history.

TL;DR: Pre-commit hooks run automated checks before every git commit. They catch security issues before it's committed, the cheapest time to fix anything.


Why pre-commit hooks

After git commit, the code exists in the repository. After git push, it exists in CI. After a merge, it's on its way to production. Each step out costs more to fix than the last.


How pre-commit hooks work

A pre-commit hook is a script stored in .git/hooks/pre-commit. Git runs it before each commit. If the script exits with a non-zero code, the commit is blocked and the developer sees the output.

#!/bin/sh
# .git/hooks/pre-commit

echo "Running security checks..."
gitleaks detect --source . --verbose --redact /dev/null
if [ $? -ne 0 ]; then
  echo "Found a secret. Commit blocked."
  exit 1
fi

echo "All checks passed."

Make it executable:

chmod +x .git/hooks/pre-commit

This is a shell script that runs whatever you want before a commit, but it's not committed to the repo. This should be used by developers to add their preferred checks on top of the team's agreed upon pre-commit framework.

This approach is straightforward but requires developers to install all tools manually, and requires every dev to keep their hooks up to date as the project evolves.


The pre-commit framework (pre-commit.com) manages hooks declaratively and handles installation automatically (or notifies you if something isn't).

Install

pip install pre-commit

Configuration

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.29.0
    hooks:
      - id: gitleaks
        args: [--verbose, --redact]
  ...
  - repo: local
    hooks:
      - id: checkov
        name: checkov
        entry: checkov -d .
        language: system
        pass_filenames: false

Some repos like Gitleaks and Bandit have official remote hooks and can be install automatically. Checkov doesn't have an official pre-commit remote repo, so you define it as a local hook that runs the checkov binary directly. That also means pre-commit won't install checkov for you, so developers need to install checkov before pre-commit install works.

How it works

After installing with pre-commit install, the hooks run automatically on every commit:

# Run install
pre-commit install
# Run all hooks on the entire repo
pre-commit run --all-files

# Run a specific hook
pre-commit run gitleaks

# Run hooks on staged files only
pre-commit run

The commit is blocked if any hook exits non-zero. The developer sees the output and can fix the issue.


Execution

Gitleaks on staged files (not full history) runs fast. Bandit on staged Python files runs fast. Checkov on all IaC files in the repo takes longer because it scans the entire directory.

If you want to avoid blocking on slow findings during quick commits, use pre-commit's stages feature:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.29.0
    hooks:
      - id: gitleaks
        stages: [commit]
  ...
  - repo: local
    hooks:
      - id: checkov
        name: checkov
        entry: checkov -d .
        language: system
        pass_filenames: false
        stages: [push]

This runs checkov only on git push (pre-push hook), not on every commit. Developers can commit IaC changes and push them later for the full scan. You lose the pre-commit guarantee for checkov but gain speed during active development.


CI is still a requirement

CI catches things the developer missed. If someone bypasses the hook or forgets to install it, CI blocks the merge. It's not as clean because the commit has already landed, but it's better than nothing.


Choose the tools

You don't need to run every check. Pick the ones that match what you ship with an eye for guarding secrets and keeping your systems secure.

Consider using any of these posts for inspiration:


Takeaways

  • The pre-commit framework manages hooks and installation declaratively. Manual git hooks are a shell script that requires manual setup and manual tool installation.
  • language: system hooks get no virtual environment: pre-commit won't install checkov for you because it has no official remote repo. Developers must install it themselves.
  • CI is a vital second line, not just a fallback. If someone bypasses the hook or forgets to install it, CI catches it later. The commit has already landed, but it's better than nothing.
  • Use stages: [push] for slow tools to avoid blocking on quick commits during active development.
  • Each tool catches a different class of mistake.