Application Security

CVE-2026-3854: One git push Owns GitHub Enterprise Server. 88% Still Unpatched.

If your organization runs GitHub Enterprise Server on premises, there is an 88 percent chance you are reading this from a vulnerable instance. That number comes from Wiz Research, the team that disclosed CVE-2026-3854 on April 28 after a quiet 55-day coordinated disclosure window with GitHub. The flaw is exactly as bad as it sounds: a single git push command, executed by any user with push access to any repository on the appliance, achieves arbitrary command execution on the server hosting that push.

Push access to a repository you created yourself is enough. Authentication is required, but the bar is so low that any developer onboarded to your GHES instance, any contractor with project access, and any compromised personal access token from a developer's laptop becomes an unauthenticated path to full server compromise. CVSS 8.7. GitHub.com and the Cloud variants were patched the same day Wiz reported the bug. Self-hosted GHES is on you.

What actually happened

Wiz reported the vulnerability to GitHub on March 4, 2026. GitHub validated it within 40 minutes, identified the root cause by 5:45 PM UTC the same day, and deployed a fix to GitHub.com by 7:00 PM UTC. Patches for GHES followed on March 10. Wiz waited until April 28 to publish the technical write-up so that customers had time to upgrade. They did not.

The root cause is what GitHub's official post calls "improper neutralization of special elements," and what every web application pentester recognizes as delimiter injection. When a client runs git push -o key=value, the GHES backend takes that user-supplied option string and embeds it inside an internal service header. That header uses semicolons to separate fields. Semicolons are valid characters in push option values. The parser used last-write-wins semantics, meaning a duplicate key silently overrode the earlier one. Combine those three facts and you have what Wiz demonstrated: a crafted push option that injects new fields into an internal trust boundary, overriding the environment in which the push was processed, bypassing sandbox controls on pre-receive hooks, and executing attacker-supplied commands on the server.

This is not a memory corruption bug. There is no exploit chain. There is no novel research technique. It is the same class of injection vulnerability that has been turning up in web applications since 1998, applied to GitHub's internal RPC layer. Wiz used AI to help discover it, which is the most interesting trivia point in the whole disclosure but does not change the underlying defect.

Why GHES is still 88 percent vulnerable two months later

Self-hosted enterprise software is bad at patching. That is not a controversial claim. What makes CVE-2026-3854 different from the usual pattern is that the patches were available March 10, the public disclosure was April 28, and as of this article 88 percent of GHES instances are running a vulnerable version. The window between fix and disclosure was almost two months. That is more lead time than most enterprise software vendors give for a critical RCE.

The reason GHES patching lags is structural. Most organizations running self-hosted GitHub Enterprise are doing so because of compliance, data residency, or air-gap requirements. The same constraints that drive the on-prem decision also slow the patch cadence: change windows, regression testing, integration validation against custom hooks and integrations, and in some cases physical access requirements. None of those reasons make CVE-2026-3854 less exploitable. They just explain why the exposure window has been weeks instead of hours.

The patch versions and the version number trap

Read the version table carefully. There are two layers of patch releases for this CVE, and the wrong one is easy to land on if you read only the first source you find:

# Initial patches (March 10, 2026)
GHES 3.14.24
GHES 3.15.19
GHES 3.16.15
GHES 3.17.12
GHES 3.18.6
GHES 3.19.3

# Subsequent point releases also contain the fix
GHES 3.14.25
GHES 3.15.20
GHES 3.16.16
GHES 3.17.13
GHES 3.18.8
GHES 3.19.4
GHES 3.20.0

If you are on 3.18.6, you have the fix. If you are on 3.18.5, you do not. Verify the running version directly on the appliance after upgrading. Inventory snapshots are routinely out of date by a release or two and the difference between 3.18.6 and 3.18.5 is the difference between "patched" and "exploitable by any developer with a laptop."

The five-question audit your platform team should run today

1. What is our actual GHES version?

Do not trust the asset inventory. Pull the version live:

# From any host with admin SSH on the GHES appliance
ssh -p 122 admin@ghes.example.internal "ghe-version"

# Or via the API from a host with admin token
curl -s -H "Authorization: token $ADMIN_TOKEN" \
  https://ghes.example.internal/api/v3/meta | jq '.installed_version'

2. Who has push access to any repository on the appliance?

Push access is the only requirement for exploitation. Any developer, contractor, or service account with push permissions to any repository — including repositories they created themselves — can trigger the attack. Run the audit:

# List all users on the appliance with their last-active dates
ghe-user-list --inactive false

# Pull the full collaborator list across all repos via REST API
gh api --paginate "/orgs/YOUR_ORG/repos" --jq '.[].full_name' | \
  while read repo; do
    gh api --paginate "/repos/$repo/collaborators" \
      --jq ".[] | select(.permissions.push==true) | .login" | \
      sort -u
  done | sort -u

The output is your blast radius. Every name on that list is a potential attacker if their credentials are compromised.

