GHSA-p3hw-mv63-rf9w: Gix Path Traversal Vulnerability
HIGHGHSA-p3hw-mv63-rf9w is a HIGH path traversal vulnerability (CWE-200: Exposure of Sensitive Information to an Unauthorized Actor) affecting Gix libraries in the crates.io ecosystem. The issue involves gix's submodule name validation bypass + trust inheritance flaw enables path traversal and credential disclosure. Remediation is available by upgrading affected packages to version 0.83.0, 0.11.1.
GHSA-p3hw-mv63-rf9w at a Glance
How to Fix GHSA-p3hw-mv63-rf9w
To remediate this vulnerability, upgrade all affected Gix dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade gix to 0.83.0
Upgrade gix-validate to 0.11.1
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for gix
How to Check if You Are Affected
To determine whether your application is affected by GHSA-p3hw-mv63-rf9w, inspect your project dependencies for Gix:
inspect dependencies for gix
Analyze the terminal output to locate any version of Gix in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 0.83.0), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-200: Exposure of Sensitive Information to an Unauthorized Actor, where the product exposes sensitive information to an actor that is not explicitly authorized to have access to that information. In Gix, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
GHSA-p3hw-mv63-rf9w Affected Gix Versions
2 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
gix
|
crates.io |
< 0.83.0
|
0.83.0
|
|
gix-validate
|
crates.io |
< 0.11.1
|
0.11.1
|
References
Frequently Asked Questions
What is GHSA-p3hw-mv63-rf9w?
Which Gix versions are affected?
What version fixes GHSA-p3hw-mv63-rf9w?
Is gix affected?
Is gix-validate affected?
What CWE is associated with GHSA-p3hw-mv63-rf9w?
How can I check crates.io dependencies for GHSA-p3hw-mv63-rf9w?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-p3hw-mv63-rf9w",
"modified" : "2026-05-05T19:20:38Z",
"published" : "2026-05-05T19:20:38Z",
"aliases" : [ ],
"summary" : "gix's submodule name validation bypass + trust inheritance flaw enables path traversal and credential disclosure",
"details" : "### Summary\n\nSubmodule 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).\n\n### Details\n\n**Bug 1: Validation bypass in `gix-validate/src/submodule.rs` (lines 27-42)**\n\nThe `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:\n\n```rust\npub fn name(name: &BStr) -> Result<&BStr, name::Error> {\n match name.find(b\"..\") {\n Some(pos) => {\n let &b = name.get(pos + 2).ok_or(name::Error::ParentComponent)?;\n if b == b'/' || b == b'\\\\' {\n Err(name::Error::ParentComponent)\n } else {\n Ok(name) // Returns Ok without checking rest of string\n }\n }\n None => Ok(name),\n }\n}\n```\n\nBypass: `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.\n\n**Bug 2: Validation never called in production**\n\n`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.\"\n\n`git_dir()` at `gix/src/submodule/mod.rs:198-204` constructs filesystem paths from raw names:\n\n```rust\npub fn git_dir(&self) -> PathBuf {\n self.state.repo.common_dir().join(\"modules\").join(gix_path::from_bstr(self.name()))\n}\n```\n\n**Bug 3: Trust inheritance bypass in `Submodule::open()`**\n\nAt `gix/src/submodule/mod.rs:270`, `open()` clones the parent repository's options:\n\n```rust\nmatch crate::open_opts(self.git_dir_try_old_form()?, self.state.repo.options.clone()) {\n```\n\nThe parent's `options.git_dir_trust` is `Some(Trust::Full)`. At `gix/src/open/repository.rs:103-104`:\n\n```rust\nif options.git_dir_trust.is_none() {\n options.git_dir_trust = gix_sec::Trust::from_path_ownership(&git_dir)?.into();\n}\n```\n\nSince 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.\n\n### PoC\n\nCompiled and executed in Rust 1.94.1 `--release` mode. All bypass cases confirmed:\n\n```\nBYPASS a..b/../../../.git/ -> PASSED validation\n git_dir = .git/modules/a..b/../../../.git/\n normalized = .git/ (parent repo!)\n\nBYPASS x..y/../../../.git/config -> PASSED validation\n git_dir = .git/modules/x..y/../../../.git/config\n normalized = .git/config\n```\n\n### Attack chain\n\n1. Attacker crafts a repository with `.gitmodules`:\n ```ini\n [submodule \"x..y/../../..\"]\n path = innocent\n url = https://attacker.com/repo.git\n ```\n\n2. Victim clones the repository using a tool built on gitoxide.\n\n3. When the tool iterates submodules and calls `submodule.open()` or `submodule.status()`:\n - `git_dir()` returns `.git/modules/x..y/../../..` which resolves to the parent `.git/`\n - `open_opts()` is called with `Trust::Full` (inherited from parent, ownership check skipped)\n - The parent's `.git/config` is fully parsed\n\n4. The returned `Repository` object exposes all config values from the traversed path:\n - `remote.origin.url` (may contain `https://user:token@github.com/...`)\n - `http.extraHeader` (often `Authorization: Bearer <token>`)\n - `credential.*` sections\n - `core.sshCommand`\n\n5. Accessible via standard API: `repo.config_snapshot().string(\"http.extraHeader\")`, `repo.find_remote(\"origin\")`, etc.\n\n### Impact\n\nA 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.\n\nThe 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.\n\n### Honest limitations\n\n- The traversed path must be a valid git directory (HEAD, objects/, refs/ must exist)\n- The victim's tool must call `open()` or `status()` on submodules (tools that only list submodules are not affected)\n- Credential exposure requires the target config to contain embedded credentials\n- Submodule operations currently require explicit user action\n\n### Suggested fix\n\n1. Fix the validation to check ALL `..` occurrences (iterate, not single `find`)\n2. Call `gix_validate::submodule::name()` in `git_dir()` before constructing the path\n3. Do NOT inherit `git_dir_trust` from parent when opening submodule repos -- always re-derive trust from path ownership\n\n### Severity\n\nHigh. Network vector (via clone), requires user interaction (submodule operations). The trust bypass enables credential disclosure from traversed git directories. Confidentiality impact is high.",
"severity" : [ {
"type" : "CVSS_V4",
"score" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "crates.io",
"name" : "gix"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.83.0"
} ]
} ]
}, {
"package" : {
"ecosystem" : "crates.io",
"name" : "gix-validate"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "0.11.1"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 0.10.0"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-p3hw-mv63-rf9w"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/GitoxideLabs/gitoxide"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-200", "CWE-22" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-05-05T19:20:38Z",
"nvd_published_at" : null
}
}