shallow-backup · GitPython: Arbitrary local file content disclosure via [include] directive in untrusted .gitmodules
Affects shallow-backupSource GCVE Database
Not known to be exploited · CVSS 8.6 High : upgrade to 6.6_7 soon.
Data as of 26 Oct 2026, 17:16 UTC
- EPSS
- 0.1% chance of exploitation in 30 days – higher than 7% of CVEsExploit Prediction Scoring System: the likelihood attackers will use this flaw soon.
- Patch
- Available – fixed in 6.6_7
CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:NExploitation evidence
Disclosed: evidence found.
CVE published 25 Aug 2026
Detectable: no evidence on record.
No public nuclei template indexed
Weaponised: no evidence on record.
No public exploit code indexed in Metasploit or Exploit-DB
Exploited in the wildExploited: no evidence on record.
Not in CISA KEV and no source reports exploitation
Observed by OffSeq MirageSeen by Mirage: no evidence on record.
No attack traffic recorded by OffSeq Mirage sensors
A missing step is not evidence of safety. Sources: cve.org (opens in a new tab), the Metasploit, Exploit-DB and nuclei-templates index as of 26 Oct 2026.
What to do
Upgrade shallow-backup to 6.6_7.
Affected software
| Product | Affected | Fixed in |
|---|---|---|
Homebrewshallow-backuppkg:brew/shallow-backupOther threats | Affected
| Fixed in
|
Check your own hosts for these packages with threat-finder (opens in a new tab), our free open-source scanner.
Description
# [HIGH] Arbitrary local file content disclosure via `[include]` directive in untrusted `.gitmodules` (`SubmoduleConfigParser` never disables `merge_includes`) - **CWE:** CWE-200 (Exposure of Sensitive Information) / CWE-73 (External Control of File Name or Path) - **Affected component:** `git/objects/submodule/base.py`, `Submodule._config_parser()` (~line 273) constructing `SubmoduleConfigParser(fp_module, read_only=read_only)`; `git/config.py`, `GitConfigParser.__init__` (`merge_includes` default), `GitConfigParser.read()`/`_included_paths()` (include-path resolution, ~lines 630-685), `GitConfigParser._read()` (~line 493-498, `MissingSectionHeaderError`) - **Affected version:** GitPython at HEAD (`9729ed3b948f2bde09f1f188c5311e172212b67e`, 2026-08-05, VERSION `3.1.58`) ## Reachability `GitConfigParser.__init__` defaults `merge_includes=True`: any config file it parses has its `[include]` (and, when a `repo=` is supplied, `[includeIf ...]`) directives followed and merged in. The maintainers already recognized this as dangerous for one specific case and fixed it in commit `41ecc6a4` ("Disable merge_includes in config writers"), which passes `merge_includes=False` when `Repo.config_writer()` builds its parser (`git/repo/base.py`). That fix never touched `Submodule._config_parser()`. This method builds the parser used for **every** read of a repo's submodule configuration — `repo.submodules`, `Submodule.iter_items()`, `Submodule.config()` — via `SubmoduleConfigParser(fp_module, read_only=read_only)`, passing neither `merge_includes=False` nor `repo=`. The `True` class default is therefore inherited unchanged, and `fp_module` here is `.gitmodules` — **the single most attacker-controlled config file in the entire codebase**, since it ships verbatim as tracked content inside any cloned repository. `GitConfigParser.read()`'s include-path resolution (~line 662-680) performs no containment check: `osp.isabs(include_path)` short-circuits the path join entirely for an absolute path, and a relative path is joined with `osp.join(osp.dirname(file_path), include_path)` / `osp.normpath()`'d with no check that the result stays under the repository. `~` is expanded via `osp.expanduser`. The only gate before opening is `os.access(include_path, os.R_OK)` — a readability check, not a path restriction. Once opened, `GitConfigParser._read()` parses the target file as git-config INI. If the first non-blank/non-comment line is not a `[section]` header — true of virtually any non-gitconfig file (source code, `/etc/passwd`, `.env` files, credential files, logs, JSON/YAML) — it raises `configparser.MissingSectionHeaderError(fpname, lineno, line)`. Python's stdlib formats this exception's `str()` as `"File contains no section headers.\nfile: %r, line: %d\n%r" % (fpname, lineno, line)` — it embeds the **verbatim content** of that file's first line in the exception message. `Submodule.iter_items()` catches only `(IOError, BadName)`, not `configparser.Error`, so this exception propagates straight out of the ordinary, read-only `repo.submodules` call. ## Root cause Parity gap between two config-parser construction sites for the exact same footgun: `Repo.config_writer()` was hardened against `merge_includes` in 2023 (`41ecc6a4`); `Submodule._config_parser()` — which parses `.gitmodules`, content that is *always* attacker-controlled the moment a repository is cloned from an untrusted source — was never given the same treatment. (The submodule *write*-mode config parser at `git/objects/submodule/base.py` for `.git/modules/<name>/config` — a different, locally-generated file — has correctly passed `merge_includes=False` since 2022, underscoring that the omission for `.gitmodules` reads looks like an oversight rather than a considered exception.) ## Exploit path 1. Attacker crafts a repository whose `.gitmodules` contains a legitimate-looking `[submodule ...]` section plus: ``` [include] path = /etc/passwd ``` (an absolute path bypasses any traversal reasoning entirely; a relative `../../../../etc/passwd`-style path works too). 2. Victim performs the extremely common, entirely read-only operation of enumerating a cloned repo's submodules: `list(repo.submodules)` (or any `for sm in repo.submodules`) — no `update()`, `init()`, or checkout of any kind required. 3. `SubmoduleConfigParser` (inheriting `merge_includes=True`) follows the `[include]` directive, opens `/etc/passwd`, and `GitConfigParser._read()` raises `MissingSectionHeaderError` whose message embeds `/etc/passwd`'s first line verbatim. 4. This exception surfaces wherever the host application observes exceptions from GitPython — CI logs, error pages, exception trackers, or any dependency-scanner/code-review-bot/hosting-platform tool built on `repo.submodules` — disclosing the targeted file's first line to the attacker (directly, or indirectly via any channel that echoes the error). ## Impact Non-blind local file content disclosure (first line) of any file readab
References
- github.com/gitpython-developers/GitPython/security/advisories/GHSA-7833-fr7j-v32q (opens in a new tab)
- nvd.nist.gov/vuln/detail/CVE-2026-78675 (opens in a new tab)
- github.com/gitpython-developers/GitPython/pull/2211 (opens in a new tab)
- github.com/gitpython-developers/GitPython/commit/ef7568e3b317ce617eacda39b8b54dcdff8c3b5c (opens in a new tab)
- github.com/gitpython-developers/GitPython (opens in a new tab)
- github.com/gitpython-developers/GitPython/releases/tag/3.1.59 (opens in a new tab)
- github.com/pypa/advisory-database/tree/main/vulns/gitpython/PYSEC-2026-3785.yaml (opens in a new tab)
- vulncheck.com/advisories/gitpython-before-local-file-content-disclosure-via-gitmodules (opens in a new tab)