Vulnerability GHSA-9qw7-j9xw-fv9c

High Risk
HIGH RISK
CVSS Score: 8.8
Score Range: 7.0–8.9
High severity vulnerabilities (CVSS 7.0–8.9). Serious vulnerabilities that should be prioritized soon after critical fixes.
8 hours ago
November 09, 2026 at 07:53 PM UTC
isomorphic-git: Arbitrary file write outside of working tree via directory symlink during checkout (potential RCE)
<1.38.6
<1.38.6

Summary

isomorphic-git: Arbitrary file write outside of working tree via directory symlink during checkout (potential RCE)

Details

Summary

git.checkout writes working-tree files through a directory symlink without checking the leading path, so a malicious repository can write outside the intended working directory. The defect is in isomorphic-git's checkout logic itself and is therefore independent of the fs provider. The impact depends on the configured filesystem; worst case is arbitrary file write on the host, e.g. injecting an attacker key into ~/.ssh/authorized_keys → remote login / code execution.

Details

A single git tree can't hold a name as both a symlink and a directory, so the attack uses two states:

Commit N:    config            (mode 120000, symlink → ../.ssh)
Commit N+1:  config/           (mode 040000, tree)
               authorized_keys (mode 100644, attacker public key)

Checking out commit N writes config as a directory symlink. Checking out commit N+1 sees config/authorized_keys as a new path: isomorphic-git mkdirs the parent (config, already a symlink → EEXIST, ignored) then writeFiles the file, which the OS resolves through the symlink to outside the working directory. A file-level symlink swap is caught (the 120000→100644 mode change triggers an rm), but the directory-level variant is not — the payload is always a new child path, a plain create, so the parent symlink is never removed.

The symlink target is attacker-chosen. Besides ~/.ssh, pointing it at the repo's own control directory — hooks → .git/hooks then hooks/pre-commit, or x → .git then overwriting .git/config (core.hooksPath / alias / core.fsmonitor) — yields RCE on the next native git command without depending on ~/.ssh existing, since .git/ is always present in a fresh clone.

Canonical git behavior (divergence)

git refuses to do this: has_symlink_leading_path() (symlinks.c) checks each leading path component during checkout and will not follow a symlink — the hardening behind CVE-2021-21300 and CVE-2024-32002. isomorphic-git has no equivalent check.

PoC

https://github.com/spiceoverflow/av1-symlink-hook-rce — open in a Codespace; it builds the malicious repo, serves it locally, and runs a victim. Output on Linux:

[AV-1] Directory symlink on disk: …/config → ../.ssh  (→ /home/node/.ssh)
[AV-1] *** SSH KEY INJECTION CONFIRMED ***

Trigger: minimal realistic path is clone (commit N) + a later pull (commit N+1) — ordinary sync of an already-trusted repo. It also collapses to a single clone when the consuming app reads or writes into the cloned tree, or when a symlink already exists on disk.

Impact

isomorphic-git is filesystem-agnostic, so exploitability and blast radius depend on the active fs provider — specifically whether it creates a symlink and follows it on a later write:

  • Node fs on Linux/macOS (servers, CI, CLIs, Electron/desktop tooling — the common server-side use): real OS symlinks → the write lands anywhere the process can reach → arbitrary file write on the host → RCE (the PoC above). Highest impact.
  • Node fs on Windows: fs.symlink typically requires privilege and throws EPERM, so the symlink-plant step usually fails — not exploitable by default. (A property of the OS/provider, not of isomorphic-git.)
  • In-memory / browser providers (memfs, ZenFS, lightning-fs, custom fs): there is no host filesystem to escape to, so the worst case is writing outside the intended clone directory but within that virtual filesystem — i.e. corrupting other repos/app data the same provider holds. Bounded by the sandbox, and only if the provider implements symlink creation + following.

The CVSS above reflects the worst-case (Node fs on a POSIX host). Browser/in-memory-only deployments are lower severity (sandboxed integrity impact).

Suggested fix

Before each working-tree write, lstat every leading path component and refuse to traverse a symlink (mirrors git's has_symlink_leading_path()); optionally honor core.symlinks=false.

Impacted packages

Timeline

Published
8 hours ago
November 09, 2026 at 07:53 PM UTC
Fixed (1.38.6)
4 months ago
June 30, 2026 at 11:09 PM UTC
Last Modified
8 hours ago
November 09, 2026 at 08:00 PM UTC