Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXV · HooksHooks

Hooks and supply chain — secret scanning, dependency review, and the role of hooks versus CI

Intermediate⏱ ~22 mingit

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

Not yet marked complete on this device.

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:

  1. Hooks (Tier 1). Fast feedback at the developer machine. Catches committed secrets and staged-format violations. Bypassable.
  2. 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.
  3. 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.
  4. 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

  1. Run gitleaks in pre-commit and in CI. The hook gives fast feedback; CI gives enforcement. Both are needed.
  2. Run the dependency audit in pre-push and in CI. The hook catches the issue before the push; CI catches it before the merge.
  3. 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.
  4. 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).
  5. 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

  1. 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?

  2. 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.

  3. Q3. List the four supply-chain defence tiers in order of increasing strength, and name the mechanism each tier uses.

  4. 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.