Summary
gix's submodule name validation bypass + trust inheritance flaw enables path traversal and credential disclosure
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.gitVictim 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:[email protected]/...)http.extraHeader(oftenAuthorization: Bearer <token>)credential.*sectionscore.sshCommand
Accessible via standard API:
repo.config_snapshot().string("http.extraHeader"),repo.find_remote("origin"), etc.
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
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.
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.
Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files. Typical impact: unauthorized file read or write outside the intended directory.
Affected versions
Security releases
Kodem intelligence
Severity tells you how bad this could be in the worst case. It does not tell you whether you are exposed. Exploitability and impact are functions of runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A vulnerable package can sit in your dependency tree and never run.
Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter. Kodem's runtime-powered SCA identifies whether this CVE is reachable in your applications.
Already deployed Kodem?
See it in your environmentNew to Kodem? Get a demo →Remediation advice
- 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
Frequently Asked Questions
- What is GHSA-P3HW-MV63-RF9W? GHSA-P3HW-MV63-RF9W is a high-severity path traversal vulnerability in gix (rust), affecting versions < 0.83.0. It is fixed in 0.83.0, 0.11.1. Input manipulates file paths to reach files outside the intended directory, such as configuration or credential files.
- Which packages are affected by GHSA-P3HW-MV63-RF9W?
gix(rust) (versions < 0.83.0)gix-validate(rust) (versions <= 0.10.0)
- Is there a fix for GHSA-P3HW-MV63-RF9W? Yes. GHSA-P3HW-MV63-RF9W is fixed in 0.83.0, 0.11.1. Upgrade to this version or later.
- Is GHSA-P3HW-MV63-RF9W exploitable, and should I be worried? Whether GHSA-P3HW-MV63-RF9W is exploitable in your environment depends on whether the vulnerable code is present and reachable. A CVSS score is a worst-case rating; it does not account for your specific deployment, configuration, or usage patterns. Kodem, an Intelligent Application Security platform, uses runtime intelligence to show which vulnerabilities actually execute in production, so you can focus on the ones that represent real risk. Get a demo
- What actually determines whether GHSA-P3HW-MV63-RF9W is exploitable, and how bad it is? Exploitability and impact are not fixed properties of a CVE. They depend on runtime truth: whether the vulnerable code is present, reachable, and actually executes in your application. A high CVSS score on a dependency that never runs is not the same as real risk. Kodem, an Intelligent Application Security platform, uses runtime intelligence to reveal which vulnerabilities actually execute in production, so teams prioritize the ones that genuinely matter.
- How do I fix GHSA-P3HW-MV63-RF9W?
- Upgrade
gixto 0.83.0 or later - Upgrade
gix-validateto 0.11.1 or later
- Upgrade