Git, CI/CD & GitOpsXXV · HooksHooks
Hooks and supply chain — secret scanning, dependency review, and the role of hooks versus CI
What you'll learn
- Configure a pre-commit hook that runs gitleaks (or trufflehog) to scan staged content for known secret patterns
- Configure a pre-push hook that runs a dependency audit (npm audit, pip-audit, osv-scanner) before the push leaves the machine
- Distinguish the supply-chain checks that belong in a client-side hook (fast, narrow scope) from those that belong in CI (slow, broader scope, mandatory)
- Recognise that hooks are one tier of supply-chain defence; signing, attestation, and SBOMs are the others
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The supply chain for an infrastructure repository is the chain of trust from the commit the engineer makes on their laptop to the artifact that runs in production. A hook is the earliest point in that chain where a check can fire: before the commit is recorded, before the push leaves the machine. This lesson covers the supply-chain checks a hook can run (secret scan, dependency audit), the tools that implement them, and the structural distinction between what hooks catch and what CI catches.
The supply-chain attack surface
An infrastructure repository is exposed to four supply-chain attack classes:
flowchart LR
subgraph AT["Attack surface"]
A1["Committed secrets\n(AWS keys, API tokens)"]
A2["Vulnerable dependencies\n(known CVEs in pinned versions)"]
A3["Unverified provenance\n(unsigned commits, unsigned tags)"]
A4["Tampered artifacts\n(registry hijack, mirror compromise)"]
end
A1 --> D1["pre-commit hook: gitleaks, trufflehog"]
A2 --> D2["pre-push hook: npm audit, pip-audit, osv-scanner"]
A3 --> D3["pre-receive hook: signing verification\nCI: attestation, SLSA"]
A4 --> D4["CI: signature verification, SBOM diff\nregistry: digest pinning"]
A hook is the right tier for the first two: secret scan and dependency audit. Both can run on the developer’s machine in seconds, both can be enforced in CI as a backup, and both have well-maintained open-source tools.
The third and fourth (unverified provenance, tampered
artifacts) are not hook-tier concerns. Provenance is a signing
problem (covered in Part XIX on signed commits and signed
tags); artifact integrity is a registry problem (digest
pinning, signature verification at deploy time). Hooks can
support these controls (a pre-receive hook that verifies
signatures) but they are not the primary defence.
pre-commit: gitleaks for secret scanning
Gitleaks is the standard open-source tool for scanning staged content for known secret patterns (AWS access keys, GitHub tokens, private keys, database URIs). A pre-commit hook that runs gitleaks against the staged diff catches a secret before it is recorded.
#!/usr/bin/env bash
# .git/hooks/pre-commit
# Scan staged content with gitleaks; refuse on any finding
set -euo pipefail
# Run gitleaks against the staged diff
# (gitleaks protect --staged scans only staged files)
gitleaks protect --staged --redact --verbose
Gitleaks reads the staged diff via git diff --cached and
applies a configurable set of regex patterns. The default rule
set covers the major cloud-provider key formats (AWS, GCP,
Azure), the major SaaS tokens (GitHub, GitLab, Slack,
Stripe), and the common private-key formats (PEM, SSH). A
finding causes gitleaks to exit non-zero; the hook propagates
the exit code; the commit is aborted.
flowchart LR
S["git add <paths>"] --> C["git commit"]
C --> PC["pre-commit hook"]
PC --> GL["gitleaks protect --staged"]
GL --> P{"matches a secret pattern?"}
P -->|"no, exit 0"| CMT["commit recorded"]
P -->|"yes, exit non-zero"| AB["commit aborted, finding printed to stderr"]
The same pattern works with trufflehog (which also checks against known-breach corpora) and with a custom regex set. The trade-off between gitleaks and trufflehog is speed (gitleaks is faster) versus depth (trufflehog catches more patterns but takes longer). For pre-commit, gitleaks is the default choice because the 200ms feedback time matches the “every commit” cadence.
pre-push: dependency audit
A pre-push hook that runs a dependency audit catches a vulnerable pinned version before the push leaves the machine. The tools depend on the ecosystem:
#!/usr/bin/env bash
# .git/hooks/pre-push
# Run dependency audit before push; refuse on known CVE
set -euo pipefail
# Node.js
if [ -f package.json ]; then
echo "Running npm audit..."
npm audit --audit-level=high
fi
# Python
if [ -f pyproject.toml ] || [ -f requirements.txt ]; then
echo "Running pip-audit..."
pip-audit --strict
fi
# Multi-ecosystem (uses the OSV database)
if command -v osv-scanner >/dev/null 2>&1; then
echo "Running osv-scanner..."
osv-scanner --recursive .
fi
The audit runs against the pinned versions in package.json,
requirements.txt, pyproject.toml, or any other lock file
the project uses. A finding at the configured severity level
(usually high or critical) causes the audit to exit
non-zero; the hook propagates; the push is aborted.
The audit is too slow for pre-commit (npm audit takes
5-30 seconds depending on the lockfile size) but fits the
pre-push cadence (once per push, the cost is amortised over
the push). The same audit runs in CI as a required status
check; the pre-push hook is the fast feedback, CI is the
enforcement.
What belongs in hooks versus CI
The structural rule:
- Hook-tier checks are fast (under a few seconds), run on every commit (pre-commit) or every push (pre-push), and catch issues that the engineer would want fixed before the commit is recorded or the push leaves the machine. The scope is the staged content (pre-commit) or the outgoing pack plus the local dependency state (pre-push).
- CI-tier checks are slower (seconds to minutes), run on every push to the server (or every PR), and catch issues that the team would want fixed before the merge. The scope is the full repository, the full dependency tree, and the full build context.
flowchart TB
subgraph HOOK["Hook tier (developer machine)"]
H1["pre-commit: gitleaks (secret scan)"]
H2["pre-commit: formatter (terraform fmt)"]
H3["pre-commit: linter (shellcheck, tflint)"]
H4["pre-push: dependency audit (npm audit)"]
H5["pre-push: full test suite"]
end
subgraph CI["CI tier (server, mandatory)"]
C1["secret scan (full history)"]
C2["dependency review (full diff vs base)"]
C3["SBOM generation"]
C4["vulnerability scan (Trivy, Grype)"]
C5["signature verification"]
C6["attestation (SLSA, in-toto)"]
end
HOOK -->|"fast feedback\nbypassable"| CI
CI -->|"mandatory\nstructural"| MERGE["merge allowed"]
A check that fits in both tiers (gitleaks, dependency audit) belongs in both. The hook is fast feedback; CI is the enforcement that catches bypasses.
A check that only fits in CI (SBOM generation, vulnerability scan against a container image, signature verification of downstream artifacts) belongs only in CI. Putting it in a hook is either impossible (it requires the production registry) or slow (it takes minutes and engineers will bypass).
Hooks are one tier; signing and SBOMs are the others
The supply chain for an infrastructure repository has four defensive tiers, in increasing order of strength:
- Hooks (Tier 1). Fast feedback at the developer machine. Catches committed secrets and staged-format violations. Bypassable.
- CI (Tier 2). Runs on every push and PR. Catches the same issues plus slower ones (full dependency review, SBOM generation, vulnerability scan). Mandatory if branch protection requires the check.
- Signing (Tier 3). Signed commits and signed tags (Part XIX) make the provenance verifiable. A consumer can verify that a commit was authored by the claimed key.
- Attestation and SBOM (Tier 4). SLSA-style attestations and SBOMs (in-toto, Sigstore) make the build process verifiable. A consumer can verify that the artifact was built from the claimed source by the claimed builder.
Hooks are Tier 1; they are necessary but not sufficient. A team that has only hooks has fast feedback but no enforcement. A team that has only CI has enforcement but no fast feedback. A team that has hooks and CI and signing has the full chain of trust from the developer’s commit to the verifiable artifact.
Production discipline
- Run gitleaks in pre-commit and in CI. The hook gives fast feedback; CI gives enforcement. Both are needed.
- Run the dependency audit in pre-push and in CI. The hook catches the issue before the push; CI catches it before the merge.
- Treat any finding as a leaked secret. A gitleaks
finding in the pre-commit hook or in CI means the secret
has been considered for commit. Rotate the secret
immediately; remove from history with
git filter-repo; re-pin downstream consumers. - Combine hooks with signing. A signed commit (Part XIX) plus a secret-scan hook plus a CI vulnerability scan is a three-tier defence that catches the engineer who bypasses the hook (CI catches it), the engineer who pastes a tampered dependency (CI catches it), and the impersonator who claims to be the engineer (signing catches it).
- Make the hook fail-open on network errors. A hook that blocks commits when the engineer is offline is a hook that will be uninstalled. The CI run is the authoritative tier.
Cross-course references
- Git, CI/CD & GitOps - Part XIX (Signing) covers signed commits and signed tags, which are Tier 3 of the supply- chain defence.
- Git, CI/CD & GitOps - Part XXII (ForcePush) lesson 06
covers the incident response when a leak has reached
history; the procedure is to rotate, remove from history
with
git filter-repo, force-push from an admin account, and re-pin downstream consumers. - CI/CD Pipeline Patterns - Parts V (SupplyChain) and VIII (BranchPolicies) cover the CI and hosted enforcement tiers for supply-chain controls.
- GitOps for Production Sysadmins - Parts IV (Sigstore) and V (SLSA) cover the Tier 4 attestation and SBOM controls.
Quiz
Knowledge check · 4 questions
Q1. A team wants to block any commit that contains an AWS access key. Which combination of hooks and CI gives the strongest defence without slowing the developer feedback loop?
Q2. Gitleaks in a pre-commit hook scans only the staged content of the next commit (`git diff --cached`) and does not retroactively scan the repository history; a secret that was committed before gitleaks was installed, or via `--no-verify`, lives in the history until explicitly removed.
Q3. List the four supply-chain defence tiers in order of increasing strength, and name the mechanism each tier uses.
Q4. Diagnose why a team that runs gitleaks only in pre-commit still has a leaked key in production, and recommend the layered defence plus the rotation procedure that would close the gap.
A team has a pre-commit hook that runs gitleaks on staged content. An engineer installs the hook, then runs `git commit --no-verify -m 'tf plan output'` after pasting an AWS access key into a Terraform variable file by accident. The commit is recorded, the engineer pushes it, and CI does not run gitleaks. Six weeks later, the AWS account shows anomalous activity from a key the team does not recognise. The forensic trail shows the key in the repository history at commit `8a3f9d2`.
Passing score: 75%. Answers are checked in this browser.