# gix and gitoxide: unvalidated submodule name traverses out of .git/modules and redirects state() / open() to another repository

Unvalidated submodule names from .gitmodules are joined into .git/modules paths, letting a name like ../../../escaped-target.git redirect state() and open() to an attacker-chosen repository.

- Payout: Research
- Severity: high
- Program: gitoxide
- Found by: N0zoM1z0
- Disclosed: 2026-05-05
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-fr8x-3vfx-f45h
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-fr8x-3vfx-f45h
- Tags: area:oss domain:oss impact:integrity kind:research platform:ghsa sev:high tech:rust technique:source-review vuln:path-traversal

**Target:** Rust git library submodule resolution (gix-submodule access.rs, gix submodule/mod.rs)
**Root cause:** Submodule names from .gitmodules are exposed unvalidated and joined directly as a path component via common_dir().join("modules").join(name) with no normalization-and-confinement check, so traversal segments escape .git/modules.
**Steps:**
1. Unzip PoC archive and enter `pocs/F002`.
2. Run `cargo run --quiet`.
3. Observe `submodule_name=../../../escaped-target.git` and `derived_git_dir_raw=.../.git/modules/../../../escaped-target.git`.
4. After normalization `derived_git_dir_normalized=.../artifacts/escaped-target.git`, outside modules root.
5. `state()` returns `repository_exists=true` and `open()` returns `submodule_opened=true` with `opened_common_dir_normalized` matching the escaped target.
**How found:** Code reading of the submodule flow: noticed names from configuration are reused directly as filesystem path fragments in common_dir().join("modules").join(name), then built a PoC proving the escaped path is actually used by state() and open() rather than just constructed as a string.
**Exploitation:** A malicious .gitmodules submodule name containing traversal segments causes callers that enumerate/inspect submodules to have state() and open() resolve to an attacker-chosen repository outside .git/modules, redirecting repository operations. No code execution claimed.
**Insight:** When a library derives filesystem paths from config-supplied identifiers, verify the sink actually consumes the escaped path (existence checks, open) rather than only string construction — and test names with `../` segments against the modules root confinement.

