git-hookd runs shell scripts automatically during git operations. The primary threats are:
-
Arbitrary code execution via
.worktree-initmanifests: The[run]section executes shell commands. A malicious manifest in a cloned repo could run arbitrary code on checkout. -
Path traversal in file operations: The
[link]and[copy]sections resolve relative paths. Crafted entries like../../.ssh/authorized_keyscould read or write outside the worktree. -
Environment variable misconfiguration:
GIT_HOOKD_DIRcontrols where hooks are installed and whererm -rfruns during uninstall. A misconfigured or unintentionally exported value could cause hooks to run from an unexpected location or delete the wrong directory. -
Uninstall safety:
rm -rfon a user-controlled path requires validation to avoid deleting unrelated directories.
The [run] section in .worktree-init manifests is blocked by default.
Commands are displayed but not executed until explicitly allowed via git config:
# Allow [run] in a specific repo
git config hookd.worktree-init.allow-run true
# Allow [run] globally (use with caution)
git config --global hookd.worktree-init.allow-run trueThis is inspired by direnv's trust model: you must explicitly opt in per-repo or globally. Unlike direnv, which re-validates on file content change, the git config toggle trusts all future manifest edits once set. Git config's standard precedence applies (local overrides global).
Both [link] and [copy] sections validate that resolved paths stay within
their expected directories. Paths containing .. that would escape the
worktree or main worktree are rejected with a warning. Path resolution uses
python3 -c "import os; print(os.path.normpath(...))" for portable
normalization across macOS and Linux (macOS realpath lacks the -m flag
needed for paths that don't exist yet).
GIT_HOOKD_DIRis validated on every CLI invocation and in the installer: must be an absolute path, must not contain..components, and must not point to dangerous locations (/,$HOME,/tmp,/etc,/usr,/var). Only these exact root paths are blocked; subdirectories like/tmp/my-hookdare permitted.- Remote names in the auto-fetch module are validated against
^[a-zA-Z0-9_.-]+$to prevent shell injection. - Module skip matching uses
grep -qxF(fixed strings, full line) to prevent regex injection via module names.
Before rm -rf on the hookd directory, the uninstall command verifies a _hookd
file exists in the target. This prevents accidental deletion if GIT_HOOKD_DIR
points somewhere unexpected.
When restoring a previously saved core.hooksPath, the saved value is validated:
empty paths and paths that don't start with / or ~/ are rejected, and
core.hooksPath is unset instead.
- CI pins
actions/checkoutto a commit SHA (not a mutable tag). - The
shfmtbinary downloaded in CI is verified against a SHA256 checksum from the official release.
If you find a security issue, please email the maintainer directly rather than opening a public issue. You can find contact information in the git commit history. Include steps to reproduce and any relevant details about the environment.