PT-2026-38895 · Crates.Io · Gix+1
Published
2026-05-05
·
Updated
2026-05-05
CVSS v4.0
7.5
High
| Vector | AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N |
Summary
Submodule name validation bypass plus missing validation in production code paths allows path traversal via crafted
.gitmodules. Combined with a trust inheritance flaw in Submodule::open(), this enables reading arbitrary git repository configs (including credentials) from traversed paths with full trust (CWE-22, CWE-200).Details
Bug 1: Validation bypass in
gix-validate/src/submodule.rs (lines 27-42)The
name() function uses name.find(b"..") which returns only the FIRST occurrence. If the first .. is embedded in a non-traversal context, the function returns Ok without checking subsequent ../ sequences:pub fn name(name: &BStr) -> Result<&BStr, name::Error> {
match name.find(b"..") {
Some(pos) => {
let &b = name.get(pos + 2).ok or(name::Error::ParentComponent)?;
if b == b'/' || b == b'' {
Err(name::Error::ParentComponent)
} else {
Ok(name) // Returns Ok without checking rest of string
}
}
None => Ok(name),
}
}
Bypass:
a..b/../../../.git/ passes because find(b"..") returns position 1 (the .. in a..b), checks name[3] == b'b', and returns Ok. The real /../../../ is never checked.Bug 2: Validation never called in production
gix validate::submodule::name() has zero production callers (only test code). The names() iterator in gix-submodule/src/access.rs:29 explicitly documents it returns "unvalidated names."git dir() at gix/src/submodule/mod.rs:198-204 constructs filesystem paths from raw names:pub fn git dir(&self) -> PathBuf {
self.state.repo.common dir().join("modules").join(gix path::from bstr(self.name()))
}
Bug 3: Trust inheritance bypass in
Submodule::open()At
gix/src/submodule/mod.rs:270, open() clones the parent repository's options:match crate::open opts(self.git dir try old form()?, self.state.repo.options.clone()) {
The parent's
options.git dir trust is Some(Trust::Full). At gix/src/open/repository.rs:103-104:if options.git dir trust.is none() {
options.git dir trust = gix sec::Trust::from path ownership(&git dir)?.into();
}
Since trust is already
Some(Full), the ownership check is skipped entirely. The traversed path is opened with Trust::Full regardless of ownership, bypassing gitoxide's safe-directory protections.PoC
Compiled and executed in Rust 1.94.1
--release mode. All bypass cases confirmed: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
Attack chain
- Attacker crafts a repository with
.gitmodules:
[submodule "x..y/../../.."]
path = innocent
url = https://attacker.com/repo.git
-
Victim clones the repository using a tool built on gitoxide.
-
When the tool iterates submodules and calls
submodule.open()orsubmodule.status():
git dir()returns.git/modules/x..y/../../..which resolves to the parent.git/open opts()is called withTrust::Full(inherited from parent, ownership check skipped)- The parent's
.git/configis fully parsed
- The returned
Repositoryobject exposes all config values from the traversed path:
remote.origin.url(may containhttps://user:token@github.com/...)http.extraHeader(oftenAuthorization: Bearer <token>)credential.*sectionscore.sshCommand
- Accessible via standard API:
repo.config snapshot().string("http.extraHeader"),repo.find remote("origin"), etc.
Impact
A crafted
.gitmodules in a malicious repository causes gitoxide to open arbitrary git directories as submodule repositories with full trust, exposing their configuration including credentials. This is the same class of vulnerability as GHSA-7w47-3wg8-547c (path traversal), but through the submodule name vector with an additional trust bypass.The trust inheritance is the critical amplifier: without it, the traversed path would undergo ownership checks that could block the attack. With it, any git directory reachable via
../ is opened with full trust.Honest limitations
- The traversed path must be a valid git directory (HEAD, objects/, refs/ must exist)
- The victim's tool must call
open()orstatus()on submodules (tools that only list submodules are not affected) - Credential exposure requires the target config to contain embedded credentials
- Submodule operations currently require explicit user action
Suggested fix
- Fix the validation to check ALL
..occurrences (iterate, not singlefind) - Call
gix validate::submodule::name()ingit dir()before constructing the path - Do NOT inherit
git dir trustfrom parent when opening submodule repos -- always re-derive trust from path ownership
Severity
High. Network vector (via clone), requires user interaction (submodule operations). The trust bypass enables credential disclosure from traversed git directories. Confidentiality impact is high.
Fix
Path traversal
Information Disclosure
Found an issue in the description? Have something to add? Feel free to write us 👾
Related Identifiers
Affected Products
Gix
Gix-Validate