7 Proven Steps for CVE-2025-48384 Git Mitigation
TL;DR (for dev & SRE leads)
CVE-2025-48384 exposes CI/CD and developer laptops to submodule-driven arbitrary file write → code execution. Treat this as a pipeline risk first, repo risk second. This battle-tested CVE-2025-48384 Git mitigation playbook gives you 7 steps you can drop into GitHub Actions, GitLab CI, and Jenkins today—no external tooling required.
You’ll do this now:
- Stop
git clone --recursive
from untrusted repos - Enforce
core.hooksPath
to disable repo-provided hooks in CI - Pin to patched Git versions at runtime
- Audit
.gitmodules
defensively - Alert on unexpected submodule entries in PRs
- Model submodules in your SBOM/checklist
- Isolate build agents and gate reviews for submodule changes
Quick internal reads for your team:
- Cyber Rely Blog
- Risk Assessment Services
- Remediation Services
- Free tool for quick web surface checks: Website Vulnerability Scanner online free
The threat in plain English
CVE-2025-48384 abuses how Git parses configuration for submodules. A crafted .gitmodules
can redirect checkout paths and plant or trigger hooks, culminating in arbitrary file write and RCE on Linux/macOS during clone or common subcommands. Because CI runners often perform recursive clones and run hooks, a poisoned submodule can sneak execution into your build—not just your repo.
Why CI/CD is the blast radius:
- CI runners are powerful (tokens, signing keys, artifact access)
- Runners often auto clone submodules
- Hooks can fire during routine steps (e.g.,
commit
,merge
,checkout
) - Build images may have unpatched Git
Free Website Vulnerability Scanner Webpage Screenshot
7 Proven Steps (drop-in ready)
1) Disable recursive submodule clones from untrusted sources
Recursive clones are the common exploit path. Block them in CI and require a manual audit of .gitmodules
first.
GitHub Actions guard (blocks --recursive
in workflows):
name: guard-git-recursive
on: [workflow_call, pull_request]
jobs:
block-recursive:
runs-on: ubuntu-latest
steps:
- name: Detect forbidden recursive clone
run: |
set -euo pipefail
if grep -R -- '--recursive' -n .github/workflows || \
( [ -n "${GIT_TRACE:-}" ] && echo "$GIT_TRACE" | grep -- '--recursive' ); then
echo "Do not use 'git clone --recursive' from untrusted sources. Audit .gitmodules first."
exit 1
fi
GitLab CI:
guard-git-recursive:
stage: test
script:
- set -euo pipefail
- if grep -R -- '--recursive' -n .gitlab-ci.yml; then
echo "Forbidden: recursive clones from untrusted sources";
exit 1;
fi
Jenkins (Declarative):
stage('Guard Recursive') {
steps {
sh '''
set -euo pipefail
if grep -R -- "--recursive" -n Jenkinsfile; then
echo "Forbidden: recursive clones from untrusted sources"
exit 1
fi
'''
}
}
2) Enforce core.hooksPath
to neutralize repo-provided hooks in CI
Point hooks to an empty dir so no repository hooks can run during builds.
mkdir -p "$HOME/.git-hooks-empty"
git config --global core.hooksPath "$HOME/.git-hooks-empty"
git config --global --get core.hooksPath # verify
Add as an early CI step (before any checkout-driven build steps). Keep normal hooks only on dev laptops—not in CI.
3) Pin patched Git versions (fail closed)
Verify the runner’s Git at runtime; fail if it isn’t one of the patched lines or a newer fixed version.
GitHub Actions:
- name: Enforce patched Git (CVE-2025-48384)
run: |
set -euo pipefail
reqs="2.43.7 2.44.4 2.45.4 2.46.4 2.47.3 2.48.2 2.49.1 2.50.1"
v=$(git --version | awk '{print $3}')
ok=0
for r in $reqs; do [ "$v" = "$r" ] && ok=1; done
# allow anything > 2.50.1
[ "$(printf '%s\n2.50.1\n' "$v" | sort -V | tail -1)" = "$v" ] && ok=1
if [ $ok -ne 1 ]; then
echo "Git $v is not patched for CVE-2025-48384 Git mitigation."; exit 1
fi
GitLab CI (before_script
):
before_script:
- |
set -euo pipefail
reqs="2.43.7 2.44.4 2.45.4 2.46.4 2.47.3 2.48.2 2.49.1 2.50.1"
v=$(git --version | awk '{print $3}')
ok=0
for r in $reqs; do [ "$v" = "$r" ] && ok=1; done
[ "$(printf '%s\n2.50.1\n' "$v" | sort -V | tail -1)" = "$v" ] && ok=1
[ $ok -eq 1 ] || { echo "Unpatched Git $v"; exit 1; }
4) Lock Git protocols at runtime
Forbid local/unsafe transports during clone to reduce attack surface:
git -c protocol.file.allow=never -c protocol.git.allow=never clone "$REPO_URL" repo
Allow only https
/ssh
in CI unless you explicitly whitelist otherwise.
5) Audit .gitmodules
before any init
Build a tiny defensive audit and run it before submodule operations.
scripts/audit-gitmodules.sh
:
set -euo pipefail
test -f .gitmodules || exit 0
# Reject control chars, absolute paths, or traversal
if grep -P '\r$' .gitmodules || grep -E 'path\\s*=\\s*/' .gitmodules || grep -E 'path\\s*=\\s*\\.\\.' .gitmodules; then
echo "Suspicious submodule paths detected in .gitmodules"; exit 1
fi
# Restrict to https/ssh only
if grep -E 'url\\s*=\\s*(git://|file://|http://)' .gitmodules; then
echo "Insecure or local protocols found in .gitmodules"; exit 1
fi
echo ".gitmodules audit passed"
Pre-commit (blocks bad entries from landing):
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: audit-gitmodules
name: Audit .gitmodules for unsafe entries
entry: bash scripts/audit-gitmodules.sh
language: system
files: ^\.gitmodules$
6) Detect & alert on unexpected submodule changes
Fail PRs when .gitmodules
is added/changed without approval.
GitHub Actions:
- name: Alert on .gitmodules changes
run: |
set -euo pipefail
if git diff --name-only origin/${{ github.base_ref }}... | grep '^.gitmodules$'; then
echo "::error:: .gitmodules changed. Label 'submodule-review' and require codeowners";
exit 1
fi
CODEOWNERS example:
# Require security/infra approval for submodule changes
/.gitmodules @security-team @devops-leads
7) Track submodules in your SBOM/checklist
Most SBOM tools won’t model submodules as packages. Emit a mini-inventory alongside your SBOM and gate on it.
Emit JSON inventory:
awk '
$1=="[submodule" {name=$2}
$1=="url" {gsub(/url = /,""); print "{\\"name\\":" name ",\\"url\\":\\""$0"\\"}"}
' .gitmodules | tr -d '[]" ' > submodules.json
Gate on unapproved hosts or new entries:
python - <<'PY'
import json, sys, re
allowed = re.compile(r'^(?:github\.com|gitlab\.com|bitbucket\.org)/your-org/')
subs = [json.loads('{'+l.strip().strip(',')+'}') for l in open('submodules.json')]
bad = [s for s in subs if not allowed.search(s['url'])]
if bad:
print("Blocked submodules:", bad); sys.exit(1)
PY
Detection & incident response (when you think a runner got popped)
Fast checks
# 1) What hooks executed recently?
find ~/.git -maxdepth 2 -type f -name 'post-*' -exec ls -l {} +
# 2) Suspicious files dropped during clone/build
sudo find / -mmin -30 -type f -user $(whoami) 2>/dev/null | head -200
# 3) New submodules or path anomalies in last commit range
git diff --name-only --diff-filter=A -U0 HEAD~5..HEAD | grep '^.gitmodules$' || true
Containment
- Rotate CI secrets/tokens
- Invalidate signing keys or attestations tied to the suspect build
- Reimage ephemeral runners; don’t reuse base images without patching Git
- Block submodule changes until security review clears the repo
Forensics
- Preserve build logs/artifacts
- Snapshot runner filesystem or container (read-only) for analysis
- Tie commit/PR to identity; check for prior suspicious forks or submodule churn
Sample report from the free tool to check Website Vulnerability
Recovery & policy hardening
- Review gating: Require CODEOWNERS for
.gitmodules
. - Runner isolation: Separate public fork builds from internal builds; no shared workspaces.
- Artifact trust: Use provenance/attestations; fail on missing SLSA-style attestations.
- Least privilege: Scope CI tokens to repo and environment; short TTL.
- Recurring checks: Add the audit + version checks to nightly jobs.
- Programmatic assurance: Include this CVE-2025-48384 Git mitigation checklist in your SDLC gates.
Need a structured, audit-ready approach? Start with a Risk Assessment and a fix plan through Remediation Services. For quick staging checks during triage, run our Free Website Security Scanner: free.pentesttesting.com.
“Good citizen” engineering snippets (copy/paste)
Safe clone wrapper (CI):
safe_clone() {
url="$1"; dest="$2"
git -c protocol.file.allow=never -c protocol.git.allow=never clone "$url" "$dest"
(cd "$dest" && test -f .gitmodules && bash scripts/audit-gitmodules.sh || true)
(cd "$dest" && git config --local core.hooksPath "$HOME/.git-hooks-empty")
}
Minimal submodule initializer (non-recursive):
git submodule init
git config -f .gitmodules --get-regexp 'submodule\\..+\\.url' | awk '{print $1}' \
| while read key; do
url=$(git config -f .gitmodules --get "$key")
case "$url" in
https://*|ssh://*) : ;;
*) echo "Blocked submodule protocol: $url"; exit 1;;
esac
done
git submodule update --init --depth=1
Where this fits in your program
- SDLC gates: Add the CI guards to your “build” and “pre-merge” gates.
- Assurance evidence: Keep the passing logs of your Git version check, hooksPath enforcement, and
.gitmodules
audit with each release. - Executive reporting: Track % of runners on patched Git; MTTR from detection to rotation.
Related posts on Cyber Rely (good internal linking)
- Git CVE-2025-48384: Safe Submodules in Practice
- Gate CI with CISA KEV JSON: Ship Safer Builds
- npm supply chain attack 2025: ‘Shai-Hulud’ CI fixes
CTA for dev leads: Paste the guards today, then share this checklist with your platform team. For quick web surface checks linked from your repos, use our free scanner.
🔐 Frequently Asked Questions (FAQs)
Find answers to commonly asked questions about CVE-2025-48384 Git Mitigation.