DevSecOps

Git Hooks as Supply Chain Controls in 2026

Server-side and client-side git hooks are an underused control surface for supply chain risk. Here is what to enforce, where to enforce it, and what to leave alone.

Aman Khan
Platform Engineer
6 min read

Git hooks have been around since the beginning of the project, and most security teams still treat them as a curiosity. That is a mistake. Hooks are the only point in the pipeline where you can intervene between a developer's editor and the central repository, and they are the right place to enforce a small set of controls that scale poorly anywhere else.

This post is about which hooks to deploy where. The short version: client-side hooks are advisory, server-side hooks are mandatory, and the line between them is the most important architectural decision in this whole area.

Why are client-side hooks insufficient on their own?

Client-side hooks, the ones that live in .git/hooks/ on a developer's checkout, can be bypassed with --no-verify or by simply deleting the hook script. They are useful for fast feedback and for catching honest mistakes, but they are not a security control in the meaningful sense. Any developer with a checkout can opt out, and any attacker with a checkout will. Treat them as ergonomic helpers, not as enforcement.

The right framing is that client-side hooks reduce the rate at which bad commits reach the server. They do not prevent them. This means your server-side configuration needs to assume that nothing a client-side hook checked has actually been checked. Re-validate everything. The cost of running the same check twice, once on the laptop for speed and once on the server for enforcement, is almost always lower than the cost of the one incident where someone bypassed the local check.

What belongs in a server-side pre-receive hook?

A pre-receive hook runs on the git server when a push arrives, before any refs are updated. This is the chokepoint where supply chain controls actually live. The hooks worth running here are commit signature verification, branch protection enforcement that goes beyond what your hosting platform's UI exposes, and large-file and binary-blob blocking.

Signature verification is the most important and the least deployed. Require every commit on protected branches to be signed by a key in a known list, either GPG or SSH-based signing, which git has supported natively since 2.34. Sigstore's gitsign tool gives you keyless signing tied to OIDC identity, which removes the key-rotation pain that has historically blocked adoption. The point is not cryptographic purity; it is that a compromised CI runner that pushes unsigned commits gets caught immediately, and a developer whose laptop is stolen cannot have their unsigned commits replayed.

How do you handle commit signing without breaking developer flow?

Commit signing has a reputation for being painful, and the reputation is partly earned. The traditional GPG flow involves key generation, key distribution, keyring management, and an agent that breaks on every macOS update. Most teams that adopt signing through GPG quietly give up within a year. SSH-based signing, configured through gpg.format = ssh in git config, reuses the SSH key developers already have for authentication, which removes most of the friction.

Sigstore's gitsign is the better option for new deployments. It signs commits using a short-lived certificate tied to the developer's OIDC identity, typically through their corporate SSO. There are no long-lived keys to manage, the signature is bound to a verifiable identity, and the entire flow is transparent to the developer after initial setup. The trade-off is that verification requires a Rekor transparency log lookup, which adds latency to server-side checks. For most teams this is acceptable; for very high commit volumes it is worth running a local Rekor mirror.

What about hooks that block malicious commits before they land?

The category of hook that catches actual supply chain attacks is one that inspects the diff content, not just the metadata. A pre-receive hook can run a pattern scan on the introduced changes and reject commits that match known indicators. This is where you catch the obfuscated postinstall script in a package.json, the suspicious URL in a build script, and the addition of a binary blob to a source tree.

The eslint-scope incident in 2018 and the Codecov bash uploader compromise in 2021 both involved small changes to repositories that would have been visible to a diff-scanning hook tuned to flag changes to install scripts, hardcoded URLs in build files, and binary additions outside designated directories. A hook of this kind needs a careful ruleset to avoid blocking legitimate changes, and it needs an escape hatch for repository owners to approve flagged commits explicitly. The escape hatch should require a code review with a second signoff, not a simple force-push.

What should you not put in a git hook?

Anything that takes more than a couple of seconds does not belong in a synchronous git hook. Full dependency vulnerability scans, full test suites, and full lint passes should run in CI after the push completes, not in the pre-receive hook that blocks the push from finishing. Developers will work around hooks that make git push slow, and the workarounds are uniformly worse than just running the check later.

Network calls to external services are also a bad idea in a hook. They add latency, they add a failure mode where the hook silently passes when the service is down, and they create a side channel where a compromised hook could exfiltrate the commit content. Hooks should be self-contained and synchronous against local data only. If you need to call an external API for a security decision, do it in CI, where the call can be retried, logged, and audited.

How Safeguard Helps

Safeguard's CI integrations pick up exactly where git hooks leave off. The hook layer enforces structural controls like signing, branch protection, and pattern-based diff scanning; Safeguard takes the resulting commit and runs full reachability analysis, SBOM diffing, and policy gate evaluation before the merge button becomes available. Griffin AI flags suspicious dependency changes by correlating them with our zero-day feed and TPRM scoring, catching the supply chain incidents that diff scans alone would miss. The combination gives you a fast local rejection of obvious problems and a deeper analysis of everything that passes, without forcing developers to wait on the server-side hook for the slow checks.

Never miss an update

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