{"id":"GHSA-pg4w-g64p-qwhj","summary":"gix and gitoxide's symlinked .gitmodules are followed and parsed from outside of the repository","details":"## Summary\nattachments:\n[pocs.zip](https://github.com/user-attachments/files/26431422/pocs.zip)\n\n\nWhen `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.\n\nThis 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.\n\n## Root cause analysis\n\nThe relevant flow is:\n\n1. [`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`.\n2. [`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.\n3. `Repository::submodules()` exposes the parsed entries through the high-level API.\n\nThe 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.\n\nBecause `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.\n\n## Reproduction steps\n\nUse the attached PoC zip that contains the `pocs/` workspace.\n\n1. Unzip the PoC archive.\n2. Enter `pocs/F001`.\n3. Run:\n    \n    ```bash\n    cargo run --quiet\n    ```\n    \n4. Compare the output with `pocs/F001/result.txt`.\n\nImportant outputs include:\n\n- `gitmodules_symlink=.../victim-repo/.gitmodules`\n- `symlink_target=.../outside/modules.conf`\n- `parsed_name=symlinked`\n- `parsed_path=deps/symlinked`\n- `parsed_url=https://attacker.example/symlinked.git`\n\nThese outputs show that gitoxide parsed the submodule configuration from the symlink target outside the repository, not from repository-local bytes.\n\n## Impact\n\nConfirmed impact:\n\n- out-of-repository bytes can be injected into the result of `Repository::submodules()`;\n- callers can be misled about submodule metadata such as `name`, `path`, and `url`;\n- 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.\n\nThis report does **not** claim direct command execution from this code path by itself. The demonstrated impact is metadata injection across the repository boundary.\n\n## Recommended fix\n\nA safe fix is to stop silently following symlinks for the worktree `.gitmodules` path in this loading path.\n\nReasonable options include:\n\n1. use `symlink_metadata()` / `lstat`style checks and reject symlinked `.gitmodules` when loading from the worktree;\n2. canonicalize the target and verify that it still resides under the repository worktree before reading it;\n3. for security-sensitive callers, prefer loading `.gitmodules` from the index or `HEAD` tree rather than following the worktree path.\n\nAt minimum, the worktree path should not silently follow symlinks to arbitrary external files.","aliases":["CVE-2026-82252"],"modified":"2026-09-10T03:51:06.546736627Z","published":"2026-05-05T19:26:09Z","database_specific":{"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-05-05T19:26:09Z","nvd_published_at":null,"cwe_ids":["CWE-22"]},"references":[{"type":"WEB","url":"https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-pg4w-g64p-qwhj"},{"type":"PACKAGE","url":"https://github.com/GitoxideLabs/gitoxide"}],"affected":[{"package":{"name":"gitoxide","ecosystem":"crates.io","purl":"pkg:cargo/gitoxide"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.52.1"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 0.52.0","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-pg4w-g64p-qwhj/GHSA-pg4w-g64p-qwhj.json"}},{"package":{"name":"gix","ecosystem":"crates.io","purl":"pkg:cargo/gix"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.83.0"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/05/GHSA-pg4w-g64p-qwhj/GHSA-pg4w-g64p-qwhj.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P"}]}