3. Are personal access tokens scoped or unscoped?

An attacker does not need to compromise an interactive login. A leaked classic personal access token with repo scope, pulled from a developer's laptop or a misconfigured CI runner, is enough to push to any repo the user has access to. Audit your token policy:

# Check whether your appliance enforces fine-grained PAT requirements
gh api "/admin/pre_receive_environments" -i

# For organizations, list all PATs that have repo access
gh api --paginate "/orgs/YOUR_ORG/personal-access-tokens" \
  --jq '.[] | {owner: .owner.login, name: .access_token_name, last_used: .last_used_at}'

Force fine-grained token migration if you have not already. Classic tokens that have not been used in 90 days should be revoked outright.

4. Are pre-receive hooks audit-logged?

Wiz's exploit chain ended at hook execution. If an attacker reaches code execution, they almost certainly do it through a pre-receive hook path. Pre-receive hooks should be logged centrally, with full process tree and command line, regardless of whether you think you have any custom hooks configured. Many GHES appliances ship with default behaviors that admins do not realize are running:

# Enable pre-receive hook environment logging
ghe-config app.gitauth.audit-pre-receive-hooks true
ghe-config-apply

# Audit existing pre-receive hooks
ghe-config-list | grep -i pre-receive
ls -la /data/user/git-hooks/pre-receive.d/

5. What is the network blast radius if a GHES backend is compromised?

The GHES appliance has network access to every internal system it integrates with: your CI runners, your artifact registries, your secret managers, your LDAP or Entra ID for authentication, and the production environments your CI pipelines deploy to. If an attacker reaches command execution on the GHES backend, they inherit that network position. Map it now, before you need to during an incident:

  • What integrations exist? Webhooks, Apps, OAuth integrations, SAML connections.
  • What service accounts does the appliance use to talk to other systems? Where are those credentials stored on the appliance?
  • What network egress is allowed from the appliance subnet?
  • What CI/CD secrets reside in repositories on the appliance? Cloud credentials, signing keys, deployment tokens.

What this means for SMBs that do not run GHES

Most small and mid-sized businesses do not run GitHub Enterprise Server. The relevant version of this story for them is the lesson, not the patch. CVE-2026-3854 is a textbook example of internal trust boundary failure — the moment user-supplied input crosses from "untrusted external data" into "trusted internal metadata," every downstream service that reads that metadata is one parser bug away from arbitrary execution. The same pattern shows up in customer support automation that takes ticket fields and passes them to internal RPC, in CI/CD pipelines that embed PR titles in deployment metadata, and in any system that uses a delimiter-based format to pass structured data between services.

Three concrete questions to ask about your own environment:

  1. Where does user input cross an internal trust boundary? Map the choke points where external data becomes internal metadata. Document the parsers on both sides.
  2. What format do those internal headers use, and what happens when user input contains the delimiter? If the answer is "we have not tested that," you have a CVE-2026-3854 waiting to happen.
  3. Do downstream services treat upstream metadata as authenticated truth? If yes, every parser bug becomes a privilege escalation. The right pattern is per-field sanitization at the boundary plus cryptographic signing of internal headers, not "trust the upstream service."

The 14-day plan for GHES operators

This week: Verify the running GHES version live. Schedule the upgrade window if you are not on a patched release. Audit the collaborator list across every repo on the appliance. Revoke unused classic personal access tokens.

Next week: Apply the patch. Verify the version after upgrade. Enable pre-receive hook environment logging. Run a tabletop exercise around the question "what happens if our GHES backend is owned for 24 hours?" — the network blast radius answer drives every other decision in the response plan.

Going forward: Track the GHES patch cadence as a first-class risk. The window between fix availability and public disclosure on this CVE was 55 days. The window between disclosure and active exploitation will be much shorter on the next one. Build the muscle now, while the cost of patching is one change window and not one breach notification letter.

The bigger pattern

This is the second cross-cutting application security disclosure in two weeks. Last week it was Comment and Control hitting Anthropic, Google, and GitHub at the runtime layer. This week it is GitHub itself at the protocol layer. The common thread is that critical infrastructure runs on internal trust assumptions that fail when one component sees attacker-controlled input where it expected authenticated metadata. AI-discovered vulnerabilities like CVE-2026-3854 are going to find more of these assumptions, not fewer, and the pace will continue to favor attackers who can iterate cheaply over defenders who must patch through change control.

The GHES patch is the easy part. The harder part is auditing your own architecture for the same class of trust failure before someone else finds it for you.

Self-hosted GHES, GitLab, or internal CI? Get an outside read on the blast radius.

Red Hound runs application infrastructure risk reviews for SMBs and mid-market teams: GHES patch verification, collaborator and PAT audits, pre-receive hook logging, and a written remediation roadmap your platform team can execute in 14 days. Book a 30-minute consultation to walk through your repository hosting configuration and start the patch and audit plan.