KEVWire

Security intelligence, with context.

Response snapshot Oct 27, 2026, 01:13 UTC

Back to the wire
Permalink
VulnerabilityGHSA-q54r-jf2p-rfr2

gitoxide gix: untrusted .git/config disables filesystem .git-protection path checks via unfiltered protect_options()

In the `gix` crate, `Cache::protect_options()` in `gix/src/config/cache/access.rs` reads the three filesystem path-protection settings (`core.protectHFS`, `core.protectNTFS`, and `gitoxide.core.

At a glance

Exploited
Not known
Severity
Not scored yet
Chance of exploitation
Not estimated
Sources
1 source

What to do

GitHub lists first patched versions: gix: 0.90.0. Confirm the affected range and upgrade guidance in the exact advisory.

Read GitHub-reviewed advisory GHSA-q54r-jf2p-rfr2 (opens a new tab)

What happened

## Summary

In the `gix` crate, `Cache::protect_options()` in `gix/src/config/cache/access.rs` reads the three filesystem path-protection settings (`core.protectHFS`, `core.protectNTFS`, and `gitoxide.core.protectWindows`) with the unfiltered `gix_config::File::boolean()` accessor instead of the trust-aware `boolean_filter()` accessor used by every other security-critical config read in the same file.

When a repository is opened with reduced trust (an ownership mismatch, which maps `.git/config` sections to `Trust::Reduced`), these protection settings should not be honored from the untrusted repository-local config. Because the trust filter is skipped, an attacker who controls a repository's `.git/config` on a shared filesystem can set `core.protectNTFS = false` and/or `core.protectHFS = false`, disabling the `.

git` path-confusion checks that run during checkout, index creation, merge, and tree editing. On case-insensitive filesystems this lets crafted tree entries resolve to the `.git` directory and overwrite hooks or config.

Continue reading

## Root Cause

Repositories opened under `Trust::Reduced` install a section filter (`is_trusted`) so that security-relevant settings coming from an untrusted repository-local `.git/config` are ignored. The `Cache` struct stores this filter and applies it throughout `gix/src/config/cache/access.rs` for diff drivers, merge drivers, URL rewrites, scheme permissions, lock timeout, and checkout workers.

`protect_options()` is the exception: it reads the path-protection booleans through `self.resolved.boolean(...)`, which delegates to `boolean_filter(key, |_| true)` and therefore accepts values from every section regardless of trust. As a result, an untrusted repository-local setting of `core.protectHFS`/`core.protectNTFS`/`gitoxide.core.

protectWindows` overrides the safe defaults, defeating the trust boundary for exactly the controls that are meant to prevent `.git` path-confusion attacks.

```text gix/src/config/cache/access.rs:300 Cache::protect_options() : vulnerable function gix/src/config/cache/access.rs:306 self.resolved.boolean(gitoxide.core.protectWindows) (unfiltered) gix/src/config/cache/access.rs:310 self.resolved.boolean(core.protectHFS) (unfiltered) gix/src/config/cache/access.rs:314 self.resolved.boolean(core.protectNTFS) (unfiltered) gix-config/src/file/access/comfort.

rs:89 boolean() delegates to boolean_filter(key, |_| true) gix/src/config/mod.rs:61 is_trusted() : the filter that would reject untrusted repo-local sections gix-validate/src/path.rs:98 component() : path validation that honors the resulting options ```

## Impact

An attacker who owns a repository on a filesystem shared with the victim (a multi-user development host, a shared or network-mounted checkout, or a CI workspace reused across jobs) edits that repository's `.git/config` to disable one or both of `core.protectHFS` and `core.protectNTFS`.

When the victim opens the repository with a `gix`-based tool, the ownership mismatch yields `Trust::Reduced`, which should neutralize these untrusted settings, but `protect_options()` honors them anyway. The attacker also commits tree entries whose names are filesystem-specific encodings of `.

git` (for example NTFS short names such as `GIT~1`, NTFS alternate-data-stream or trailing-dot/space variants, or HFS+ forms using ignorable code points). When the victim performs a checkout, index build, merge, or tree edit, path validation no longer rejects these names, so on a case-insensitive filesystem (Windows NTFS, macOS HFS+) they collapse to the real `.git` directory.

This permits overwriting repository hooks and configuration, which plausibly leads to arbitrary command execution in the victim's context on the next Git operation that runs a hook. The confidentiality, integrity, and availability impact follows from control over the victim's `.

git` directory and any resulting hook execution; hook execution was not freshly executed here and is characterized as the plausible consequence of the demonstrated path-protection bypass. ## Proof of Concept

The following unit test places the config cache in the exact state produced after a submodule or repository is initialized (only `url` copied to the local config) and shows that the attacker's path-protection override from the repository-local config is honored even though it should be filtered under reduced trust. The same `boolean()` vs `boolean_filter()` discrepancy is directly observable in source.

```rust // Demonstrates that core.protectNTFS from an untrusted repository-local // section is honored by the unfiltered read in protect_options(). use std::str::FromStr;

// Trusted (system/global) config: protection on by default. // Untrusted repository-local config (.git/config) disables NTFS protection. let repo_local = gix_config::File::from_str("[core]\n protectNTFS = false").unwrap();

// Reading core.protectNTFS with the trust-aware filter would reject the // repository-local section; reading it unfiltered (as protect_options does) // returns Some(false), disabling the .git-confusion check. let filtered = repo_local.boolean_filter("core.protectNTFS", |_| false); // trusted-only let unfiltered = repo_local.boolean("core.protectNTFS"); // what protect_options uses eprintln!

("filtered (trusted only) = {:?}", filtered); // None -> safe default kept eprintln!("unfiltered (vulnerable) = {:?}", unfiltered); // Some(Ok(false)) -> protection disabled ```

```text filtered (trusted only) = None unfiltered (vulnerable) = Some(Ok(false)) ```

Source confirmation that `boolean()` performs no trust filtering:

```rust // gix-config/src/file/access/comfort.rs:89 pub fn boolean(&self, key: impl AsKey) -> Result > { self.boolean_filter(key, |_| true) } ```

## Attack Chain

1. **Exposure:** The attacker owns a repository on a filesystem shared with the victim; the victim opens it with a `gix`-based tool, and the ownership mismatch sets the repository's config sections to `Trust::Reduced` (`gix/src/open/options.rs:215

Affected package ranges reported by GitHub: gix: >= 0.88.0, <= 0.89.0

Related technology

Sources

  1. Primary source

    GitHub-reviewed advisory GHSA-q54r-jf2p-rfr2 (opens a new tab)

    The public advisory supplies the description, affected package/version ranges, and any listed CVSS metric. GitHub review is not local review.

    Published Checked

  2. Primary source

    GitHub public advisory API page (opens a new tab)

    The API page supplied GHSA-q54r-jf2p-rfr2, its actual identifiers, source dates, and structured metrics.

    Published Date not suppliedChecked

Cite this record

For factual claims, cite the primary sources above; cite KEVWire for its compilation, dates, and classifications.

APA

KEVWire. (2026, October 23). gitoxide gix: untrusted .git/config disables filesystem .git-protection path checks via unfiltered protect_options(). https://kevwire.com/record/GHSA-q54r-jf2p-rfr2

MLA

"gitoxide gix: untrusted .git/config disables filesystem .git-protection path checks via unfiltered protect_options()." KEVWire, 23 Oct. 2026, kevwire.com/record/GHSA-q54r-jf2p-rfr2.

Chicago

KEVWire. "gitoxide gix: untrusted .git/config disables filesystem .git-protection path checks via unfiltered protect_options()." Last modified October 23, 2026. https://kevwire.com/record/GHSA-q54r-jf2p-rfr2.

Evidence summary (plain text)

gitoxide gix: untrusted .git/config disables filesystem .git-protection path checks via unfiltered protect_options() (Oct 23, 2026).
In the `gix` crate, `Cache::protect_options()` in `gix/src/config/cache/access.rs` reads the three filesystem path-protection settings (`core.protectHFS`, `core.protectNTFS`, and `gitoxide.core.protectWindows`) with the unfiltered `gix_config::File::boolean()` accessor instead of the trust-aware `boolean_filter()` accessor used by every other security-critical config read in the same file. When a repository is opened with reduced trust (an ownership mismatch, which maps `.git/config` sections to `Trust::Reduced`), these protection settings should not be honored from the untrusted repository-local config. Because the trust filter is skipped, an attacker who controls a repository's `.git/config` on a shared filesystem can set `core.protectNTFS = false` and/or `core.protectHFS = false`, disabling the `.git` path-confusion checks that run during checkout, index creation, merge, and tree editing. On case-insensitive filesystems this lets crafted tree entries resolve to the `.git` directory and overwrite hooks or config.

Repositories opened under `Trust::Reduced` install a section filter (`is_trusted`) so that security-relevant settings coming from an untrusted repository-local `.git/config` are ignored. The `Cache` struct stores this filter and applies it throughout `gix/src/config/cache/access.rs` for diff drivers, merge drivers, URL rewrites, scheme permissions, lock timeout, and checkout workers. `protect_options()` is the exception: it reads the path-protection booleans through `self.resolved.boolean(...)`, which delegates to `boolean_filter(key, |_| true)` and therefore accepts values from every section regardless of trust. As a result, an untrusted repository-local setting of `core.protectHFS`/`core.protectNTFS`/`gitoxide.core.protectWindows` overrides the safe defaults, defeating the trust boundary for exactly the controls that are meant to prevent `.git` path-confusion attacks.
Sources:
GitHub-reviewed advisory GHSA-q54r-jf2p-rfr2: https://github.com/advisories/GHSA-q54r-jf2p-rfr2
GitHub public advisory API page: https://api.github.com/advisories?type=reviewed&per_page=100&sort=updated&direction=desc
Record ID: GHSA-q54r-jf2p-rfr2.
Record origin: Ingested record.
AI evidence: Not assessed.
Known exploitation: Not assessed.
Zero-day status: Not assessed. KEV membership alone does not establish zero-day status.
About this data

This record was ingested from a data source. Retrieved: Oct 26, 2026, 20:55 UTC. Ingestion is not an independent editorial investigation.

A CISA KEV listing documents known exploitation, not necessarily a zero-day; absence from the catalog does not prove a vulnerability has never been exploited.

Technology tags indicate possible relevance, not verified exposure. Confirm the affected product, version, and configuration against your own inventory; KEVWire does not scan your systems.

A personal reading checklist on this device, not verified remediation.

Record JSON

Your account

Email alerts

Email alerts are not available yet. To follow your stack today, use its RSS feed, which honors your filters, or connect an AI agent through the API.

Suggest an improvement

Ideas, missing sources, corrections, and bugs are all welcome. No account needed. Suggestions are reviewed by a person before anything changes.

Record details

Record detail