
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.
| Item | Confirmed Detail |
|---|---|
| CVE | CVE-2026-78676 |
| GitHub advisory | GHSA-284h-m62q-gf8w |
| Affected | GitPython before 3.1.59 |
| Fixed in | 3.1.59 |
| CVSS v4.0 | 9.3 critical |
| CVSS v3.1 | 9.8 critical |
| CWE | CWE-88, Improper Neutralization of Argument Delimiters in a Command |
| EPSS | 0.004, 33rd percentile (as of August 26, 2026) |
| Published | August 24, 2026 |
| Root cause | Unsafe re-serialization of multi-line git-config values on write |
| Impact | Arbitrary 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.
| Ecosystem | Package | Affected | Fixed | Action |
|---|---|---|---|---|
| PyPI | GitPython | < 3.1.59 | 3.1.59 | Upgrade, 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.
| Category | What to look for |
|---|---|
| Config integrity | Unexpected core.hooksPath entries in .git/config or any managed git config file |
| Filesystem | Hook directories or executable hook files outside the expected repository layout |
| Process | Unexpected child processes spawned by git operations on CI runners or service hosts |
| Change history | Git 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.
- Upgrade
GitPythonto3.1.59or later everywhere, including transitive pins, lockfiles, and container base images. The release closes five advisories, not one. - Inventory where GitPython actually lives, treating transitive presence as the default case rather than the exception. Search resolved lockfiles and SBOMs, not declared dependencies.
- 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.
- For any service that processes untrusted or user-supplied repositories, audit git config files for unexpected
core.hooksPathvalues and for hook directories outside the expected layout. - Inspect CI runner and service-host process history for unexpected children of git operations across the window since that exposure began.
- 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.
- Where the upgrade cannot ship immediately, stop processing untrusted git configuration on that path and treat repository ingestion as the compensating control boundary.
- 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.
- 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.
- Run repository ingestion in a sandboxed process with no ambient credentials, so a hook execution inherits nothing worth stealing.
- Set
core.hooksPathexplicitly, or disable hooks entirely, in automation contexts with no legitimate need for them. - Default-deny egress from CI runners and repo-processing workloads, which caps what an injected hook can do after firing.
- Pin and hash-verify dependencies so a transitive GitPython bump becomes a visible, reviewable event rather than a silent one.
- Track GitPython specifically in upgrade automation given the 2026 advisory cadence, rather than waiting for a quarterly dependency sweep.
- 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
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.
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.
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.
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.
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.
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.
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.
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
- NVD. August 24, 2026. CVE-2026-78676 Detail. NVD.
- GitHub. August 2026. GHSA-284h-m62q-gf8w. GitHub Security Advisories.
- GitPython. Changelog. gitpython.readthedocs.io.
- GitHub Advisory Database. GitPython advisories. GitHub.
- FIRST. Exploit Prediction Scoring System (EPSS). FIRST.
Related blogs

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.
10
Stop the waste.
Protect your environment with Kodem.
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.
.avif)
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.


