fix(security): harden destructive paths and add audit tiers (v0.6.0)

Address critical and high findings from an external security review.

Critical/high fixes:
- reset-signing no longer treats general-purpose keys (id_ed25519, etc.)
  as deletion candidates, defaults the delete prompt to No, and never
  deletes files in -y mode
- FIDO2 retry now re-runs the same attempt (for-loop reassignment bug
  silently advanced to the next fallback key type)
- core.hooksPath redirection installs dispatch stubs for all client-side
  hook types so repo-local hooks (husky, lefthook, pre-commit) keep
  running; pre-commit combines gitleaks with dispatch and warns loudly
  when gitleaks is absent
- public-key validation everywhere a key path is consumed, preventing
  private key material in allowed_signers or user.signingkey
- config backups written mode 600 (may contain tokens)
- SSH config audit/apply is scope-aware (global vs host-specific),
  appends new directives at EOF to preserve precedence, scans Include-d
  files for keys
- pubkey algorithm restriction guarded against RSA/DSA-only lockout and
  chooses the directive name by OpenSSH version

Added:
- audit tiers (security/hygiene/preference); --audit exit 2 reflects
  security-tier issues only
- signing smoke test catching No-principal-matched at setup time
- http.sslVerify audit distinguishes unset from insecure override

Docs: correct fsmonitor precedence, log.showSignature and fsckObjects
breakage, SSH scoping semantics in REASONING.md; plan for agent-backed
keys (1Password/Bitwarden/forwarded agents) in docs/specs.

126/126 BATS tests pass; shellcheck clean.

Closes #53

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Flo
2026-06-09 23:55:31 +02:00
parent e27bbaaa43
commit 382a35c47e
5 changed files with 1091 additions and 179 deletions
+28
View File
@@ -9,6 +9,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Changed
- Signing wizard "Skip" option now clarifies the agent-container use case (humans sign at PR merge)
## [0.6.0] - 2026-06-09
Fixes for the critical and high findings of an external security review, plus audit tiering.
### Security / Critical fixes
- `--reset-signing` no longer lists general-purpose keys (`id_ed25519`, `id_ed25519_sk`, `id_ecdsa_sk`) for deletion — they are likely SSH **authentication** keys. Only dedicated `*_signing` keys (and a configured key matching that convention) are candidates
- `--reset-signing` deletion prompt now defaults to **No**, offers delete / rename-to-.bak / leave-untouched, and **never deletes files in `-y` mode**
- FIDO2 key generation: "insert key and retry" actually retries the same attempt (a `for`-loop reassignment bug silently advanced to the next fallback type instead)
- `core.hooksPath` redirection no longer silently disables repo-local hooks: dispatch stubs are installed for all client-side hook types, forwarding to `.git/hooks/<name>` when present. The pre-commit hook combines the gitleaks scan with dispatch, and existing pre-dispatch hooks are offered an upgrade
- Pre-commit hook now **warns loudly** when gitleaks is missing instead of silently skipping the secret scan (still fail-open by design)
- Private key material can no longer end up in `allowed_signers` or `user.signingkey`: public-key validation everywhere a key path is consumed; a `user.signingkey` pointing at a private key is recovered to its `.pub` neighbor
- Config backups (`pre-harden-backup-*.txt`) are created with mode 600 — the gitconfig dump can contain tokens (`http.extraHeader`, `insteadOf` URLs)
### SSH config handling (high fixes)
- Audit and apply are now **scope-aware**: only directives in global scope (top-level or `Host *`) count; a directive set only in a host-specific block is reported as "no global default" instead of a false OK
- Replacements only touch global-scope occurrences and preserve indentation; host-specific values are never rewritten
- New directives are appended in a `Host *` block at **EOF** (previously prepended/inserted at top, which shadowed host-specific blocks under ssh's first-obtained-wins rule)
- `Include`-d config files are scanned for `IdentityFile` keys (one level); audit prints a notice that included directives are not audited/modified
- Pubkey algorithm restriction is guarded: skipped with a warning when the only available keys (on disk or in the SSH agent) are RSA/DSA (lockout risk; default-No override, never applied in `-y`), and the directive name is chosen per OpenSSH version (`PubkeyAcceptedKeyTypes` before 8.5; skipped for unknown/ancient clients where it would break every ssh invocation)
### Added
- Audit tiers: every item is classified security / hygiene / preference; the summary reports per-tier counts and `--audit` exit code 2 now reflects **security-tier issues only**
- Signing smoke test after enabling signing: signs a test message and verifies it against `allowed_signers` with the recorded principal, catching "No principal matched" at setup time
- `http.sslVerify` audit distinguishes unset (OK — git default is true) from an explicit insecure override
### Tests
- 14 new BATS tests (126 total): reset-signing safety, private-key guards, dispatch stubs, pre-commit dispatch and gitleaks warning, SSH scope handling, audit tiers, version bump
## [0.5.0] - 2026-04-05
### Added