Read the docs
TI-2026-66656 Aliases GHSA-pg4w-g64p-qwhj

gix and gitoxide's symlinked .gitmodules are followed and parsed from outside of the repository

Priority LOW 38CWE-22Published · Updated

Key facts

Attack vector
—
Privileges required
—
User interaction
—
Attack complexity
—
Impact type
—
Confidentiality · integrity · availability
—
Fix status
Fixed

No CVSS vector from any source. Fix status from GitHub.

Summary

## Summary attachments: [pocs.zip](https://github.com/user-attachments/files/26431422/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. ## Root cause analysis The relevant flow is: 1. [`gix/src/repository/location.rs`](https://github.com/GitoxideLabs/gitoxide/blob/v0.52.0/gix/src/repository/location.rs) derives the worktree `.gitmodules` path as `workdir/.gitmodules`. 2. [`gix/src/repository/submodule.rs`](https://github.com/GitoxideLabs/gitoxide/blob/v0.52.0/gix/src/repository/submodule.rs) reads that path with `std::fs::read(&path)` and immediately parses the bytes as a submodule configuration file. 3. `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. ## Reproduction steps Use the attached PoC zip that contains the `pocs/` workspace. 1. Unzip the PoC archive. 2. Enter `pocs/F001`. 3. Run: ```bash cargo run --quiet ``` 4. Compare the output with `pocs/F001/result.txt`. Important outputs include: - `gitmodules_symlink=.../victim-repo/.gitmodules` - `symlink_target=.../outside/modules.conf` - `parsed_name=symlinked` - `parsed_path=deps/symlinked` - `parsed_url=https://attacker.example/symlinked.git` These outputs show that gitoxide parsed the submodule configuration from the symlink target outside the repository, not from repository-local bytes. ## Impact Confirmed impact: - out-of-repository bytes can be injected into the result of `Repository::submodules()`; - callers can be misled about submodule metadata such as `name`, `path`, and `url`; - any downstream workflow that uses those values to decide clone, fetch, update, or policy behavior is operating on attacker-controlled data that did not actually originate from the repository tree. This report does **not** claim direct command execution from this code path by itself. The demonstrated impact is metadata injection across the repository boundary. ## Recommended fix A safe fix is to stop silently following symlinks for the worktree `.gitmodules` path in this loading path. Reasonable options include: 1. use `symlink_metadata()` / `lstat`style checks and reject symlinked `.gitmodules` when loading from the worktree; 2. canonicalize the target and verify that it still resides under the repository worktree before reading it; 3. for security-sensitive callers, prefer loading `.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.

Why it matters now

Fix Fixed gitoxide (crates.io): 0.52.1gix (crates.io): 0.83.0

SSVC decision points

Not available: CISA has not published SSVC decision points for this record.

Affected products

VendorProductAffected versionsFixed inStatusSource
—gitoxide (crates.io)before 0.52.10.52.1Patch availableGHSA-pg4w-g64p-qwhj
—gix (crates.io)before 0.83.00.83.0Patch availableGHSA-pg4w-g64p-qwhj
  • gitoxide (crates.io) Vendor— Affected before 0.52.1 Fixed in 0.52.1 Patch available GHSA-pg4w-g64p-qwhj
  • gix (crates.io) Vendor— Affected before 0.83.0 Fixed in 0.83.0 Patch available GHSA-pg4w-g64p-qwhj

Remediation

  • Fix · GHSA-pg4w-g64p-qwhj gitoxide (crates.io) Fixed in 0.52.1 Advisory →
  • Fix · GHSA-pg4w-g64p-qwhj gix (crates.io) Fixed in 0.83.0 Advisory →

Timeline

  1. 2026-10-03 Threatist record created from GHSA-pg4w-g64p-qwhj

References

Sources

This product uses the NVD API but is not endorsed or certified by the NVD. Sources and licences