CVE-2026-78676: How a Dormant git-config Value Becomes Code Execution in GitPython

August 24, 2026
August 24, 2026

0 min read

Vulnerabilities
SCA Security
CVE-2026-78676: How a Dormant git-config Value Becomes Code Execution in GitPython

CVE-2026-78676, published on August 24, 2026, is a critical severity (CVSS 4.0 9.3, CVSS 3.1 9.8) argument-injection flaw (CWE-88) in GitPython before 3.1.59 that lets a crafted multi-line git-config value be promoted into a live git directive whenever GitPython performs an unrelated config write. The end state is arbitrary code execution through git's hook mechanism. This post is written for AppSec engineers, DevSecOps and platform teams, and anyone running a service that clones or inspects repositories it does not control, current as of August 2026. The post covers the affected range, the mechanism, the first-hour runbook, and the reachability question that determines whether this CVE is urgent for you specifically.

What Happened: CVE-2026-78676 in GitPython Before 3.1.59

CVE-2026-78676 is a critical argument-injection vulnerability in GitPython before 3.1.59, fixed in 3.1.59, in which unsafe re-serialization of multi-line git-config values corrupts dormant quoted values into injected directives such as core.hooksPath. Exploitation ends in arbitrary code execution when git next invokes a hook.

ItemConfirmed Detail
CVECVE-2026-78676
GitHub advisoryGHSA-284h-m62q-gf8w
AffectedGitPython before 3.1.59
Fixed in3.1.59
CVSS v4.09.3 critical
CVSS v3.19.8 critical
CWECWE-88, Improper Neutralization of Argument Delimiters in a Command
EPSS0.004, 33rd percentile (as of August 26, 2026)
PublishedAugust 24, 2026
Root causeUnsafe re-serialization of multi-line git-config values on write
ImpactArbitrary code execution via an injected core.hooksPath and hook invocation

One detail changes the calculus on upgrading. Release 3.1.59 fixes five advisories at once, not just this one: GHSA-5xxx-qhh7-9287, GHSA-3wxw-xv34-2frg, GHSA-8mcc-hrx5-hvxc, GHSA-284h-m62q-gf8w, and GHSA-7833-fr7j-v32q. The version before that, 3.1.58, closed six. Whatever you conclude about your exposure to CVE-2026-78676 specifically, the upgrade is carrying a lot more than one CVE.

How the Injection Executes: Dormant Value to Live Directive

The attack is a stored injection with a delayed trigger. An attacker-influenced git-config value sits inert as a quoted multi-line string until GitPython rewrites the config file, and at that moment the embedded newline becomes a real line break and the smuggled text becomes a directive that git honors.

Stage 1: The Plant

A config value containing an encoded newline is placed in a repository or config file the target's tooling will read. Nothing executes at this stage. Git parses the file normally and treats the value as a quoted string, which is exactly why the plant survives casual inspection.

Stage 2: The Unrelated Write

GitPython performs a config write against that repository. The write does not need to touch the poisoned key, or any key nearby. The write path re-serializes existing values, and that is the whole trigger condition.

Stage 3: The Promotion

Re-serialization fails to re-escape the embedded newline. The smuggled text lands on its own line and stops being part of a quoted value, becoming a parsed configuration directive instead. The documented example is core.hooksPath pointed at a directory the attacker controls.

Stage 4: Execution

The next git operation that fires a hook runs code from that hooks path, in the context of whatever process invoked GitPython. On a CI runner, that context holds the job's injected secrets and its cloud identity. On a service host, it holds whatever that service can reach.

State the precondition plainly, because the scoring and the practical exposure are easy to conflate. NVD scores the vector as network-reachable with no authentication and no user interaction, and that is accurate for the vector. Exploitation still requires GitPython to operate on git configuration an attacker can influence, and to perform a config write against that repository. Both statements are true at once. The CVSS vector describes what the flaw permits, not how common that code path is in your environment.

The 2026 GitPython Advisory Chain: Why This Keeps Happening

CVE-2026-78676 is not an isolated bug. The flaw is the latest entry in a long 2026 sequence of GitPython injection advisories, a striking number of which are explicitly bypasses of earlier fixes. Five advisories in 3.1.59 and six in 3.1.58 tell you the rate at which this class is being found.

