Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,11 @@ split applies to config that isn't a sidecar: private work-machine bspwm
profiles live in the overlay, while intentionally public hardware-specific
profiles may live in
`desktop-environment/bspwm/profiles/`.

`git/hooks/` guards this repo's own history: `./install` points this clone's
`core.hooksPath` at it (repo-local — no other repo on the machine is
affected), and the hooks then refuse any commit or push whose committer is
not one of the two identities this repo is developed under. The allowlist in
`identity-guard.sh` is deliberately hard-coded and closed; never add a work
or otherwise private address to it — this repo is public, so anything
written there is published.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ each gets its own top-level directory:

- `bash/` — `bashrc`, `bash_aliases`, `profile`, `inputrc`,
`bazel_completions.bash`
- `git/` — `gitconfig`, `gitmessage`, `git-prompt.sh`, `lazygit.yml`
- `git/` — `gitconfig`, `gitmessage`, `git-prompt.sh`, `lazygit.yml`,
`hooks/` (committer-identity guard for this repo, wired up by `./install`
via a repo-local `core.hooksPath`)
- `tmux/` — `tmux.conf`, `settings.conf`, plugins via submodules (tpm,
nord-tmux, tmux-sensible)
- `kitty/` — config, themes, `launch.sh`, `zenmode.py`
Expand Down
61 changes: 61 additions & 0 deletions git/hooks/identity-guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/bin/sh
# Shared logic for the identity-guarding hooks in this directory. Sourced,
# never run directly (git only executes files named after a hook).
#
# These hooks apply to this repo only: ./install points this clone's
# core.hooksPath at git/hooks, and no other repo is touched. The guard exists
# to stop a work identity from ever committing here. It is a guard against
# habit, not against an attacker: --no-verify, `git -c core.hooksPath=`, and
# editing this file all bypass it. Enforcement that cannot be bypassed has to
# live on the remote.

# The only committer addresses allowed to create commits in this repo:
# the personal identity and the one Claude Code web sessions commit under.
guard_allowed_emails() {
printf '%s\n' 'krishna0bala@proton.me' 'noreply@anthropic.com'
}

# Lowercase, since the domain half of an address is case-insensitive and the
# local half is in practice.
guard_normalize() {
printf '%s' "$1" | tr '[:upper:]' '[:lower:]'
}

# The address inside a "Name <addr> 1730000000 +0000" ident string.
guard_ident_email() {
_ident=${1#*<}
guard_normalize "${_ident%%>*}"
}

guard_email_allowed() {
_candidate=$(guard_normalize "$1")
for _allowed in $(guard_allowed_emails); do
if [ "$_candidate" = "$_allowed" ]; then
return 0
fi
done
return 1
}

# Reject the commit about to be created if it would carry an unlisted
# committer. The committer, not the author, is the identity being checked:
# the committer is whoever is running git right now, which is the thing that
# leaks, while the author is provenance that gets preserved when you amend or
# rebase someone else's work.
guard_check_committer() {
_email=$(guard_ident_email "$(git var GIT_COMMITTER_IDENT)")
if guard_email_allowed "$_email"; then
return 0
fi

cat >&2 <<EOF
$(basename "$0"): refusing to commit as <$_email>.

Committers allowed in this repo:
$(guard_allowed_emails | sed 's/^/ /')

Set the right identity for this repo:
git config user.email <address>
EOF
return 1
}
7 changes: 7 additions & 0 deletions git/hooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
# Runs on `git commit` and `git commit --amend`.
set -eu

. "$(dirname "$0")/identity-guard.sh"

guard_check_committer
7 changes: 7 additions & 0 deletions git/hooks/pre-merge-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/sh
# Runs on `git merge` when it creates a merge commit; pre-commit does not.
set -eu

. "$(dirname "$0")/identity-guard.sh"

guard_check_committer
55 changes: 55 additions & 0 deletions git/hooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/sh
# Backstop for commits that never passed pre-commit: rebase, cherry-pick,
# `git commit --no-verify`, and anything committed before this hook existed.
set -eu

. "$(dirname "$0")/identity-guard.sh"

zero=$(git hash-object --stdin </dev/null | tr '0-9a-f' '0')

# stdin is one "<local ref> <local sha> <remote ref> <remote sha>" line per ref
# being pushed. Collect the local tips; a zero local sha is a branch deletion,
# which pushes no commits.
tips=''
while read -r _local_ref local_sha _remote_ref _remote_sha; do
if [ -n "$local_sha" ] && [ "$local_sha" != "$zero" ]; then
tips="$tips $local_sha"
fi
done

if [ -z "$tips" ]; then
exit 0
fi

# Only commits not already reachable from a remote-tracking ref: the ones this
# push would actually add. Checking the committer rather than the author keeps
# this quiet on replayed outside work — cherry-pick and rebase re-stamp the
# committer as you while preserving the original author.
offenders=''
count=0
for line in $(git log --format='%h=%ce' $tips --not --remotes); do
if ! guard_email_allowed "${line#*=}"; then
count=$((count + 1))
if [ "$count" -le 10 ]; then
offenders="$offenders ${line%%=*} committed by <${line#*=}>
"
fi
fi
done

if [ "$count" -eq 0 ]; then
exit 0
fi

cat >&2 <<EOF
pre-push: $count commit(s) in this push carry an unlisted committer.

$offenders$([ "$count" -gt 10 ] && printf ' ... and %s more\n' "$((count - 10))")
Committers allowed in this repo:
$(guard_allowed_emails | sed 's/^/ /')

Rewrite them with the right identity, e.g. for the last commit:
git config user.email <address>
git commit --amend --reset-author --no-edit
EOF
exit 1
4 changes: 4 additions & 0 deletions install.conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@

- shell:
- [git submodule update --init --recursive, Installing/updating submodules]
# Repo-local only: points this clone's hooks at the tracked git/hooks
# (relative paths resolve against the repo root). Other repos on the
# machine are untouched.
- [git config core.hooksPath git/hooks, Enabling identity-guard hooks for this repo]
Loading