GHSA-9qw7-j9xw-fv9c

Suggest an improvement
Source
https://github.com/advisories/GHSA-9qw7-j9xw-fv9c
Import Source
https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/11/GHSA-9qw7-j9xw-fv9c/GHSA-9qw7-j9xw-fv9c.json
JSON Data
https://api.osv.dev/v1/vulns/GHSA-9qw7-j9xw-fv9c
Aliases
Published
2026-11-09T19:53:44Z
Modified
2026-11-09T20:00:04Z
Severity
  • 8.8 (High) CVSS_V3 - CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H CVSS Calculator
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.

Database specific
{
    "cwe_ids": [
        "CWE-22",
        "CWE-59"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2026-11-09T19:53:44Z",
    "nvd_published_at": "2026-11-03T20:18:14Z",
    "severity": "HIGH"
}
References

Affected packages

npm / isomorphic-git

Package

Name
isomorphic-git
View open source insights on deps.dev
Purl
pkg:npm/isomorphic-git

Affected ranges

Type
SEMVER
Events
Introduced
0 Unknown introduced version / All previous versions are affected
Fixed
1.38.6

Database specific

last_known_affected_version_range
"<= 1.38.5"
source
"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/11/GHSA-9qw7-j9xw-fv9c/GHSA-9qw7-j9xw-fv9c.json"