CVE-2026-82252High▾ Twilightgix and gitoxide's symlinked .gitmodules are followed and parsed from outside of the repository
▾ Twilight zone — High severity, or a signal on a lesser flaw
impact 41.3 · likelihood 0.1 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Aug 29.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via OSV
Last analysed / modified upstream
0.4%
attachments: pocs.zip
When Repository::submodules() loads submodule metadata, it prefers the worktree .gitmodules file if that path exists. In the current implementation, the path is read with std::fs::read(), which follows symlinks. As a result, a repository can present a symlinked .gitmodules that points outside the repository, and gitoxide will parse the out-of-repository bytes as submodule configuration.
This is a repository-boundary violation. A caller using the high-level submodule API can believe it is reading repository-local submodule metadata, while the bytes are actually coming from an arbitrary file outside the repository tree.
The relevant flow is:
gix/src/repository/location.rs derives the worktree .gitmodules path as workdir/.gitmodules.gix/src/repository/submodule.rs reads that path with std::fs::read(&path) and immediately parses the bytes as a submodule configuration file.Repository::submodules() exposes the parsed entries through the high-level API.The issue is not in the parser. The issue is that the worktree path is treated as an ordinary file without checking whether it is a symlink, and without checking whether the canonicalized target remains inside the repository worktree.
Because std::fs::read() follows symlinks, a malicious repository can cause gitoxide to ingest bytes from an attacker-chosen location outside the repository. The resulting Submodule objects then expose name, path, and url values derived from that external file.
Use the attached PoC zip that contains the pocs/ workspace.
Unzip the PoC archive.
Enter pocs/F001.
Run:
cargo run --quiet
Compare the output with pocs/F001/result.txt.
Important outputs include:
gitmodules_symlink=.../victim-repo/.gitmodulessymlink_target=.../outside/modules.confparsed_name=symlinkedparsed_path=deps/symlinkedparsed_url=https://attacker.example/symlinked.gitThese outputs show that gitoxide parsed the submodule configuration from the symlink target outside the repository, not from repository-local bytes.
Confirmed impact:
Repository::submodules();name, path, and url;This report does not claim direct command execution from this code path by itself. The demonstrated impact is metadata injection across the repository boundary.
A safe fix is to stop silently following symlinks for the worktree .gitmodules path in this loading path.
Reasonable options include:
symlink_metadata() / lstatstyle checks and reject symlinked .gitmodules when loading from the worktree;.gitmodules from the index or HEAD tree rather than following the worktree path.At minimum, the worktree path should not silently follow symlinks to arbitrary external files.
gitoxide < 0.52.1gix < 0.83.0Upgrade to a patched release:
gitoxide 0.52.1gix 0.83.0Connected by shared product, vendor, weakness, or advisory.
CVE-2025-24890Medium· 6.8gitoxide is an implementation of git written in Rust
CVE-2026-82251Highgix and gitoxide: unvalidated submodule name traverses out of .git/modules and redirects state() / open() to another repository
CVE-2026-91986Medium· 5.4gitoxide gix-transport before 0.59.2 fails to filter control characters in git-daemon connect requests, allowing attackers to inject NUL/CR/LF bytes via crafted git URLs