It Only Took One Git Push to Access Millions of GitHub Repos
How an X-Stat field injection in GitHub’s internal git protocol let an authenticated user reach RCE on GitHub.com and GHES using nothing but a standard git push.
I use GitHub almost every day.
For personal projects, client work, experiments, and pretty much anything that involves code, GitHub is always part of my workflow. I push code, create branches, connect repos to deployment tools, and store a lot of my projects there without thinking too much about the system behind it.
So when I saw a post on X about a vulnerability that could be triggered with a single git push, it bothered me.
Not because GitHub is perfect and should never have bugs. It’s how normal the attack path looked. git push is one of the most basic things developers do every day.
The vulnerability is tracked as CVE-2026–3854 with severity level of “High.”
Updated: 2026-04-17
Published: 2026-03-10
Updated: 2026-04-17
Title: Remote code execution via git push option injection in GitHub Enterprise Server
Description
An improper neutralization of special elements vulnerability was identified in GitHub Enterprise Server that allowed an attacker with push access to a repository to achieve remote code execution on the instance.
During a git push operation, user-supplied push option values were not properly sanitized before being included in internal service headers.
Because the internal header format used a delimiter character that could also appear in user input, an attacker could inject additional metadata fields through crafted push option values.
This vulnerability was reported via the GitHub Bug Bounty program and has been fixed in GitHub Enterprise Server versions 3.14.25, 3.15.20, 3.16.16, 3.17.13, 3.18.7 and 3.19.4.Wiz Research found that a Git push options could lead to remote code execution on GitHub Enterprise Server. In simple terms, any authenticated user with push access to a repository on a vulnerable GHES instance could potentially run commands on the server.
Here’s the timeline:
2026–03–04 — Wiz Research discovers the X-Stat push option injection vulnerability.
2026–03–04 — RCE confirmed on GHES 3.19.1.
2026–03–04 — Wiz Research reports the vulnerability to GitHub.
2026–03–04 — GitHub acknowledges receipt.
2026–03–04 — GitHub deploys fix on GitHub.com.
2026–03–10 — CVE-2026–3854 assigned with CVSS 8.7.
2026–03–10 — GHES patch released.
2026–04–28 — Public disclosure.
That is serious. But there is also a caveat worth making clear.
This does not mean GitHub.com users need to panic. They immediately posted a Blog about the issue with details on the validation, fix, and investigation.
GitHub patched the hosted service quickly, said there was no evidence of exploitation in the wild, and normal GitHub.com users do not need to take action.
In less than two hours we had validated the finding, deployed a fix to github.com, and begun a forensic investigation that concluded there was no exploitation.
The urgent part is for GitHub Enterprise Server admins. If you run GHES, patch it.
Still, the story is worth understanding because the bug itself is a good reminder of how small input handling mistakes can turn into infrastructure level problems.
Why Wiz started with GitHub Enterprise Server
Wiz started with GitHub Enterprise Server because it can be run locally. That gives researchers a safer way to inspect GitHub’s git infrastructure without touching real users or live production systems.
GHES also shares a lot of behavior with GitHub.com, so it is a good place to study how GitHub processes git operations. The difficult part is that much of the interesting logic lives inside compiled binaries. This is not like reading through a clean open source repo where everything is already laid out.
The researchers used AI assisted reverse engineering with IDA MCP to help reconstruct the internal protocols behind a git push. I do not think this means AI magically found the bug. That would be a lazy way to explain it. The researchers still needed the skill to know where to look, what looked suspicious, and how to turn the finding into a working exploit.
But AI clearly helped compress the research process. A bug that may have sat around for years became easier to find because the tooling made it faster to inspect a huge closed source system. That is both impressive and uncomfortable.
What happens behind a git push
From the developer side, git push is simple. You push commits, GitHub checks your permission, and the branch updates.
Behind the scenes, the request moves through several internal services. One service receives the git operation. Another checks authentication and repository permissions. Another passes security metadata to downstream processes. Then pre receive hooks run before the push is accepted.
The important part here is an internal header called X-Stat. It carries security related fields between GitHub’s internal services. The format uses semicolon separated key value pairs.
That sounds fine until user controlled input gets placed into that format.
Git has a feature called push options, which you can pass using git push -o. These are arbitrary strings sent to the server during a push. GitHub embedded those push options into the X-Stat header.
The problem was that semicolons inside push option values were not properly sanitized.
So a user supplied value could break out of its assigned field and create new internal fields.
The bug was a semicolon injection
The core bug was field injection.
GitHub expected something like this inside the internal header:
push_option_0=safe_value;But if a user sent a value containing a semicolon, it could become something like this:
push_option_0=x;some_security_field=false;Now the parser sees some_security_field=false as a separate field. That is where things went wrong.
It gets worse because the parser used last write wins behavior. If a field appeared twice, the later value could override the earlier one. So if GitHub set a trusted security value first, and the attacker injected the same field later, the attacker controlled value could win.
This is what makes the bug frustrating. The system was complex, but the root issue was old and familiar. A delimiter was not handled properly, and user input got interpreted as internal structure.
We have seen this pattern many times before. SQL injection, header injection, command injection, config injection. The format changes, but the mistake is usually the same. Data gets treated like instructions.
How it turned into RCE
Field injection alone is not good, but remote code execution requires the injected fields to control something dangerous.
Wiz found that some injectable fields affected GitHub Enterprise Server’s pre receive hook behavior. GHES supports custom pre receive hooks so admins can run scripts before accepting a push. Companies use this for internal checks and enforcement rules.
The researchers found that the hook logic had different execution paths. One path ran hooks inside a sandbox. Another path could run hooks without that sandbox. The choice depended on internal metadata passed through X-Stat.
That became the exploit path.
They could inject a field to change the environment behavior and reach the unsandboxed hook path. Then they could inject a custom hooks directory. Then they could inject a hook definition that used path traversal to point to a binary on the server.
git push -o ‘<injected fields>’ origin master
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 250 bytes | 250.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote: uid=500(git) gid=500(git) groups=500(git) ← RCE as the git service user
To github.com:user/repo.git
abc1234..def5678 master -> masterFrom there, a normal looking git push could execute a command as the git service user.
On GHES, that is a huge problem. The git user is not some harmless account with no access. It is part of the system that serves repository operations. If an attacker can run commands as that user, the whole self hosted GitHub instance could be at risk, including repositories and internal secrets.
That is why this should be treated as urgent for GHES admins.
The GitHub.com part needs careful wording
The viral part of this story is the claim that the researchers got access to millions of repositories belonging to other users and organizations.
That sounds terrifying, and it is easy to run too far with it.
The more careful version is this: the vulnerability also touched GitHub.com infrastructure, GitHub patched it quickly, and there was no evidence of exploitation in the wild. Normal GitHub.com users do not need to do anything.
The cross tenant angle came from the fact that GitHub.com runs on shared backend infrastructure. If code execution lands on a shared storage node as the git user, the possible blast radius becomes scary. Wiz validated the exposure using their own test accounts and repositories, not by reading other people’s private code.
So yes, the architecture made the potential impact huge. But no, this does not mean millions of private GitHub.com repos were stolen or read by attackers.
The accurate takeaway is that CVE-2026–3854 was a critical GHES vulnerability, and the research also showed how dangerous a shared backend node can become if a trusted service user is compromised.
GitHub fixed it fast
GitHub deserves credit for the response.
Wiz reported the issue through the bug bounty program. GitHub validated it, patched GitHub.com within hours, investigated for signs of abuse, and released fixes for supported GHES versions.
That is about as good as vendor response gets. A severe bug is bad, but a severe bug with slow response is much worse. In this case, GitHub moved fast.
The hard part is the self hosted side. GHES customers still need to patch their own instances. That is where things usually slow down because enterprise environments have maintenance windows, approval chains, integrations, and the usual fear that an upgrade will break something.
But this is not the kind of patch you delay. If you run GHES, check your version and upgrade to a fixed release. Do not assume you are safe because the bug is public now or because GitHub.com already patched its side.
What this says about internal systems
The lesson here is not just “GitHub had a bug.”
The better lesson is that internal protocols can become dangerous when every service trusts the previous service too much.
One service copied push options into an internal header. Another service trusted that header. Another part of the system used fields from that header to decide execution behavior. Then a hook system trusted enough of that metadata to reach code execution.
That is how these bugs survive. No single piece has to look ridiculous by itself. The danger appears in the handoff between components.
This happens in large systems all the time. Internal formats start small. More fields get added. More services depend on them. Debug flags stay around. Enterprise features share code with production features. Years later, one field becomes more powerful than anyone remembers.
And then one semicolon becomes a security problem.
AI made the discovery faster
Wiz used AI assisted reverse engineering to move faster through compiled binaries and internal protocols. That does not replace skilled researchers, but it makes the boring and repetitive parts faster.
That should change how companies think about old code.
Some systems feel safe because they are hard to understand from the outside. That comfort is fading. Closed source binaries are still inspectable. Internal protocols can still be reconstructed. AI just makes the process faster.
This is good when responsible researchers report bugs. It is less comforting when attackers can use similar tools.
Security teams need faster review cycles and faster patch habits. Old assumptions are going to age badly. Learn or suffer. Thrive or stagnate.
What developers should learn
If you build backend systems, do not place raw user input into delimiter based formats. If your internal format uses semicolons, commas, pipes, newlines, or anything similar, encode the value or reject dangerous characters.
Do not let duplicate fields silently override security values. Last write wins behavior may be convenient, but it can become dangerous when attackers can influence field order.
Do not let sandboxing, hook execution, debug output, or production behavior depend on request metadata unless that metadata has a trusted source.
And most of all, trace user input across services. Not just inside one API endpoint. Follow it through headers, queues, RPC calls, config blobs, environment variables, and internal hooks. The bug is often hiding in the handoff.
This came through GitHub’s bug bounty program
To be clear, this was not discovered after a public breach or some messy incident where users found out too late.
Wiz researchers submitted it through GitHub’s Bug Bounty program on March 4, 2026, and GitHub’s security team started validating it right away.
This is where bug bounty programs prove their value. A vulnerability like this could have been found by someone with much worse intentions. Instead, it went through a responsible disclosure path, reached the right people, and was fixed quickly.
I also like that GitHub did not just patch the obvious bug and move on. They checked whether anyone else had used the same exploit path before the report.
Their conclusion was that every occurrence matched Wiz’s own testing activity. No other users triggered it, and GitHub said no customer data was accessed, modified, or exfiltrated because of the vulnerability.
There is also a good defense in depth lesson here. GitHub found that the exploit worked partly because a code path existed in an environment where it should not have been available. It was present in the container image due to a deployment change where an older exclusion rule was not carried over.
GitHub released patches for supported versions, including 3.14.25, 3.15.20, 3.16.16, 3.17.13, 3.18.7, 3.19.4, and 3.20.0 or later. GitHub also recommends checking /var/log/github-audit.log for push operations containing ; in push options. If you run GitHub Enterprise Server, this is not something to leave for a later maintenance window. Upgrade to the latest patch release as soon as you can.
Final thoughts
This vulnerability is serious, but it should be described clearly.
It was a real RCE in GitHub Enterprise Server. A user with push access to a repository on a vulnerable GHES instance could use custom git push -o options to execute commands on the server. GitHub.com was patched quickly, and normal GitHub.com users do not need to take action.
I find it hard to imagine how a delimiter injection like this survived inside GitHub’s infrastructure for so long. But large systems are messy. Internal protocols become trusted over time. Eventually, a small parsing issue becomes a big security boundary.
Credit to Wiz for finding it and reporting it responsibly. Credit to GitHub for fixing it within hours.
Hi there! Thanks for making it to the end of this post! If you enjoyed this content and would like to support my work, consider becoming a paid subscriber. Your support means a lot!






