Repository navigation
fix(deps): update rust crate gix to 0.90 [security] - #1347
Conversation
|
There was a problem hiding this comment.
The security fix (GHSA-q54r-jf2p-rfr2) is worth taking, but this bump doesn't build as-is: one blocking finding inline. Automerge is on, so don't let it go through before CI is green.
There was a problem hiding this comment.
The build break from my last review is fixed in 1277fa1 (thread resolved). One new blocking finding inline: max-control drops gix's object and pack caches, and max-performance is the upstream replacement. Automerge is on and Test/Build are still running, so this could merge as-is once they pass.
There was a problem hiding this comment.
The max-control cache regression is fixed in 2378103: max-performance brings back gix's pack and object caches, and the lockfile adds no new crates, so no C code. No blocking findings left. Test, Build Release Binary and Coverage were still running when I checked.
## [7.33.2] - 2026-10-24 ### Bug Fixes - fix(deps): update rust crate gix to 0.90 [security] (#1347)
This PR contains the following updates:
0.89→0.90gitoxide gix: untrusted .git/config disables filesystem .git-protection path checks via unfiltered protect_options()
GHSA-q54r-jf2p-rfr2
More information
Details
Summary
In the
gixcrate,Cache::protect_options()ingix/src/config/cache/access.rsreads the three filesystem path-protection settings (core.protectHFS,core.protectNTFS, andgitoxide.core.protectWindows) with the unfilteredgix_config::File::boolean()accessor instead of the trust-awareboolean_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/configsections toTrust::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/configon a shared filesystem can setcore.protectNTFS = falseand/orcore.protectHFS = false, disabling the.gitpath-confusion checks that run during checkout, index creation, merge, and tree editing. On case-insensitive filesystems this lets crafted tree entries resolve to the.gitdirectory and overwrite hooks or config.Root Cause
Repositories opened under
Trust::Reducedinstall a section filter (is_trusted) so that security-relevant settings coming from an untrusted repository-local.git/configare ignored. TheCachestruct stores this filter and applies it throughoutgix/src/config/cache/access.rsfor diff drivers, merge drivers, URL rewrites, scheme permissions, lock timeout, and checkout workers.protect_options()is the exception: it reads the path-protection booleans throughself.resolved.boolean(...), which delegates toboolean_filter(key, |_| true)and therefore accepts values from every section regardless of trust. As a result, an untrusted repository-local setting ofcore.protectHFS/core.protectNTFS/gitoxide.core.protectWindowsoverrides the safe defaults, defeating the trust boundary for exactly the controls that are meant to prevent.gitpath-confusion attacks.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/configto disable one or both ofcore.protectHFSandcore.protectNTFS. When the victim opens the repository with agix-based tool, the ownership mismatch yieldsTrust::Reduced, which should neutralize these untrusted settings, butprotect_options()honors them anyway. The attacker also commits tree entries whose names are filesystem-specific encodings of.git(for example NTFS short names such asGIT~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.gitdirectory. 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.gitdirectory 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
urlcopied 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 sameboolean()vsboolean_filter()discrepancy is directly observable in source.Source confirmation that
boolean()performs no trust filtering:Attack Chain
gix-based tool, and the ownership mismatch sets the repository's config sections toTrust::Reduced(gix/src/open/options.rs:215installs theis_trustedsection filter).core.protectNTFS = false(and/orcore.protectHFS = false) into the repository-local.git/config, plus tree entries whose names are filesystem-specific encodings of.git.protect_options()(gix/src/clone/checkout.rs:103,gix/src/repository/index.rs:206,gix/src/repository/merge.rs:153and:214,gix/src/object/tree/editor.rs:24,gix/src/config/cache/access.rs:362).is_trustedfilter (gix/src/config/mod.rs:61) would reject the untrusted repository-local section, butprotect_options()reads viaself.resolved.boolean(...)(gix/src/config/cache/access.rs:306,310,314), which delegates toboolean_filter(key, |_| true)and ignores the filter.gix_validate::path::component()(gix-validate/src/path.rs:98) receivesprotect_hfs=falseand/orprotect_ntfs=false, so the HFS+/NTFS.git-confusion checks are skipped. When only one protection is disabled, the combined-case fallback atgix-validate/src/path.rs:137does not run, and the remaining single-filesystem check does not cover the other filesystem's encodings..gitpass validation and are written to disk; on a case-insensitive filesystem they resolve into the real.gitdirectory, overwriting hooks and configuration and plausibly yielding code execution in the victim's context.Bypass Evidence
The protection under test is the reduced-trust section filter that is supposed to stop untrusted repository-local config from altering security settings. The strongest counter-argument is the case-insensitive
.gitfallback ingix-validate/src/path.rs:137, which rejects a plain.gitmatch when both HFS+ and NTFS protections are off. That fallback is insufficient for two reasons verified in source: it only runs when BOTHprotect_hfsandprotect_ntfsare false (!(protect_hfs | protect_ntfs)at line 137), so disabling a single protection leaves it inactive; and even when it runs, its exacteq_ignore_ascii_case(b".git")test does not cover filesystem-specific encodings such as NTFS short names (GIT~1), NTFS trailing-dot/space or alternate-data-stream forms, or HFS+ ignorable-code-point variants, which are precisely what the dedicatedis_dot_hfs/is_dot_git_ntfschecks (now skipped) exist to catch. Theboolean()accessor was confirmed to apply no trust filtering (gix-config/src/file/access/comfort.rs:89), and the sibling reads in the same file consistently applyfilter_config_section, so the unfiltered read is a genuine deviation rather than an intentional exemption. This issue is distinct from the previously disclosed submodule command-execution bypass (CVE-2026-40034 / GHSA-f26g-jm89-4g65): that advisory's fix changed only thegix-submodulecrate andgix/src/repository/submodule.rsand never touchedprotect_options(), so this code path and its remediation boundary are not covered by it.Affected Versions
rustgix(crates.io)>= 0.88.0, <= 0.89.00.89.0(crates.io)0.90.0protect_options()was introduced ingix0.88.0 and did not exist in earlier releases; before then the path-validation options were not configurable from repository config in this way. The unfiltered read is present from introduction through the latest published release (0.89.0).Suggested Fix
Read the path-protection settings through the same trust-aware accessor used by the other security-critical reads in
gix/src/config/cache/access.rs: replace eachself.resolved.boolean(...)call inprotect_options()withself.resolved.boolean_filter(..., &mut self.filter_config_section.clone())so that untrusted repository-local sections are excluded underTrust::Reducedand the safe defaults are retained. The fix must apply to all three keys (core.protectHFS,core.protectNTFS,gitoxide.core.protectWindows) so that no single protection can be disabled from untrusted config. As an interim mitigation, operators can avoid opening untrusted or other-user-owned repositories withgix-based tools on shared or case-insensitive filesystems; this does not remove the underlying bypass.Reported by zx.
Severity
CVSS:3.1/AV:L/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:HReferences
This data is provided by OSV and the GitHub Advisory Database (CC-BY 4.0).
Release Notes
GitoxideLabs/gitoxide (gix)
v0.90.0: gix v0.90.0Compare Source
New Features (BREAKING)
add worktree locking and a lock override that preserves changes
Expose
Worktree::set_lock(Option<&BStr>)andworktree::Proxy::set_lock()so callers can protect linked worktrees from pruning, moving, or removal,
including when their checkout is offline. Implement file handling in
gix_worktree::set_lock(): preserve byte-oriented reasons, append Git'strailing newline when needed, and make unlocking idempotent. Reject the
main worktree in the porcelain and avoid writing through lock-file symlinks.
Previously
Force::OverrideLockalso discarded local changes and initializedsubmodules. Add
Force::OverrideLockKeepChangesto bypass just the lock check,retaining status and submodule validation. This expands an exhaustive public
enum, so adapt the Tix caller in the same commit; existing CLI force levels
retain their behavior.
Git reference:
v2.56.0-116-g6de20f6092(
6de20f6092dcf9bdb1c8efe03db4b70c82b423dd),builtin/worktree.clock/unlock/removal,
wrapper.cnewline encoding, andt/t2403-worktree-move.sh. Git'sworktree remove -ffbypasses both lock andcleanliness checks; the new mode deliberately permits bypassing only the lock.
exposes a pre-existing unresolved
with_fetch_optionslink in clone docs.Test
one-pass deletion leaves files while default retries finish.
Performance
Reverted (BREAKING)
max-performance-safefeaturegixalways useszlib-rs, so the safe bundle no longer differs frommax-performance. Makemax-performanceown the default cache setup andupdate defaults, cache tests, and developer commands. Downstream users of
the removed name must select
max-performanceinstead.New Features
add
Submodule::resolved_url()for cloningDefinitely went through the tests quickly, besides the usual
'make it a shell script', but poked the URL resolution and Windows
special cases.
Some of the newly added drive-prefix tests are truly from another world,
and I don't think I could ever have written those, for better or worse.
Relative submodule URLs in
.gitmodulesrefer to the superproject'supstream, even when that upstream uses
ssh://,git://, or SCP syntax.Passing the existing
Submodule::url()result directly to a clone treats./and../entries as local paths instead.Document
url()as the raw configured accessor and addresolved_url()for cloning or fetching. Port Git's byte-oriented
relative_url()rulesfrom
remote.c, select the current branch's remote or the default remote,and fall back to the superproject itself when the selected URL is missing.
Make local paths absolute so the result does not depend on the caller's
working directory. Honor configuration overrides as initialized URLs,
avoiding a second remote-relative resolution of local URLs written by
git submodule init.Validate transport and local URLs, traversal errors, default-remote
selection, initialized overrides, unborn and bare repositories, and
non-UTF-8 local paths in disposable repositories. Compare resolution with
git submodule init; include native-separator coverage for Windows.expose selected global attribute paths
Allow callers that retain detached diff results to track the attribute files
selected by the repository. Reuse the existing resolver so permissions,
configuration interpolation, fallback paths, and precedence stay consistent
with the attribute stack.
Return selected paths even when files are missing so their later creation can
invalidate cached results. Per-directory attributes and
info/attributesremain separate because their locations are already known to the caller.
provide safe interrupt setup and handle
SIGHUPApplications that only need the shared cancellation flag should not have to
supply an unsafe signal callback. Add
interrupt::init_handler_default()as asafe entry point while preserving the callback and grace policy of an existing
registration, including when a UI runs inside the
gixcommand.Treat Unix
SIGHUPlike the other termination signals so long-running work canobserve cancellation and callers can restore terminal state before exiting.
It previously bypassed the shared flag and terminated immediately. Repeated
signals retain the existing forced-termination policy.
Exercise all supported termination signals and nested safe registration in
isolated subprocesses. Four interrupt tests pass with the feature enabled and
two pass without it.
add
Remote::default_branch()to learn the default branch of a remote without connecting to it.git cloneandgit remote set-headrecord the default branch of a remote in the symbolic referencerefs/remotes/<name>/HEAD, which points to the remote-tracking branch of that branch, likerefs/remotes/origin/main. Since Git 2.48,git fetchalso creates it if it's missing.make repository paths absolute without reloading
Went through the tests quite quickly, thinking their are deep and thorough
as usual.
Relative repository paths depend on the process current directory. Changing it
after opening a repository can redirect checkout validation, Git-link handling,
HEAD and object reads, or index persistence away from the intended repository.
Add
Repository::make_paths_absolute()to anchor the selected Git directory,common directory, worktree, index, and object database to the CWD captured at
opening. Unlike
reload(), this keeps runtime configuration, reference namespaces,and in-memory objects. Join paths lexically so missing worktrees remain supported
and destination symlinks are still rejected. Leave existing clones unchanged,
avoid partial updates on failure, and make repeated calls a no-op.
Recreate relative reference and object stores through their existing APIs.
Reset caches carrying relative index paths or store-specific pack IDs while
preserving outstanding snapshots.
Use an anchored repository view for initial checkout, and anchor global attribute
paths before optional-file metadata checks. Document the interpolation helper
and its captured-directory behavior.
Cover CWD changes, main and linked worktrees, bare repositories, missing checkouts,
index overrides, retained runtime state, idempotence, and failed reconstruction.
Include a Windows-only test for rejecting unresolved drive-relative index paths
without partially updating the repository.
separate linked worktree registration from checkout
Skipped over the tests, knowing they are very detailed
can be trusted by now.
Add
Repository::prepare_add_worktree()with consuming checkout and explicitpersistence. Persisted repositories can run their initial checkout later, even
after their checkout directory is removed, while failed unpersisted checkouts
roll back their owned data.
Bug Fixes
create SHA-256 repositories for
object_hash: Nonein SHA-256-only buildsPreviously a SHA-1 repository was created that the same build couldn't open.
Now
Nonefalls back to the build's default object format.respect section trust for filesystem path protections
GHSA-q54r-jf2p-rfr2 reports that path-protection configuration did not
respect the section filter used for security-relevant settings. Use the
filter for
core.protectHFS,core.protectNTFS, andgitoxide.core.protectWindows, including the latter's early read for thereference store. Keep platform defaults and boolean error handling intact.
Read-only fixtures exercise local and worktree configuration, full and
reduced trust, custom filters, trusted API overrides, and malformed
values in strict and lenient mode. Before the fix, reduced-trust settings
replaced the defaults and a rejected malformed Windows setting prevented
opening. Validation also exercises representative path names.
Git baseline: the local Git reference identifies itself as
v2.56.GIT.Its
t/t1014-read-tree-confusing.shandread-cache.c:verify_path_internalcover filesystem-specific path protections. Git's ownership policy in
setup.c:ensure_valid_ownershiprejects dubious ownership, whereasgixsupports reduced trust by filtering configuration.
match Git clone reference handling and preserve offline defaults
The prior CI run failed
check-modebecausemake_clone_refs.shhad ashebang without its executable bit. Its ARM32 tests also ran Git 2.47.3,
which rejects
clone --revision; the matrix mistook that unsupportedoption for a clone failure and disagreed with successful
gixclones.Mark the fixture executable and detect revision support from
git clone -h.Skip only revision baselines on older Git, retaining all 72 other cases
and the independent
gixrevision tests. Modern Git still exercises all81 baseline cases. Capture help despite its expected nonzero exit status.
match Git clone reference handling and preserve offline defaults
gixclones recorded remoteHEADas a direct object ID, losing thebranch relationship needed by later fetches and
Remote::default_branch().Bare clones also used the ordinary remote-tracking layout, while detached
remote heads and custom destinations selected different references from
git clone.Match Git's clone orchestration and reference selection:
refs/heads/*, without a default persistedfetch refspec, tracking
HEAD, or branch upstream configuration. Keepexplicit refspecs persistent and use
WithWorktreeplusfetch_only()for ordinary clones without a checkout.
detached
HEAD, preferringinit.defaultBranch, thenmaster, then thefirst matching branch by name. Record the selected branch's first mapped
peer before updating local
HEAD, including overwriting a branch namedHEAD; omit trackingHEADwhen the selected target is excluded.HEAD, and align shallow selection, tag policy, initial packed-referencereflogs, and persisted revision configuration with Git. Preserve the
reference directory required for repository discovery.
Preserve fetch-source identity when deriving an offline default branch.
Recognize
refs/heads/sources and exactheads/<name>sources, includingan exact source whose mapping was deduplicated against a glob. Return
Nonefor sources whose branch cannot be identified offline, count allambiguous mappings, and share reverse mapping with upstream-branch lookup.
Consult Git's
builtin/clone.candremote.c, and add deterministicGit-produced baselines for 27 cases in normal, no-checkout, and bare modes.
Compare complete references, relevant configuration, reflog IDs, checkout
contents, shallow boundaries, and success or failure with installed Git.
Keep source fixtures read-only and read their existing refs through
gix.Update API docs, CLI help and update printing, and affected clone/fetch tests.
Known difference: replacing a direct branch named
HEADwith the trackingsymref logs a null old object ID in
gix-ref; Git logs the resolved target.The baseline test explicitly checks this exception. Replacement-only custom
refspecs remain a
gixextension rather than being compared with Git'ssupplementary clone mappings.
resolve linked worktree submodules in their private Git directory
Git stores each linked superproject's submodule repositories under
.git/worktrees/<id>/modules/<name>, separately from the main worktree's.git/modules/<name>repositories. Usingcommon_dir()forSubmodule::git_dir()could open the main worktree's submodule beforeinitialization or after deinitialization of the linked submodule.
Derive the path from the superproject's
git_dir()instead. Preserve namevalidation and the precedence of an existing submodule
.gitfile.Cover initial checkout, initialization, and deinitialization using Git to
populate a disposable linked worktree created by
gix. Check that the mainsubmodule remains populated and that linked repository state and opening
follow its own lifecycle.
honor replacement refs and bypass stale commit graphs
Enable standard replacement refs by default and honor
core.useReplaceRefswith the same polarity as Git. The presence of
GIT_NO_REPLACE_OBJECTSdisables them regardless of its value.
Commit graphs contain original parent links, so skip them while object
replacements are active, including when a caller forces or supplies a
cache. Per-handle raw reads can still use the graph. Document the corrected
opening and graph behavior.
The regression first failed on the original code, then passed for ordinary
open/discovery in bare and non-bare repositories, reopening after a graft,
configuration and environment disablement, explicit raw reads, supplied
and forced graphs, merge-base, and graph removal. All 503
gixintegrationtests passed; the regression also passed with only
revision,sha1enabled.Native rebuilt
gix logandtix showmatch Git across two grafts in bothrepository kinds with pre-existing raw graphs.
Commit Statistics
Commit Details
view details
b8ef9ea)6260a55)Commit::short_id()doctest (75ca154)object_hash: Nonein SHA-256-only builds (521b3b2)36aaf51)7a22948)0a49d46)779b6c0)86d5fa6)38beb4d)7439578)8c513c5)Submodule::resolved_url()for cloning (41cb46f)a6f95af)SIGHUP(840faa0)7540f69)ad0202e)1cc509b)aa7eaea)Remote::default_branch()to learn the default branch of a remote without connecting to it. (2ddcae4)eae2fef)31185ff)79f3880)862e9c3)ca9eb08)2d8dcc4)core.longpathswhen initializing linked-submodule fixtures (7edbf02)6d1b686)54234a6)2711f6a)1b6d8da)max-performance-safefeature (7467af0)b94cd79)32a27be)e3b7670)5fccccf)Configuration
📅 Schedule: (UTC)
🚦 Automerge: Enabled.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
👻 Immortal: This PR will be recreated if closed unmerged. Get config help if that's undesired.
This PR has been generated by Mend Renovate CLI.