The advisory titles themselves are unusually candid about the pattern. The GitHub Advisory Database entries for GitPython in 2026 include an incomplete command-injection blocklist, an incomplete clone-option denylist, a long-option prefix abbreviation bypass, a joined short-option bypass, newline injection that bypassed an earlier patch, an unsafe option check that validates before shlex.split rather than after, and incomplete section-name escaping in git config.

Read those together and the shape is obvious. GitPython's safety model leans heavily on denylists of dangerous git options and values. A denylist has to be right every time. An attacker needs one encoding the list did not anticipate. Abbreviated long options, joined short options, embedded newlines, and quoted multi-line values are the same move executed four different ways against the same defense.

That is a structural problem rather than a competence problem, and the distinction matters. GitPython's job is to hand user-supplied strings to a git binary that treats an enormous surface as control input, including options, config keys, and URL schemes. Policing that boundary from the Python side means enumerating everything git might interpret, forever, across every version of git. Any library in that position would struggle. The lesson generalizes to every wrapper around a powerful CLI.

Affected Versions, Exposure Checks, and Behavioral Signals

There are no indicators of compromise to hunt in the usual sense, because this is a vulnerability rather than a malware campaign. What you hunt instead is presence, then reachability, then the runtime artifacts a successful hook injection would leave behind.

EcosystemPackageAffectedFixedAction
PyPIGitPython< 3.1.593.1.59Upgrade, including transitive pins

Presence checks. Search for the literal string GitPython, and for the import name git, in requirements.txt, constraints.txt, pyproject.toml, poetry.lock, Pipfile.lock, uv.lock, container images, and generated SBOMs. One warning on method: GitPython is far more often a transitive dependency of CI tooling, MLOps frameworks, and repository-analysis tooling than a direct one, so searching declared dependencies alone will under-report badly. Search resolved lockfiles and SBOMs.

Reachability checks. Determine whether any code path performs a GitPython config write against a repository the service did not create. The realistic exposure pattern is narrow and nameable: services that clone, scan, or inspect repositories supplied by users, and AI coding agents that clone arbitrary repositories on request. If your only GitPython usage reads the current commit SHA of your own checkout during a build, the practical risk profile is very different from a service that ingests customer repositories.

Post-exploitation signals.

CategoryWhat to look for
Config integrityUnexpected core.hooksPath entries in .git/config or any managed git config file
FilesystemHook directories or executable hook files outside the expected repository layout
ProcessUnexpected child processes spawned by git operations on CI runners or service hosts
Change historyGit config files whose contents changed on a write that should not have altered unrelated keys

Be clear about what is not published. At the time of writing there is no public proof of concept, no confirmed exploitation in the wild, no CISA Known Exploited Vulnerabilities listing, and no credited researcher named for this specific CVE. Treat the signals above as things worth checking on exposed services, not as evidence that anyone has been targeted.

Immediate Response: The First-Hour Runbook

Upgrade to 3.1.59, then work out whether you were ever reachable, then check config integrity on anything that processes untrusted repositories. The upgrade is cheap and closes five advisories, so ship it first and investigate second.

  1. Upgrade GitPython to 3.1.59 or later everywhere, including transitive pins, lockfiles, and container base images. The release closes five advisories, not one.
  2. Inventory where GitPython actually lives, treating transitive presence as the default case rather than the exception. Search resolved lockfiles and SBOMs, not declared dependencies.
  3. Identify which services perform config writes against repositories they did not create. This is the reachability question, and it separates having the package from being exposed.
  4. For any service that processes untrusted or user-supplied repositories, audit git config files for unexpected core.hooksPath values and for hook directories outside the expected layout.
  5. Inspect CI runner and service-host process history for unexpected children of git operations across the window since that exposure began.
  6. Rotate credentials scoped to any environment where step 4 or step 5 turned something up. Do not rotate broadly on the basis of package presence alone, since an unscoped sweep delays the rotations that matter.
  7. Where the upgrade cannot ship immediately, stop processing untrusted git configuration on that path and treat repository ingestion as the compensating control boundary.
  8. Record which services were checked and cleared. The next GitPython advisory should start from an inventory rather than from zero.

