Git Commit Validation Pipeline¶
Purpose: Enforce code quality, security, and consistency across all commits to the scandora.net infrastructure repository.
Philosophy: Prevent issues at commit time, not in production. Every commit should be production-ready.
Table of Contents¶
- Overview
- Installation
- Validation Tiers
- Daily Workflow
- Handling Failures
- False Positives
- Performance
- Troubleshooting
- CI/CD Integration
Overview¶
The validation pipeline runs automatically on every git commit via pre-commit hooks. It includes 9 tiers of checks:
| Tier | Category | Tools | Purpose |
|---|---|---|---|
| 1 | Security | gitleaks, trufflehog | Detect hardcoded secrets |
| 2 | File Hygiene | pre-commit-hooks | Whitespace, EOF, merge conflicts |
| 3 | Ansible | ansible-lint | Infrastructure validation |
| 4 | Terraform | terraform fmt/validate, tfsec | IaC formatting + security |
| 5 | Docker | hadolint | Dockerfile best practices |
| 6 | Shell | shellcheck | Bash/sh script linting |
| 7 | YAML | yamllint | YAML syntax (non-Ansible) |
| 8 | Python | black | Python formatting |
| 9 | Commit Messages | conventional-pre-commit | Enforce commit format |
Estimated run time: 30-60s (first run: 5-10min for tool installation)
Installation¶
Prerequisites¶
# Install pre-commit
brew install pre-commit
# Install additional tools (optional, for local runs)
brew install gitleaks tfsec hadolint shellcheck shfmt
Setup¶
cd ~/src/scandora.net
# Install git hooks
pre-commit install # For commit-time validation
pre-commit install --hook-type commit-msg # For commit message validation
# Test installation
pre-commit run --all-files
✓ Done! Hooks will now run automatically on every commit.
Validation Tiers¶
TIER 1: Security (MUST-PASS)¶
Gitleaks - Primary secrets detection
- Scans for: API keys, passwords, tokens, private keys
- Uses: Pattern matching (fast, low false-positives)
- Whitelist:
.gitleaksignore
TruffleHog - Deep secret scanning
- Scans for: High-entropy strings, unusual patterns
- Uses: Entropy analysis (slower, catches obfuscated secrets)
- May produce false positives (review carefully)
⚠️ CRITICAL: If secrets are detected, DO NOT COMMIT. Follow the remediation steps in Handling Failures.
TIER 2: File Hygiene¶
Automatic fixes for common issues:
- Trailing whitespace removed
- Final newline added to files
- Line endings normalized to LF (Unix)
- Large files blocked (>1MB)
- Merge conflict markers detected
TIER 3: Ansible (Infrastructure)¶
ansible-lint with production profile:
- Enforces task names, tags, handlers
- Checks for deprecated modules
- Validates YAML syntax
- Auto-fixes with
--writeflag
Common failures:
- Missing task names
- Deprecated
whensyntax - Untagged tasks
Fix: Run ansible-lint --write cloud/ansible/ to auto-fix most issues.
TIER 4: Terraform (Security + Best Practices)¶
terraform fmt - Formatting
- Enforces canonical formatting
- Auto-fixes on commit
terraform validate - Syntax checking
- Validates HCL syntax
- Checks module references
- Note: Requires
terraform initin each environment
tfsec - Security scanning
- Detects: Unencrypted resources, overly permissive security groups, exposed secrets
- Severity: HIGH+ only
- See:
.tflint.hclfor configuration
Manual: tflint - Best practices
- Run:
tflint --config=.tflint.hcl --recursive - Not in pre-commit (requires plugins)
TIER 5: Docker¶
hadolint - Dockerfile linting
- Best practices: USER directive, layer optimization
- Security: Don't run as root, use COPY > ADD
- Ignored rules: DL3008, DL3018 (version pinning too strict)
TIER 6: Shell Scripts¶
shellcheck - Bash/sh linting
- Detects: Quoting issues, deprecated syntax, security issues
- Assumes:
bash(not POSIX sh) - Exclusions: Terraform heredocs (confuse shellcheck)
Manual: shfmt - Shell formatting
- Run:
shfmt -i 2 -ci -bn -sr -w scripts/**/*.sh - Not in pre-commit (no hook available)
TIER 7: YAML (Non-Ansible)¶
yamllint - YAML syntax
- Line length: 120 chars max
- Comments: Minimum 1 space from content
- Excludes: Ansible files (use ansible-lint)
TIER 8: Python¶
black - Python formatting
- Opinionated formatter (no configuration)
- Enforces: Double quotes, consistent spacing
TIER 9: Commit Messages¶
conventional-pre-commit - Commit format
- Enforces:
type(scope): subjectformat - Types:
feat|fix|docs|refactor|test|chore|style|perf|ci|build|revert
Examples:
feat(ddns): add 1Password integration for credentials
fix(ansible): correct OPNsense API auth flow
docs(security): document secrets audit findings
refactor(terraform): consolidate static IP configs
Daily Workflow¶
Normal Commits¶
# Make changes
vim scripts/ddns/cf-ddns.sh
# Stage changes
git add scripts/ddns/cf-ddns.sh
# Commit (hooks run automatically)
git commit -m "feat(ddns): migrate to 1Password credential retrieval"
# If hooks pass:
✅ All checks passed! Commit created.
# If hooks fail:
❌ Some checks failed. Fix issues and re-commit.
Manual Validation (Before Commit)¶
# Run all hooks on staged files
pre-commit run
# Run all hooks on entire codebase
pre-commit run --all-files
# Run specific hook
pre-commit run gitleaks
pre-commit run ansible-lint
# Show what would change (dry-run)
pre-commit run --all-files --show-diff-on-failure
Emergency Bypass (Use Sparingly!)¶
# Skip hooks for WIP commits
git commit --no-verify -m "wip: debugging network issue"
# ⚠️ WARNING: Bypassed commits still need validation before push!
# Run before pushing:
pre-commit run --all-files
When to bypass:
- Urgent production fixes (will validate before push)
- Local WIP commits (not pushed to remote)
- Debugging (temporary state)
NEVER bypass for:
- Commits pushed to main/master
- Pull requests
- Shared branches
Handling Failures¶
Secrets Detected¶
❌ STOP! DO NOT COMMIT!
# Example output:
🔐 Gitleaks - Detect hardcoded secrets..................Failed
Finding: scripts/ddns/owl.conf:18
Secret: CF_API_KEY="b9850b275095a87122890c185b1e642f38323"
Steps:
- Remove the secret from the file
- Add to .gitleaksignore if it's a false positive (with justification)
- Rotate the credential if it was committed previously
- Check git history for previous leaks:
Auto-Fixable Failures¶
Many tools can auto-fix issues:
# Terraform formatting
terraform fmt -recursive cloud/terraform/
# Ansible linting
ansible-lint --write cloud/ansible/
# Python formatting
black cloud/ansible/**/*.py scripts/**/*.py
# Shell formatting (manual)
shfmt -i 2 -ci -bn -sr -w scripts/**/*.sh
# Re-run hooks
pre-commit run --all-files
Manual Fixes Required¶
ShellCheck failures:
# View specific errors
shellcheck scripts/ddns/cf-ddns.sh
# Common issues:
# - SC2086: Quote variables to prevent word splitting
# - SC2154: Variable referenced but not assigned
# - SC2155: Declare and assign separately
Ansible-lint failures:
# View details
ansible-lint cloud/ansible/playbooks/site.yml
# Common issues:
# - Add task names: `name: "Install package"`
# - Add tags: `tags: [setup]`
# - Use FQCN: `ansible.builtin.copy` instead of `copy`
False Positives¶
Whitelisting Secrets (Gitleaks)¶
Edit .gitleaksignore:
# Get fingerprint from report
jq -r '.[] | select(.File == "path/to/file") | .Fingerprint' /tmp/gitleaks.json
# Add to .gitleaksignore with explanation
# WireGuard public key (not a secret)
*:docs/vpn/wireguard.md:generic-api-key:*cmOApvAKmLEHmS93O8NrfnmNPUKhokNb*
Acceptable whitelists:
- Example credentials in documentation
- Public keys (SSH, WireGuard, certificates)
- Encrypted data (OPNsense config.xml with base64-encoded certs)
- Deployment placeholders (
__API_KEY__)
NEVER whitelist:
- Real API keys or passwords
- Private keys
- Tokens with actual permissions
Ignoring Specific Rules¶
Inline suppression:
# Ansible
- name: Legacy command (no module exists)
command: /usr/local/bin/custom-tool
changed_when: false
# noqa: command-instead-of-module
# YAML
# yamllint disable-line rule:line-length
long_key: "very long value that exceeds 120 characters but is unavoidable"
File-level suppression:
Performance¶
First Run (Cold Cache)¶
Subsequent Runs (Warm Cache)¶
# Expected: 30-60 seconds
# Only validates changed files on commit
git commit -m "..."
# Full codebase scan
# Expected: 2-5 minutes
pre-commit run --all-files
Optimization Tips¶
- Run specific hooks for faster feedback:
- Use
--filesflag for targeted checks:
- Skip slow hooks during iteration:
- Update cache weekly:
Troubleshooting¶
"Hook failed to install"¶
"Command not found: gitleaks"¶
Pre-commit will install tools automatically. If manual installation needed:
"Terraform validate failed"¶
Terraform requires terraform init in each environment:
Or skip terraform validation during local development:
"Ansible-lint too strict"¶
Adjust .ansible-lint profile or skip rules:
"Hooks running slow"¶
Check for network issues (downloading tools) or disable slow hooks:
# Check what's slow
pre-commit run --all-files --verbose
# Disable TruffleHog (slowest)
# Edit .pre-commit-config.yaml and remove trufflehog entry
CI/CD Integration¶
GitHub Actions (Future)¶
# .github/workflows/validate.yml
name: Validation
on: [push, pull_request]
jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: pre-commit/action@v3.0.1
Pre-Push Hook (Optional)¶
Run validation before pushing to remote:
# Install pre-push hook
pre-commit install --hook-type pre-push
# Configure to run all hooks
# Edit .git/hooks/pre-push and add:
#!/bin/bash
pre-commit run --all-files
Summary¶
Key Commands:
pre-commit run # Validate staged files
pre-commit run --all-files # Validate entire codebase
pre-commit autoupdate # Update hook versions
git commit --no-verify # Emergency bypass (use sparingly)
Best Practices:
- ✅ Never commit secrets - Use 1Password retrieval patterns
- ✅ Fix issues immediately - Don't accumulate validation debt
- ✅ Whitelist responsibly - Document why false positives are safe
- ✅ Test before committing - Run
pre-commit runproactively - ✅ Keep hooks updated - Run
pre-commit autoupdateweekly
Support:
- Report issues:
https://github.com/scandora/scandora.net/issues - Hook documentation:
/Users/joe/src/scandora.net/.pre-commit-config.yaml - Whitelist guidance:
/Users/joe/src/scandora.net/.gitleaksignore