A Simple Global Git Hook to Block Sensitive Strings

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.

https://stacker.news/items/1554578

Write a comment