Skip to content

Commit 8356cf4

Browse files
feat: add require-signed-commits hook (#1)
* feat: add require-signed-commits hook Checks commit.gpgsign=true and user.signingkey is set before allowing a commit. Blocks unsigned commits in non-interactive shells (e.g. agentic AI workflows) where signing can silently fall through. Usage in .pre-commit-config.yaml: - repo: https://github.com/injectedfusion/pre-commit-hooks rev: <tag> hooks: - id: require-signed-commits * fix: correct misleading comment and remove unused gpg_format variable * ci: add PR pipeline with shellcheck and Claude reviewer
1 parent 9b63a85 commit 8356cf4

3 files changed

Lines changed: 162 additions & 0 deletions

File tree

.github/workflows/pr-pipeline.yml

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
name: PR Pipeline
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened]
6+
issue_comment:
7+
types: [created]
8+
pull_request_review_comment:
9+
types: [created]
10+
11+
jobs:
12+
shellcheck:
13+
name: ShellCheck
14+
# Only run for the repo owner — block random fork PRs from consuming CI
15+
if: github.event_name == 'pull_request' && github.actor == 'injectedfusion'
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Get changed shell scripts
23+
id: changed
24+
env:
25+
BASE_REF: ${{ github.base_ref }}
26+
run: |
27+
files=$(git diff --name-only --diff-filter=ACMR "origin/$BASE_REF"...HEAD -- '*.sh' | tr '\n' ' ')
28+
echo "files=$files" >> "$GITHUB_OUTPUT"
29+
if [ -n "$files" ]; then
30+
echo "has_files=true" >> "$GITHUB_OUTPUT"
31+
fi
32+
33+
- name: Install shellcheck
34+
if: steps.changed.outputs.has_files == 'true'
35+
run: sudo apt-get install -y shellcheck
36+
37+
- name: Run shellcheck on changed scripts
38+
if: steps.changed.outputs.has_files == 'true'
39+
env:
40+
CHANGED_FILES: ${{ steps.changed.outputs.files }}
41+
run: |
42+
exit_code=0
43+
for f in $CHANGED_FILES; do
44+
[ -f "$f" ] || continue
45+
echo "::group::Checking $f"
46+
shellcheck -S warning "$f" 2>&1
47+
result=$?
48+
echo "::endgroup::"
49+
if [ $result -ne 0 ]; then
50+
echo "::error file=$f::shellcheck found issues"
51+
exit_code=1
52+
fi
53+
done
54+
exit $exit_code
55+
56+
# --- Claude Review (waits for CI on PRs, runs directly on @claude mentions) ---
57+
58+
review:
59+
needs: [shellcheck]
60+
# Only the repo owner can trigger reviews — prevents billing abuse on public repo
61+
if: |
62+
always() && github.actor == 'injectedfusion' &&
63+
(
64+
(github.event_name == 'pull_request' &&
65+
needs.shellcheck.result != 'failure') ||
66+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
67+
(github.event_name == 'pull_request_review_comment')
68+
)
69+
runs-on: ubuntu-latest
70+
permissions:
71+
actions: read
72+
contents: write
73+
pull-requests: write
74+
id-token: write
75+
steps:
76+
- uses: actions/checkout@v4
77+
with:
78+
fetch-depth: 1
79+
80+
- uses: anthropics/claude-code-action@v1
81+
env:
82+
PR_NUMBER: ${{ github.event.pull_request.number }}
83+
REPO: ${{ github.repository }}
84+
with:
85+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
86+
track_progress: true
87+
prompt: |
88+
REPO: ${{ env.REPO }}
89+
PR NUMBER: ${{ env.PR_NUMBER }}
90+
91+
You are the sole code reviewer for this repository. Your review decision
92+
determines whether this PR merges to the main branch.
93+
94+
This repo contains reusable pre-commit hooks (bash scripts + .pre-commit-hooks.yaml).
95+
Review this PR focusing on:
96+
- Shell script correctness and safety (quoting, error handling, set -euo pipefail)
97+
- Security (no credential leaks, no unsafe eval/exec patterns)
98+
- Hook configuration validity (.pre-commit-hooks.yaml fields)
99+
- Documentation accuracy (comments match actual behavior)
100+
101+
After your review:
102+
- Use inline comments for specific code issues.
103+
- Post a single PR comment with your overall summary.
104+
- If the PR is acceptable: approve it with `gh pr review --approve` and
105+
then merge it with `gh pr merge --squash --auto`.
106+
- If the PR has issues that must be fixed: request changes with
107+
`gh pr review --request-changes` and do NOT merge.
108+
109+
claude_args: |
110+
--model claude-haiku-4-5-20251001 --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr review:*),Bash(gh pr merge:*)"

.pre-commit-hooks.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,16 @@
3131
language: script
3232
types: [yaml]
3333
stages: [pre-commit]
34+
35+
- id: require-signed-commits
36+
name: require signed commits
37+
description: >
38+
Fails if commit.gpgsign is not true or user.signingkey is not set.
39+
Enforces GPG/SSH commit signing discipline, especially important in
40+
agentic AI workflows where unsigned commits can slip through
41+
non-interactive shells.
42+
entry: hooks/require-signed-commits.sh
43+
language: script
44+
always_run: true
45+
pass_filenames: false
46+
stages: [pre-commit]

hooks/require-signed-commits.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
# require-signed-commits — fail if the commit being created will be unsigned.
3+
# Prevents unsigned commits from entering the repo, enforcing GPG/SSH signing
4+
# discipline especially important in agentic AI workflows.
5+
#
6+
# Fails unless commit.gpgsign is explicitly set to true and user.signingkey is configured.
7+
8+
set -euo pipefail
9+
10+
# Check if signing is configured
11+
gpgsign="$(git config --get commit.gpgsign 2>/dev/null || echo 'false')"
12+
13+
if [[ "$gpgsign" != "true" ]]; then
14+
echo "✗ Unsigned commit blocked: commit.gpgsign is not set to true"
15+
echo ""
16+
echo "To enable commit signing:"
17+
echo " git config --global commit.gpgsign true"
18+
echo " git config --global user.signingkey <your-key>"
19+
echo ""
20+
echo "If using 1Password SSH agent:"
21+
echo " git config --global gpg.format ssh"
22+
echo " git config --global user.signingkey <your-public-key>"
23+
echo ""
24+
echo "To bypass (not recommended): git commit --no-verify"
25+
exit 1
26+
fi
27+
28+
# Check a signing key is set
29+
signingkey="$(git config --get user.signingkey 2>/dev/null || echo '')"
30+
31+
if [[ -z "$signingkey" ]]; then
32+
echo "✗ Unsigned commit blocked: commit.gpgsign=true but user.signingkey is not set"
33+
echo ""
34+
echo "Set your signing key:"
35+
echo " git config --global user.signingkey <your-key>"
36+
exit 1
37+
fi
38+
39+
exit 0

0 commit comments

Comments
 (0)