A CVSS 9.8 With a 0.4% EPSS Is a Prioritization Problem, Not a Patch Problem

CVE-2026-78676 scores 9.8 on CVSS v3.1 and 0.004 on EPSS, roughly the 33rd percentile. The impact is severe and the modeled probability of near-term exploitation is low. Both numbers are correct, and neither one tells you whether your code calls the vulnerable path.

The two scores answer different questions. CVSS describes what happens if the flaw is exploited under the worst reasonable conditions. EPSS estimates how likely exploitation activity is across the internet in the near term, and for a CVE published two days earlier that figure is largely a statement about the absence of observed activity so far. A low EPSS on a fresh CVE is a scheduling input, not an all-clear.

Neither score is a statement about your application, and that gap is where the operational pain lives. GitPython sits in a very large number of Python dependency trees, usually transitively, and the overwhelming majority of those code paths never write git config against a repository the service did not create. A manifest-based scanner reports every one of them identically, at 9.8. That is the mechanism by which a genuine critical becomes indistinguishable from noise, and it is why "patch everything critical" stops being executable at scale.

Closing that gap takes two things. Finding every affected version across direct and transitive trees is the necessary first half, and Kodem SCA does that with the transitive reach this particular package demands. The second half is reachability analysis backed by runtime execution evidence: whether the vulnerable path is actually invoked in the deployed application rather than merely present in a lockfile. For the runtime end of this specific flaw, where a successful injection surfaces as an unexpected child process from a git operation, Kodem ADR is the layer that sees the behavior. Together that turns an upgrade queue from a list of package matches into a list of real exposures.

One qualifier worth stating rather than implying. Deprioritizing a finding with evidence is not the same as declaring it safe. A non-exploitable result is a point-in-time observation about current application conditions, and it needs revalidating when those conditions change. A refactor that introduces a config write on an ingestion path moves the finding back into scope.

Hardening Beyond the 3.1.59 Upgrade

Assume another GitPython injection advisory lands, because the 2026 record says one will. The controls that survive the next one treat git configuration as untrusted input and constrain what a compromised git invocation can reach.

  1. Treat any repository or git config your service did not create as untrusted input, and isolate the code path that ingests it from everything else.
  2. Run repository ingestion in a sandboxed process with no ambient credentials, so a hook execution inherits nothing worth stealing.
  3. Set core.hooksPath explicitly, or disable hooks entirely, in automation contexts with no legitimate need for them.
  4. Default-deny egress from CI runners and repo-processing workloads, which caps what an injected hook can do after firing.
  5. Pin and hash-verify dependencies so a transitive GitPython bump becomes a visible, reviewable event rather than a silent one.
  6. Track GitPython specifically in upgrade automation given the 2026 advisory cadence, rather than waiting for a quarterly dependency sweep.
  7. Keep behavioral monitoring on repo-processing services, since the exploitation signal here is an unexpected child process rather than a network indicator.

What the GitPython Chain Signals About Denylist Security

The GitPython sequence is a clean case study in how denylists degrade under sustained attention. Each fix closed one encoding, and each subsequent advisory found another way to say the same thing to the same git binary.

  • Wrapper libraries that hand strings to a powerful CLI inherit that CLI's entire control surface, and policing it from the wrapper is a structurally losing position.
  • The interesting bugs have moved from "can I inject a command" to "can I get my input re-serialized into a control position," which is a quieter class and much harder to write a test for.
  • Dependency severity keeps decoupling from dependency risk, which pushes the real work away from patching and toward reachability.

One falsifiable prediction: the next GitPython advisory of this class will be another serialization or normalization boundary rather than a new dangerous-option name, since the option denylist has now had the most attention. Should the next one turn out to be simply another unlisted git option, this prediction is wrong.

Frequently Asked Questions

What is CVE-2026-78676?

CVE-2026-78676 is a critical argument-injection vulnerability (CWE-88) in GitPython before 3.1.59 in which unsafe re-serialization of multi-line git-config values promotes a dormant quoted value into a live git directive such as core.hooksPath, ending in arbitrary code execution through git hooks. Published August 24, 2026 and tracked as GHSA-284h-m62q-gf8w.

