Proof/CVEs & advisories/CVE-2026-77355
The name was checked. NTFS disagreed.
isomorphic-git already rejected .git, case variants, trailing-dot forms and short-name aliases. It accepted .git::$INDEX_ALLOCATION — the one spelling that NTFS resolves back to the real gitdir.
.git::$INDEX_ALLOCATION.git and git~1 rejectedA Git tree entry is untrusted input. During clone or checkout, isomorphic-git parses the tree and writes the corresponding files and directories into the working copy. The active .git directory must stay unreachable from any repository-supplied pathname.
No attacker-controlled tree path may resolve to the active Git metadata directory after platform-specific filesystem namespace transformations.
NTFS supports alternate data streams and stream types. For a directory, ::$INDEX_ALLOCATION identifies the directory’s index-allocation stream — which means a pathname like .git::$INDEX_ALLOCATION can alias the actual .git directory rather than naming an ordinary sibling.
Version 1.38.6 normalized the whole path component before comparing it against the reserved-name list:
const hfsClean = path.replace(
/[\u200C-\u200F\u202A-\u202E\u206A-\u206F\uFEFF]/g,
''
)
const normalized = hfsClean.toLowerCase().replace(/[. ]+$/, '')For .git::$INDEX_ALLOCATION, the normalized value still carries the colon suffix. It is not equal to .git, so it passes.
The parser behaviour makes an unusually clean control. Exact 1.38.6 rejects literal .git, a case/trailing-space form, and git~1 — while accepting the stream alias. This is not an absence of validation; it is a platform-specific namespace form missing from the equivalence model.
Attacker controls the object stream
A crafted tree carries .git::$INDEX_ALLOCATION path components. The clearest threat model is an untrusted or permissive self-hosted remote; some hosted services reject the malicious object first.
Victim clones on Windows NTFS
The working directory must be on NTFS for the alias to mean anything.
String-level validation accepts the path
The normalized name is not equal to .git, so reserved-name filtering lets it through.
NTFS resolves it into the real gitdir
Repository-controlled content is written over trusted Git metadata, including configuration and hook-area content.
A later native Git run consumes it
The published follow-on places core.fsmonitor configuration; a subsequent native git status executes the marker.
Ordinary reserved names
Exact 1.38.6 rejects .git, case and trailing-space variants, and git~1. Validation is active.
NTFS stream alias
Exact 1.38.6 accepts the alias; content lands inside the real gitdir after loopback Smart-HTTP clone.
1.38.7
The alias is rejected. Benign colon-containing names that do not resolve to the gitdir keep working.
The validator made a security decision on the lexical pathname before accounting for the destination filesystem’s equivalence rules. NTFS could assign a string that did not compare equal to .git the meaning of the .git directory itself.
The security question is not “does this string equal .git?” It is “after the target filesystem interprets this name, can the destination resolve inside the protected metadata directory?”The negative controls are what made the claim precise.
An accepted path is only interesting if the surrounding filter is provably working. Four controls separated a namespace gap from a general absence of validation, and separated the library’s write primitive from the consumer’s execution.
Source mapping
Locate the reserved-name filter and the exact point where a tree path becomes a filesystem write.
Platform hypothesis
Ask what the destination filesystem resolves, not what the string looks like.
Differential controls
Prove 1.38.6 rejects ordinary reserved names while accepting the alias; prove Linux does not alias at all.
Skeptic gate
Keep the isomorphic-git write and the native-Git execution as two separate claims.
Promotion and disclosure
Confirm the threat model, version boundaries and public-safety limits before reporting.
Version 1.38.7 treats the first colon as the boundary between the NTFS filename and the stream declaration, then applies the existing reserved-name checks:
const ntfsClean = hfsClean.split(':')[0]
const normalized = ntfsClean.toLowerCase().replace(/[. ]+$/, '')The fixing commit is e5dbec689fab148fd5b518f3d4958c9d728886f9. The accompanying regression tests reject .git::$INDEX_ALLOCATION, other .git stream forms, case and trailing-dot/space variants, and git~1 stream forms, while retaining benign colon-containing names.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:HUser interaction reflects the later consumer step in the demonstrated chain rather than a second independent vulnerability.
| Scope | Range | Basis |
|---|---|---|
| Public advisory | ≤ 1.38.6 | publisher’s stated range |
| Directly reviewed vulnerable | 1.38.5, 1.38.6 | source review of exact npm artifacts |
| Fixed | 1.38.7 | source review plus runtime control |
Earlier releases were not individually sampled, so ≤1.38.6 remains the publisher’s range rather than a claim that every historical release was independently executed. Commit 823843d1cd19182df79bc907c767f5bcecb39a0b added incomplete reserved-name hardening in 1.38.6, but it did not introduce the unsafe acceptance — 1.38.5 already lacked an equivalent protection. The exact introduction point was not established.
Upgrade to isomorphic-git 1.38.7 or later. More generally, code that writes attacker-controlled paths should perform platform-aware containment checks at the final filesystem boundary rather than rely on a growing list of forbidden spellings.
Regression coverage should include:
.git::$INDEX_ALLOCATIONand other.gitstream declarations;.gitcase variants and trailing dot/space variants;git~1and its stream variants;- benign non-reserved colon names, which must keep working;
- clone and checkout assertions that the real gitdir is never modified by repository content.
- GHSA-6fxm-h49m-4fg3 — isomorphic-git advisory and public PoC
- CVE-2026-77355 record
- Fixing commit — NTFS filename/stream split before reserved-name checks
Credit: Discovered and reported by Charles Vosburgh. Research was AI-assisted through source mapping, hypothesis generation, release comparison and evidence organization; final validation, disclosure coordination, severity calibration and publication review remained human.