Safeguard
Developer Tools•

Pre-commit or CI: where each security check belongs

Which checks go in a pre-commit hook, which in pre-push, and which only in CI? Three tests decide it: speed, bypass risk and who needs the result.

Safeguard Security Team
4 min read

Put fast, low noise checks in pre-commit, slower ones in pre-push or on demand, and anything you must be able to prove in CI as a required status check. Three questions decide it: how long does it take, can it be skipped, and who needs to see the result.

The three questions

How long does it take? A hook that adds ten seconds to every commit trains people to bypass it. The githooks documentation confirms the bypass is one flag away for pre-commit.

What happens if someone skips it? If you would want to know, it needs a server side copy.

Who needs the evidence? A reviewer or auditor can see a CI result on a pull request. They cannot see what ran on a laptop.

A starting split

CheckPre-commitPre-pushCI required
Formatting and whitespaceYesNoOptional
Private key and obvious secret patternsYesNoYes
Large file blockerYesNoYes
Unit testsNoOptionalYes
Dependency and SBOM scanNoNoYes
Static analysis on changed filesOptionalOptionalYes

The pre-commit framework supports this with stages. A hook can be limited to [pre-commit, pre-push], and stages: [manual] makes it run only on demand. The command to install the pre-push type is in the block below.

pre-commit install --hook-type pre-push

Why duplicate at all

It looks wasteful to run the secret check twice. The reason is the cost of finding the problem late. A secret caught at commit time never enters history. One caught only in CI is already in a commit on a branch that may have been pushed.

GitHub's push protection documentation draws the same line: blocking before the push is preventive, while scanning afterward means the secret "must be remediated after exposure".

A config that serves both

default_stages: [pre-commit]
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: detect-private-key
      - id: check-added-large-files

Then in CI run the same hooks over every file. One file, two enforcement points.

pre-commit run --all-files

Signs your split is wrong

  • Developers routinely bypass hooks: the hooks are too slow or too noisy.
  • CI failures are usually things a hook could have caught: move them earlier.
  • Nobody can show an auditor when a control ran: move it to CI.

For more on the bypass problem see why local hooks need a CI backstop.

Safeguard's policies and gates can hold a pull request on dependency findings, which fits the required CI column. A free scan is available at gold.safeguard.sh/scan.

A worked example

Take a secret check. Locally, a hook scans staged files for key blocks in about a second, so the developer fixes it before committing. In CI, the same check runs over the whole pull request and is marked required. If a developer skipped the hook, CI still stops the merge.

Now take a full dependency scan. It may need network access and a minute or more. Running it on every commit would be miserable, so it lives in CI only, and developers can run it by hand when they want an early look.

Quick check

Should security scanning run in pre-commit?

Only the fast, low noise parts such as key detection. Full dependency or static analysis scans usually belong in CI where they are required and visible.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.