Add gitleaks pre-commit hook, global gitignore, plaintext credential detection, SSH key hygiene audit, 8 new git config settings, and safe.directory wildcard detection. Fix ssh-keygen macOS compatibility, FIDO2 detection via ioreg, and interactive test isolation. Implements docs/specs/2026-03-31-v0.2.0-expanded-hardening.md Co-Authored-By: Claude <noreply@anthropic.com>
git-harden.sh
Audit and harden your global git configuration with security-focused defaults.
Protects against history rewriting, supply chain attacks, credential theft, and malicious repository exploitation. Runs on macOS and Linux.
Quick Start
# Clone and run
git clone https://github.com/YOUR_ORG/git-hardening.git
cd git-hardening
chmod +x git-harden.sh
# Audit your current config (no changes)
./git-harden.sh --audit
# Interactive mode — review and approve each change
./git-harden.sh
# Apply all recommended defaults without prompting
./git-harden.sh -y
On first interactive run, the script asks you to confirm you've reviewed it for safety. If you haven't, it prints instructions for piping it to Claude Code or Gemini CLI for an automated review.
What It Does
The script runs in two phases:
- Audit — scans your current
git config --globaland~/.ssh/config, prints a color-coded report:[OK]already set to the recommended value[WARN]set to a non-recommended value[MISS]not configured
- Apply — for each non-OK setting, shows what it does and prompts you to accept or skip (or auto-applies with
-y)
Settings Applied
| Category | What it does |
|---|---|
| Identity | user.useConfigOnly=true — prevents commits without explicit identity |
| Object integrity | fsckObjects on transfer/fetch/receive, transfer.bundleURI=false, fetch.prune=true |
| Protocol restrictions | Default-deny policy: only HTTPS and SSH. Blocks git:// and ext://. Forces protocol.version=2 |
| Filesystem protection | core.protectNTFS, core.protectHFS, core.fsmonitor=false, core.symlinks=false (interactive-only) |
| Hook control | Redirects core.hooksPath to ~/.config/git/hooks so repo-local hooks can't execute |
| Pre-commit hook | Installs gitleaks secret scanner as global pre-commit hook (with SKIP_GITLEAKS bypass) |
| Repository safety | safe.bareRepository=explicit, submodule.recurse=false, detects/removes safe.directory=* wildcard |
| Pull/merge hardening | pull.ff=only, merge.ff=only — refuses non-fast-forward merges |
| Transport security | Rewrites http:// to https://, enforces http.sslVerify=true |
| Credential storage | Platform-detected secure helper (osxkeychain on macOS, libsecret on Linux). Warns if using plaintext store |
| Credential hygiene | Warns about plaintext ~/.git-credentials, ~/.netrc, ~/.npmrc (tokens), ~/.pypirc (passwords) |
| Global gitignore | Creates ~/.config/git/ignore with patterns for secrets, credentials, and OS/IDE artifacts |
| Defaults | init.defaultBranch=main |
| Forensic readiness | Extended reflog retention (gc.reflogExpire=180.days, gc.reflogExpireUnreachable=90.days) |
| Commit signing | SSH-based signing with interactive key setup wizard (software or FIDO2 hardware key) |
| SSH hardening | StrictHostKeyChecking=accept-new, HashKnownHosts=yes, IdentitiesOnly=yes, modern algorithm restrictions |
| SSH key hygiene | Audits ~/.ssh/*.pub for weak key types (DSA, ECDSA, short RSA) |
| Visibility | log.showSignature=true |
A config backup is saved to ~/.config/git/pre-harden-backup-<timestamp>.txt before any changes.
Signing Setup
The script includes an interactive wizard that:
- Detects existing SSH keys (including custom-named keys from
~/.ssh/config) - Detects FIDO2 hardware (YubiKey, etc.)
- Offers two tiers:
- Software SSH key — use existing
ed25519or generate one - FIDO2 hardware key — generate
ed25519-skwith touch-to-sign (if hardware detected)
- Software SSH key — use existing
- Configures
user.signingkey,commit.gpgsign,tag.gpgsign - Sets up
~/.config/git/allowed_signersfor local signature verification
With -y, the script auto-detects the best available key. If no key exists, signing config is prepared but not enabled (to avoid breaking commits).
Privacy note: The signing wizard warns that reusing the same signing key across personal and work accounts enables cross-platform identity correlation (OSINT risk). For identity separation, generate dedicated keys per context and use git's includeIf for per-org config.
Usage
git-harden.sh [OPTIONS]
Options:
--audit Audit only, no changes (exit code 2 if issues found)
-y, --yes Auto-apply all recommended defaults
--help, -h Show help
--version Show version
Exit Codes
| Code | Meaning |
|---|---|
| 0 | All OK, or changes applied successfully |
| 1 | Error (missing dependencies, etc.) |
| 2 | Audit found issues (--audit mode) |
Requirements
git>= 2.34.0 (required for SSH signing)ssh-keygen- Bash 3.2+ (compatible with macOS default bash)
Optional:
gitleaksfor pre-commit secret scanning (hook is installed regardless; scans run only if gitleaks is on$PATH)ykmanorfido2-tokenfor FIDO2 hardware key detection
Threat Model
What this protects against
- History rewriting —
pull.ff=onlyandmerge.ff=onlyrefuse non-fast-forward operations, making force-pushed changes visible - Object injection —
fsckObjectsvalidates every object transferred, catching corruption or malicious payloads - Protocol downgrade — blocks plaintext
git://and dangerousext://protocol - Hook-based RCE — redirects hook execution away from repo-local
.git/hooks/ - Submodule attacks — disables auto-recursion; submodules must be explicitly initialized
- Credential theft — ensures secure credential storage, warns about plaintext
store, detects leaked credentials in~/.git-credentials,~/.netrc,~/.npmrc,~/.pypirc - Secret leakage — gitleaks pre-commit hook blocks commits containing secrets before they enter git history
- Commit impersonation — SSH signing proves key possession (anyone can fake
user.name/user.email) - Filesystem tricks — blocks NTFS/HFS+/symlink path manipulation attacks
- Weak SSH keys — audits and warns about DSA, ECDSA, and short RSA keys
What this does NOT protect against
- A compromised machine (malware can use cached keys)
- Malicious code from an authorized signer
- Historical unsigned commits (signing is not retroactive)
- Server-side misconfigurations (see admin recommendations printed by the script)
Admin Recommendations
The script prints (but does not apply) server/org-level recommendations:
- Enable "require signed commits" on protected branches
- Enable GitHub/GitLab vigilant mode
- Restrict force-pushes server-side
- Use fine-grained, short-lived tokens in CI/CD
- Maintain an allowed signers file in repos
- Clone untrusted repos with
--no-recurse-submodules - Use separate signing keys per org to prevent cross-platform identity correlation (OSINT)
Running Tests
# Run the BATS test suite (90 tests)
./test/run.sh
# Requires bats-core submodules — init them if needed
git submodule update --init --recursive
Tests run in an isolated $HOME (via mktemp) and never touch your real git or SSH config.
License
MIT