A Simple Global Git Hook to Block Sensitive Strings
- Set up global Git hooks
- Add the blocklists
- Check commit messages
- Check the staged diff
- What happens when something is blocked?
- Why make it global?
Sometimes you want Git to stop you before you accidentally publish something you shouldn’t.
For me, there are two useful cases:
- avoiding identity leaks when contributing anonymously
- blocking automated commit metadata such as
Co-authored-by
A small global Git hook can catch both before the commit is created.
Set up global Git hooks
Create a global hooks directory and a place for the blocklists:
mkdir -p ~/.config/git/hooks ~/.config/git/git-guard
git config --global core.hooksPath "$HOME/.config/git/hooks"
Git will now use that hooks directory for every repository.
Add the blocklists
For commit messages:
# ~/.config/git/git-guard/message.blocklist
Co-authored-by
For staged changes:
# ~/.config/git/git-guard/diff.blocklist
alice@example.org
Alice Example
private-username
The checks below are case-insensitive, so Alice Example, alice example, and ALICE EXAMPLE are all caught.
Check commit messages
Create ~/.config/git/hooks/commit-msg:
#!/bin/sh
BLOCKLIST="$HOME/.config/git/git-guard/message.blocklist"
MSG_FILE="$1"
[ -f "$BLOCKLIST" ] || exit 0
patterns="$(mktemp)"
trap 'rm -f "$patterns"' EXIT HUP INT TERM
sed 's/\r$//' "$BLOCKLIST" |
awk 'NF && $0 !~ /^[[:space:]]*#/' > "$patterns"
[ -s "$patterns" ] || exit 0
matches="$(grep -Fin -f "$patterns" "$MSG_FILE")"
if [ -n "$matches" ]; then
echo >&2
echo "ERROR: commit message contains a blocked string:" >&2
printf '%s\n' "$matches" >&2
echo >&2
echo "Blocklist: $BLOCKLIST" >&2
exit 1
fi
exit 0
Check the staged diff
Create ~/.config/git/hooks/pre-commit:
#!/bin/sh
BLOCKLIST="$HOME/.config/git/git-guard/diff.blocklist"
[ -f "$BLOCKLIST" ] || exit 0
patterns="$(mktemp)"
diff="$(mktemp)"
trap 'rm -f "$patterns" "$diff"' EXIT HUP INT TERM
sed 's/\r$//' "$BLOCKLIST" |
awk 'NF && $0 !~ /^[[:space:]]*#/' > "$patterns"
[ -s "$patterns" ] || exit 0
git diff --cached --no-ext-diff --no-color --unified=0 -- |
awk '
/^--- / { next }
/^\+\+\+ / { next }
/^[+-]/ { print }
' > "$diff"
matches="$(grep -Fin -f "$patterns" "$diff")"
if [ -n "$matches" ]; then
echo >&2
echo "ERROR: staged diff contains a blocked string:" >&2
printf '%s\n' "$matches" >&2
echo >&2
echo "Blocklist: $BLOCKLIST" >&2
exit 1
fi
exit 0
Make both hooks executable:
chmod +x ~/.config/git/hooks/pre-commit
chmod +x ~/.config/git/hooks/commit-msg
What happens when something is blocked?
Imagine a tool has added this to your commit message:
Co-authored-by: Example Person <example@example.org>
When you try to commit:
$ git commit --amend
ERROR: commit message contains a blocked string:
5:Co-authored-by: Example Person <example@example.org>
Blocklist: /home/user/.config/git/git-guard/message.blocklist
The commit is rejected, and you get both the matching line and the blocklist that caused it.
The same thing happens if a blocked name, email address, username, or other identifying string appears in your staged diff.
Why make it global?
With core.hooksPath, you only configure this once. New repositories automatically get the same protection.
It’s especially useful if you sometimes contribute under different identities or anonymously. Instead of relying on remembering to inspect every diff and commit message, Git catches known identifiers before they leave your machine.
It isn’t a hard security boundary, hooks can still be bypassed with git commit --no-verify, but it’s a simple and useful guard against mistakes.
Write a comment