Which GitPython versions are affected, and what is the fix?

Every version before 3.1.59 is affected, and 3.1.59 is the fixed release. That release closes five advisories in total, so the upgrade is worth considerably more than this single CVE.

How do I know if I am actually exposed?

Presence is not exposure. You are exposed where a code path performs a GitPython config write against a repository or config your service did not create, which in practice means services that clone, scan, or inspect user-supplied repositories, including AI coding agents.

Why is the EPSS score so low if the CVSS is 9.8?

CVSS measures impact under worst reasonable conditions, while EPSS estimates the probability of exploitation activity in the near term. For a CVE published two days earlier, that figure largely reflects the absence of observed activity so far. Treat a low EPSS as a scheduling input rather than as permission to skip the upgrade.

Does exploitation really need no authentication?

NVD scores the vector as network-reachable without authentication or user interaction, which is accurate for the vector. Exploitation still requires GitPython to process attacker-influenced git configuration and to perform a config write against it, so practical exposure depends on your code path.

Why does GitPython keep getting this class of CVE?

GitPython's safety model leans on denylists of dangerous git options and values, and the 2026 advisory record is largely a sequence of encodings those denylists did not anticipate: abbreviated long options, joined short options, embedded newlines, and quoted multi-line values. The underlying difficulty is structural, since git treats a very large surface as control input.

Can my SCA scanner tell me whether this matters?

A manifest-based scanner tells you the affected version is present, which is necessary and not sufficient. Determining whether the vulnerable path is actually invoked requires reachability analysis and runtime execution evidence.

What should I do if I cannot upgrade immediately?

Stop processing untrusted git configuration on the affected path, isolate repository ingestion in a sandboxed process without ambient credentials, and set or disable core.hooksPath explicitly in automation contexts. Treat these as compensating controls, not as a substitute for 3.1.59.

References

  1. NVD. August 24, 2026. CVE-2026-78676 Detail. NVD.
  2. GitHub. August 2026. GHSA-284h-m62q-gf8w. GitHub Security Advisories.
  3. GitPython. Changelog. gitpython.readthedocs.io.
  4. GitHub Advisory Database. GitPython advisories. GitHub.
  5. FIRST. Exploit Prediction Scoring System (EPSS). FIRST.
Table of contents

Related blogs

scrambleeeer PyPI Reverse Shell: The Payload That Waits for You to Call It

scrambleeeer PyPI Reverse Shell: The Payload That Waits for You to Call It

scrambleeeer and scrambleeer hid a PyPI reverse shell that fires on library use, not install. Get the affected versions, the IOCs, and the first-hour runbook.

August 22, 2026

10

The keyv Supply Chain Attack: Affected Versions, IOCs, and the First-Hour Response Runbook

The keyv Supply Chain Attack: Affected Versions, IOCs, and the First-Hour Response Runbook

The keyv supply chain attack poisoned 11 npm seed packages and spread a Shai-Hulud worm to 400+ more. Get affected versions, IOCs, and the first-hour runbook.

August 4, 2026

13

Reachability Predicts. Runtime Proves.

Reachability Predicts. Runtime Proves.

Reachability tells you code can execute. Runtime tells you it did.

July 20, 2026

7

Stop the waste.
Protect your environment with Kodem.

A Primer on Runtime Intelligence

See how Kodem reads what actually loads and executes in your running applications, and why that changes which findings matter.

See the Kodem platform

Kodem shows which vulnerabilities actually load and execute in your running applications, so your team works the risk that is real.

The State of the Application Security Workflow

This report aims to equip readers with actionable insights that can help future-proof their security programs. Kodem, the publisher of this report, purpose built a platform that bridges these gaps by unifying shift-left strategies with runtime monitoring and protection.

3D book mockup of Kodem's State of the Application Security Workflow 2025 report

Get real-time insights across the full stack…code, containers, OS, and memory

Watch how Kodem’s runtime security platform detects and blocks attacks before they cause damage. No guesswork. Just precise, automated protection.

Kodem issues list with a magnified view of insight icons: runtime, ingress, and exploitability
Combined author
Kodem Security Research Team
Publish date

0 min read

Vulnerabilities

SCA Security