A terminal prompt technique to see the status of git hooks.
Frank Wiles wrote about an attack he nearly fell victim to: a downloaded git repo had active git hooks that would have compromised his system. Seeing the risks, I wanted to make git hooks more visible on my own machine.
A git hook is a script in the .git directory of a git repo on your machine. They run during various git operations. The important detail to understand about hooks is that they do not get installed locally when you clone a repo. A newly cloned repo never has active hooks. Hooks have to be enabled after cloning, usually by you.
The attack Frank saw was dangerous because it wasn’t a cloned repo: it was a local checkout that had hooks installed then shared as a Dropbox folder. So when it landed on Frank’s machine, the hooks were active without him realizing it.
Git hooks are not apparent: they are meant to sit quietly until they run and ideally to be unobtrusive even then. But this attack made me want to know what hooks were in each of my git directories.
In zsh, you can define a function called chpwd, and it will execute whenever the current directory changes. Using this feature, I could create a script that lists the active hooks when I change into a git directory:
git_show_hooks() {
local git_dir=$(git rev-parse --absolute-git-dir 2>/dev/null)
if [[ -z $git_dir ]]; then
_git_shown_hooks_dir=
return
fi
local hooks_dir=$(git rev-parse --git-path hooks)
hooks_dir=${hooks_dir:a}
# Only announce once per repo, not for every cd inside it.
if [[ $hooks_dir == $_git_shown_hooks_dir ]]; then
return
fi
_git_shown_hooks_dir=$hooks_dir
local -a hooks
local hook
setopt localoptions nullglob
for hook in $hooks_dir/*; do
if [[ -f $hook && -x $hook && $hook != *.sample ]]; then
hooks+=(${hook:t})
fi
done
if (( $#hooks )); then
echo -e "\e[31mgit hooks:\e[0m $hooks (from $git_dir)"
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook chpwd git_show_hooks
Now entering a git repo I get an announcement of active hooks, if any. It’s very rare to see a hook other than pre-commit, but if any are there, they will be reported. The message will only be shown the first time I cd into a repo to keep the noise down.
Once I had this in place, there was another git hook consideration I could address: it’s common to have a .pre-commit-config.yaml file in a repo, but it’s easy to forget to enable the pre-commit hook that will actually use the file. Many developers probably have cloned repos where they haven’t enabled the pre-commit hook, so the desired checks aren’t running.
A second chpwd function can look for the configuration file and let you know if you have the pre-commit hook enabled:
git_check_precommit() {
local toplevel=$(git rev-parse --show-toplevel 2>/dev/null)
if [[ -z $toplevel ]]; then
_git_shown_precommit_dir=
return
fi
# Only announce once per repo, not for every cd inside it.
if [[ $toplevel == $_git_shown_precommit_dir ]]; then
return
fi
_git_shown_precommit_dir=$toplevel
if [[ ! -f $toplevel/.pre-commit-config.yaml ]]; then
return
fi
local hooks_dir=$(git rev-parse --git-path hooks)
hooks_dir=${hooks_dir:a}
local pc_tool=
if [[ -f $hooks_dir/pre-commit ]]; then
if grep -q 'File generated by prek:' $hooks_dir/pre-commit 2>/dev/null; then
pc_tool=prek
elif grep -q 'File generated by pre-commit:' $hooks_dir/pre-commit 2>/dev/null; then
pc_tool=pre-commit
fi
fi
if [[ -n $pc_tool ]]; then
echo "$pc_tool installed"
else
echo -e "\e[31mpre-commit:\e[0m not installed," \
"run 'pre-commit install' or 'prek install'" \
"($toplevel/.pre-commit-config.yaml)"
fi
}
add-zsh-hook chpwd git_check_precommit
One more step, just to be extra safe: zsh doesn’t run the chpwd function when starting a new shell. So we run the functions explicitly to check our initial directory:
# Run them once on shell startup to check our starting directory.
git_show_hooks
git_check_precommit
These shell functions are keeping me aware of the enabled git hooks. The messages might appear too often, and I might stop noticing them after a while, but I think this is much better than not knowing what’s going on. I haven’t seen any attacks, but I found at least one of my projects where I forgot to install pre-commit.
Comments
Add a comment: