CVE-2026-3854: When a `git push` Is Enough to Compromise GitHub

CVE-2026-3854: When a git push Is Enough to Compromise GitHub

Classification: Critical Vulnerability — CVSS 8.7
Public Disclosure Date: April 28, 2026
Researchers: Wiz Research Team

The discovery of CVE-2026-3854 has marked a milestone in AI-assisted vulnerability research: a bug in the sanitization of internal headers allows any user with normal access to escalate privileges to full server control. This analysis digs into how a delimiter injection in Git’s transport protocol can bypass security sandboxes and silently compromise millions of corporate repositories.


The Problem in One Line

Any user with normal access to a GitHub repository could, with a single git push, execute arbitrary commands on the servers powering the platform. No sophisticated exploits. No special privileges. Using the same git client you use every day.

That is CVE-2026-3854.


Context: Why Is This Different?

Critical vulnerabilities in development platforms are not rare, but most require specific conditions that are difficult to reproduce: high-privilege authentication, internal network access, uncommon configurations. This vulnerability breaks that norm.

The flaw resides in the internal infrastructure that processes every git push operation on GitHub — both the cloud version (GitHub.com) and self-managed installations (GitHub Enterprise Server). The root of the problem is not in a third-party library or a peripheral component: it sits at the heart of the pipeline that moves code from developers to GitHub’s servers, billions of times a day.

What makes this case especially interesting — beyond its impact — is that it was discovered through AI-assisted reverse engineering of compiled closed-source binaries, something that until recently was practically unachievable at scale.


How GitHub’s Internal Pipeline Works

To understand the flaw, you first need to understand the path a git push travels before code reaches the server.

When a developer runs git push over SSH, the request does not go directly to storage. It passes through a chain of four internal services:

[git client] → babeld → gitauth → gitrpcd → pre-receive hook

babeld acts as the gatekeeper: it receives the SSH connection, queries gitauth to verify whether the user has push permission, and with the response from that service, it constructs an internal header called X-Stat. That header is essentially the operation’s passport: it contains who the user is, which repository they want to modify, and which security policies apply to that session.

gitrpcd receives that header and configures the execution environment for the subsequent processes. The critical point: gitrpcd does not verify anything on its own. It blindly trusts whatever the X-Stat header says.

The pre-receive hook is the final link: a Go-compiled binary that decides whether the push is accepted or rejected, enforcing rules such as file size limits, LFS validation, or custom hooks defined by the administrator.


The Flaw: An Unguarded Delimiter

The X-Stat header has a simple format: key=value pairs separated by semicolons. For example:

X-Stat: user_id=123;repo_id=456;large_blob_rejection_enabled=bool:true;push_option_0=my_option

Push options (git push -o "something") are a standard git feature that allows passing additional metadata to the server. babeld takes them directly from user input and embeds them in the X-Stat header as-is, without any sanitization.

The problem: if a push option’s value contains a semicolon, that character splits the field into two, creating a new entry in the header. And since the parser processing X-Stat applies a last-write-wins rule — if a key appears twice, the second value overwrites the first — an attacker can inject fields that override legitimate security settings.

A concrete example: if the server has file size restrictions enabled (large_blob_rejection_enabled=bool:true), a push with the option -o "x;large_blob_rejection_enabled=bool:false" would inject a second value for that key, disabling the restriction for that session.

Up to this point, the impact is significant but limited. The jump to code execution is what turns this into a critical vulnerability.


From Field Injection to Code Execution

The pre-receive hook has two operating modes, controlled by the rails_env field in the X-Stat header:

That distinction — a simple string value determining whether isolation exists — is the core of the RCE escalation.

The exploit chain combines three successive injections into the X-Stat header:

Step 1 — Exit the sandbox
Inject a non-production value for rails_env. The hook abandons the safe path and switches to the direct execution path.

Step 2 — Redirect the hooks directory
Inject custom_hooks_dir with an attacker-controlled path. The binary will look for hook scripts in that location instead of the default one.

Step 3 — Point to an arbitrary binary
Inject repo_pre_receive_hooks with a hook definition whose script field includes a path traversal sequence (../../...). The binary builds the final path by joining the injected directory with the traversal, landing on an arbitrary executable in the filesystem.

The result: the system executes that binary as the git user, without restrictions, before processing the push. From that point on, the attacker has full control of the server.

# Actual exploit output against a GHES instance
remote: uid=500(git) gid=500(git) groups=500(git)

GitHub.com Too? Yes, With One Extra Step

When researchers applied the same chain against a repository on GitHub.com, code execution did not occur immediately. The reason: GitHub.com has the custom hooks path disabled by default via another flag in the X-Stat header.

The revealing detail: that flag also travels in the same header and is therefore injectable with the exact same mechanism. One additional injection — enabling enterprise mode — was enough for the full chain to work on GitHub.com’s shared infrastructure.

The difference in impact between GHES and GitHub.com is not technical, but architectural. In GHES, the attacker compromises one instance. In GitHub.com, they compromise a shared storage node that serves repositories for multiple organizations and users. The git user running the code has access, by design, to all repositories stored on that node. Wiz researchers confirmed — without accessing any third-party content — that millions of third-party repositories were reachable from the compromised nodes.


The Role of AI in the Discovery

This case has a methodological dimension worth its own attention. GitHub is closed source: its internal binaries are not available for inspection. Auditing a chain of compiled components in search of parsing inconsistencies between services would have required weeks of manual work with conventional reverse engineering tools.

The Wiz team used IDA MCP, an integration that connects the IDA Pro disassembler with AI-assisted analysis capabilities, to reconstruct the internal logic of GitHub’s binaries at a speed that would otherwise not have been feasible. This allowed them to trace user input flow across the entire service chain, identify execution paths conditioned by rails_env, and map injectable fields before validating them with real traffic capture on a test instance.

The finding has implications that go beyond this specific vulnerability: the barrier to auditing closed-source software is lowering. That is good for defensive researchers. And potentially concerning if the same resources are available to malicious actors.


Real Exposure: 88% of Instances Unpatched

GitHub fixed the issue on GitHub.com within hours of receiving the report. For GitHub Enterprise Server, it published patches on March 10, 2026. However, at the time of public disclosure — nearly two months later — Wiz’s data indicated that 88% of GHES instances were still running vulnerable versions.

This figure is not surprising to those who manage enterprise environments, but it is a reminder of how long organizations remain exposed even when a patch is available. The usual reasons: rigid maintenance windows, internal validation processes required before updates, or simply a lack of visibility into which version is running in production.


What to Do Now

For GitHub Enterprise Server Instances

The priority is to update. Versions that include the patch are:

Branch Fixed Version
3.14.x 3.14.24 or later
3.15.x 3.15.19 or later
3.16.x 3.16.15 or later
3.17.x 3.17.12 or later
3.18.x 3.18.6 or later
3.19.x 3.19.3 or later

Any instance on version 3.19.1 or earlier is vulnerable.

For GitHub.com

No action required. The patch was applied directly to GitHub’s infrastructure.


Conclusion

CVE-2026-3854 is a reminder of something the industry knows but frequently underestimates: distributed systems inherit the attack surface of all their components, plus the interfaces between them. One service that blindly trusts the previous one. A parser that does not escape delimiters. A sandboxless execution path that nobody removed. Each piece, reasonable on its own. Devastating together.

The good news is that GitHub acted quickly and there is no evidence of malicious exploitation prior to disclosure. The bad news is that, as we write this, the majority of self-managed servers remain unpatched.


References