Pre-commit Hooks as Security Gates: Enforcement Before Commit¶
The Terraform config was already committed. Three pull requests deep before anyone noticed. Now it's in git history. Audit logs show violations. The team is explaining how vendor lock-in crept in.
Pre-commit hooks stop this before it starts.
The Problem with Post-Commit Detection¶
CI catches security issues after they're committed. By then:
- Code is in git history (even if the PR never merges)
- Secrets, if committed, are already exposed (we later added TruffleHog scanning to catch this—see OpenSSF Certification)
- Audit trails show violations happened
- Fixing requires force-push or explanation
flowchart LR
A[Write Code] --> B[Commit]
B --> C[Push]
C --> D[CI Checks]
D --> E{Pass?}
E -->|No| F[Fix & Recommit]
F --> C
%% Ghostty Hardcore Theme
style A fill:#65d9ef,color:#1b1d1e
style B fill:#fd971e,color:#1b1d1e
style C fill:#fd971e,color:#1b1d1e
style D fill:#f92572,color:#1b1d1e
style E fill:#f92572,color:#1b1d1e
style F fill:#9e6ffe,color:#1b1d1e
The violation already happened at step B. Everything after is damage control.
Shift Left to Pre-Commit¶
Move enforcement to the commit boundary:
flowchart LR
A[Write Code] --> B{Pre-commit Check}
B -->|Pass| C[Commit]
B -->|Fail| D[Fix Before Commit]
D --> A
C --> E[Push]
E --> F[CI Validates]
%% Ghostty Hardcore Theme
style A fill:#65d9ef,color:#1b1d1e
style B fill:#fd971e,color:#1b1d1e
style C fill:#a7e22e,color:#1b1d1e
style D fill:#f92572,color:#1b1d1e
style E fill:#a7e22e,color:#1b1d1e
style F fill:#a7e22e,color:#1b1d1e
Violations never enter git history. CI becomes validation, not discovery.
Real-World Example: Vendor-Neutral Enforcement¶
Adaptive Enforcement Lab maintains vendor-neutral documentation. The policy:
| Forbidden | Preferred | Rationale |
|---|---|---|
| Docker | OCI, Containerfile, podman | Vendor-neutral standards |
| Terraform | Crossplane, CNRM, Pulumi | Kubernetes-native IaC |
| AWS-specific | GCP or cloud-agnostic | Avoid vendor lock-in |
This isn't documentation. It's enforced.
When NOT to Use This¶
Pre-commit hooks add friction. Use them for:
- Security policies (secrets, forbidden tech)
- Compliance requirements (audit trails, vendor rules)
- Destructive mistakes (large files, sensitive data)
Don't use them for:
- Formatting (prettier, gofmt run these automatically)
- Linting (too slow, devs will disable)
- Style preferences (code review is better)
The rule: if developers bypass the hook regularly, it shouldn't be a hook.
The --no-verify Debate¶
This bypasses all hooks. Should you allow it?
Arguments for:
- Emergency hotfixes need speed
- Developers own their commits
- CI is the real gate
Arguments against:
- Secrets committed in emergencies
- "Just this once" becomes habit
- Audit compliance requires prevention
The compromise: allow --no-verify. But CI must run the same checks. The hook is convenience. CI is enforcement.
Measuring Effectiveness¶
Track violations caught:
func logViolation(v Violation) {
// Send to metrics system
metrics.Increment("forbidden_tech.blocked", map[string]string{
"technology": v.Technology,
"file": filepath.Ext(v.File),
})
}
Dashboards show:
- Which rules catch the most violations
- Which file types are riskiest
- When developers bypass hooks (CI violations vs. pre-commit blocks)
Implementation Checklist¶
Building your own security gates:
- Define forbidden patterns - What are you blocking?
- Regex or AST? - Regex for simple cases, parse for languages
- Exception strategy - Path-based, inline suppression, or both
- Error messages - Link to policy docs, suggest alternatives
- CI integration - Same tool runs locally and in CI
- Metrics - Track blocks, bypasses, false positives
- Maintenance - Review patterns quarterly, update for new risks
Related Patterns¶
Pre-commit hooks are one layer. The full stack:
Defense in Depth
- Pre-commit hooks: Block at commit boundary (this post)
- Branch protection: Require reviews, status checks
- CI validation: Automated policy enforcement
- Audit trails: Log all policy checks
- SDLC hardening: Build security into pipelines (coming soon)
See Pre-commit Hooks That Don't Make You Wait for implementation details.
The Terraform config never makes it to git history. The Docker reference gets caught at commit time. The audit shows zero violations. Prevention works.
For implementation details, see Pre-commit Hooks Implementation Guide.