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).
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.
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
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() or submodule.status():
git_dir() returns .git/modules/x..y/../../.. which resolves to the parent .git/open_opts() is called with Trust::Full (inherited from parent, ownership check skipped).git/config is fully parsedThe returned Repository object exposes all config values from the traversed path:
remote.origin.url (may contain https://user:token@github.com/...)http.extraHeader (often Authorization: Bearer <token>)credential.* sectionscore.sshCommandAccessible via standard API: repo.config_snapshot().string("http.extraHeader"), repo.find_remote("origin"), etc.
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.
open() or status() on submodules (tools that only list submodules are not affected).. occurrences (iterate, not single find)gix_validate::submodule::name() in git_dir() before constructing the pathgit_dir_trust from parent when opening submodule repos -- always re-derive trust from path ownershipHigh. Network vector (via clone), requires user interaction (submodule operations). The trust bypass enables credential disclosure from traversed git directories. Confidentiality impact is high.
{
"github_reviewed": true,
"github_reviewed_at": "2026-05-05T19:20:38Z",
"nvd_published_at": null,
"severity": "HIGH",
"cwe_ids": [
"CWE-22",
"CWE-200"
]
}