Hooks and signing turn intent into enforcement
Git hooks are scripts in .git/hooks/ that fire at lifecycle events: before-commit, after-commit, before-push, before-receive, etc. Two categories matter for teams. Client-side hooks (pre-commit, commit-msg, pre-push) run on the developer's machine and catch problems before they leave the laptop — formatting, linting, banned-string detection, basic test runs. Server-side hooks (pre-receive, update) run on the server and reject pushes that violate policy — they are the last line of defense and the only one that cannot be bypassed by a careless contributor.
The practical client-side stack today: pre-commit (the Python tool, not the hook name) or Husky (Node ecosystem) configures hooks declaratively from a YAML or package.json. A hook config can run formatters (Prettier, Black), linters (ESLint, Ruff), and custom checks (no console.log, no TODO in production code, secret scanning). Each push surfaces violations early when they are cheap to fix, instead of after CI runs minutes later.
Commit signing is the second hardening layer. With git commit -S (or commit.gpgsign = true), Git signs the commit with your GPG or SSH key. GitHub's "Verified" badge confirms the signature matches a key registered to the author. This blocks impersonation: an attacker who steals a developer's account password but not their signing key cannot push commits that pass branch protection requiring signed commits. SSH commit signing (Git 2.34+) is easier than GPG and works with the same keys you already use for SSH push.
Configure once. git config --global commit.gpgsign true turns on signing for every commit. git config --global gpg.format ssh uses SSH keys instead of GPG. git config --global user.signingkey ~/.ssh/id_ed25519.pub picks the key. Branch protection rule "Require signed commits" rejects unsigned pushes. Combined with hook-enforced format and content rules, the team's standards are encoded in tooling rather than relying on careful humans.