Git CVE-2026-111760: An overlong path shrinks onto a submodule's .git file during checkout
Before Git writes a file from a repository to disk, it checks the path, and it always rejects names like .git. That check reads the full name. The write, however, uses a length value that is only 32 bits wide in the index. CVE-2026-111760 exploits exactly this difference: a path longer than four gibibytes passes the check and, once truncated, lands as a symlink on a submodule's .git file. Anyone who clones such a repository recursively then runs configuration and hooks that the attacker brought along.
TL;DR — 90 seconds
- What: A crafted repository can make Git replace a submodule's
.gitfile during a recursive clone with a symlink to a directory holding foreign configuration and hooks. Git then runs the attacker's code. - Severity: Amazon rates the flaw CVSS 8.8 (
AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H), SecurityOnline reports 8.0 for the GitHub advisory. No public exploit is known, and the flaw is not in the CISA KEV catalog. - Who is affected: Every machine and service that recursively clones repositories with submodules whose content it does not control. That includes developer machines, CI jobs with a recursive submodule strategy and Argo CD, whose repo-server updates submodules recursively by default.
- What to do: Upgrade Git to 2.56.1 or the matching maintenance release 2.55.1, 2.54.1, 2.53.1, 2.52.1, 2.51.3 or 2.50.2. Bundled Git copies in container images and tools need their own update.
- What would have protected you: no recursive clones of foreign repositories, object checks on fetch with a hard limit for path names, and a cloning process with few privileges and little network access. A global
core.hooksPathand container hardening do not stop the execution.
How a validated path gets shorter on write
Git records the state of the working tree in the index, and every entry there is a struct cache_entry in the code. It holds the path as a NUL-terminated string and, next to it, its length in the field ce_namelen, which read-cache-ll.h declares as unsigned int and which is 32 bits wide on common platforms. During checkout, Git builds such entries from a commit's tree objects, checks every path and then writes the files to the working tree. Tree objects themselves know no such limit, and an entry in them may carry a name of any length.
Submodules are what makes this dangerous. A cloned submodule does not have its own .git directory in its working tree but a small file named .git that points with a gitdir: line to the actual repository under the superproject's .git/modules/. The Git documentation calls this a gitfile. This one file therefore decides which directory Git treats as the submodule's repository, and that directory brings its config and its hooks. If it points to a directory the attacker shipped in the working tree, Git reads that configuration and starts those hooks, and both can contain arbitrary commands.
That is why Git has made sure for years that no entry from a foreign repository lands on .git. The function verify_path_internal() in read-cache.c rejects any path containing a .git component, including the spellings that are equivalent on Windows. It works on the full string, though. The code in entry.c that later creates the file builds the target path from ce->name and ce_namelen. As long as both agree, nobody notices the difference.
This is where the bug was. In create_ce_entry() in unpack-trees.c, Git computed the path length correctly as size_t, allocated enough memory and copied the full name, but then stored the length in the 32-bit field. For a path longer than 232 bytes, only the lower 32 bits remain. The check saw the long name and found nothing wrong, but only its beginning was written. According to the advisory, the attacker can choose that beginning so that it ends on a submodule's gitfile. If the entry is created as a symlink, Git replaces the file with a link to a directory of the attacker's. The Hacker News explains why such a symlink gets through: Git catches symlinks over a .git directory, but in a freshly cloned submodule .git is a file.
The pattern is not new. In 2024, CVE-2024-32002 already used submodules and symlinks to run just-cloned code during a recursive clone, back then via file systems that do not distinguish upper and lower case. Again two assumptions meet that look harmless on their own: that the checked name is the same as the written one, and that no path ever gets so long that its length no longer fits its field.
What the fix changes
I could not retrieve a single public fix commit by November 11. So I compared the Git 2.50.1 and 2.50.2 source tarballs from kernel.org. The core is in read-cache.c. A new function verify_ce_namelen() aborts with the message “index entry path is too long” as soon as a length exceeds INT_MAX, and the functions through which Git allocates memory for index entries call it before allocating. A path whose length does not fit the field therefore never becomes an entry.
In addition, the patch replaces int with size_t in several places and adds lengths with st_add(), which aborts on overflow instead of silently wrapping, for example in read_one_entry_opt() and in update_some() in builtin/checkout.c. verify_path_internal() now also rejects paths longer than the new setting index.maxPathLen. Its default is 1073740799 bytes according to the documentation, and Git rejects values above 2147483647. The release notes explicitly list this limit as defense in depth.
The new tests in t/t1600-index.sh reveal the most. One builds a tree path whose length wraps to a small value, others check that checkout, restore and ls-files reject it before its length is truncated. So the problem was not a single forgotten check. It was a data type that was narrower in many places than the values flowing through it. The fix therefore acts where every entry is created, not at the many places where Git uses it later.
From a foreign repository to a hook in your own clone
The attack needs a repository with a submodule whose content the attacker controls, and a victim who clones it recursively, for example with git clone --recurse-submodules. The attacker needs no privileges on the target system, and the required user interaction is the clone itself. Out of the box, a plain git clone is not affected, because Git only fetches submodules on explicit request, and according to the documentation the setting submodule.recurse does not apply to clone either. The release notes and the GitHub blog name only recursive clones as the trigger. Whether a non-recursive git submodule update --init is already enough is left open by both, and the GitHub advisory itself could not be retrieved as of November 12.
Conceptually the chain has three links. The repository contains a tree with an overlong path whose truncated form points to a submodule's gitfile, plus a directory with prepared configuration and hooks. During checkout, Git replaces the gitfile with the symlink. As soon as Git then works inside the submodule, it treats the attacker's directory as the submodule's repository and runs whatever is stored there. Long, uniform names compress well, so in my assessment the attacker does not need to transfer large amounts of data. On the victim, however, the full path is built in memory. Containers with a tight memory limit are therefore more likely to abort than to complete the attack, which I derive from the code and which is not a protection you should rely on.
The damage is code execution with the privileges of the cloning process. On a developer machine that code reaches SSH keys, cloud credentials and tokens in the home directory. In a CI job it reaches that job's variables and tokens, and on runners that execute several jobs in sequence on the same host, possibly more. Argo CD is the most sensitive case. According to its source code, its repo-server runs git submodule update --init --recursive on every checkout unless ARGOCD_GIT_MODULES_ENABLED is set to false, and the default is true. The repo-server receives the credentials of the repositories it clones and renders the manifests Argo CD deploys. In my assessment, anyone controlling a repository that Argo CD syncs could use this path to run code in the repo-server and influence rendered manifests.
So you are really affected if a process recursively clones repositories whose content is not fully under your control. That includes your own repositories whose submodules point to third-party projects, because a compromised upstream project is enough as an entry point. If you only run flat clones without submodules, you are right to stay calm, but you should check whether a tool default fetches submodules after all. GitLab operators should mainly watch CVE-2026-105437 from the same release wave, because GitLab names only that flaw in its patch release of November 10 and rates it critical.
| CVE | Affected | Fix | Precondition | CVSS |
|---|---|---|---|---|
| CVE-2026-111760 (GHSA-vp2w-7xxj-jvxr) | Git before the fix releases; Debian also lists 2.39 and 2.47 as vulnerable | 2.56.1, 2.55.1, 2.54.1, 2.53.1, 2.52.1, 2.51.3, 2.50.2 | Recursive clone of a crafted repository | 8.8 per Amazon, 8.0 per SecurityOnline (high) |
| CVE-2026-105437 (GHSA-m7r7-8v36-j77r), same release | Git before the fix releases; GitLab from 16.2 before 19.3.8, 19.4.6, 19.5.4 | Git as above; GitLab 19.3.8, 19.4.6, 19.5.4 | Merge with many conflicts and a large conflict-marker-size | 7.5 (high), critical at GitLab |
The sources disagree on the CVSS score. Amazon assigns 8.8 without elevated attack complexity, SecurityOnline reports 8.0 for the GitHub advisory, which I could not retrieve myself. There is no official CNA score yet, because the CVE ID is still only reserved at cve.org as of November 12. None of the sources checked mentions a public exploit as of November 11, no attacks are known, and the flaw is not in the CISA KEV catalog. There is no EPSS score yet, because the CVE record has been published neither at NVD nor in the CVE list. The tests in the patch do show fairly precisely where the limit lies, though, which lowers the effort for a reimplementation.
Git 2.56.1, the maintenance releases and the forgotten copies
The Git project released Git 2.56.1 on November 10 and backported the fix to 2.55.1, 2.54.1, 2.53.1, 2.52.1, 2.51.3 and 2.50.2. If you run an older branch, you get no upstream fix and depend on your distribution. GitHub Enterprise Server 3.19.15, 3.20.11, 3.21.9 and 3.22.4 are out, and according to GitHub, GitHub.com is already patched. Git for Windows has shipped 2.56.1 as well.
Distribution coverage was still thin on November 11. The Amazon Linux Security Center lists the git package for Amazon Linux 2 and Amazon Linux 2023 as “Pending Fix”, with no ALAS yet. The Debian tracker marks bookworm, trixie, forky and sid as vulnerable. Since November 11, the Wolfi package index has listed git 2.56.1-r0 for x86_64 and aarch64. Wolfi's security feed does not yet list the CVE as fixed, though, and the public package recipe still showed 2.56.0 on November 12. apk info git shows which version your image contains. Update of November 12, 2026: Amazon Linux 2 and Amazon Linux 2023 are still listed as “Pending Fix” in ALAS, and Debian still marks all branches as vulnerable.
The real pitfall lies elsewhere. Git ships as its own copy in many container images and tools, in the Argo CD image, in CI base images, in a GitLab Runner's helper image and in GitHub Desktop. Updating the host package changes none of these copies. GitHub announces patched versions for Actions, Codespaces, Desktop and the Copilot app. For every other image you have to take care yourself, by rebuilding it as soon as the base image contains the patched package.
The release also changes behavior unrelated to this flaw. git fast-import now rejects the features rewrite-submodules-from, rewrite-submodules-to and export-pack-edges unless you set --allow-unsafe-features, which can break migration scripts. The new limit index.maxPathLen is set so high, at roughly one gibibyte, that nobody notices it in daily use.
Hardening that would have worked before the patch
First it is worth sorting out which measures can work against this mechanism at all. The attack turns data from a repository into code that starts during the clone, using Git's perfectly normal means: reading configuration and running hooks. Three things are therefore effective. You can prevent foreign repositories from being processed recursively. You can reject malformed objects before they are checked out. And you can limit what the cloning process reaches if foreign code runs after all. Container hardening belongs only to the third group: it reduces the damage and does not prevent the execution.
Submodules only where you need them
The flaw needs a recursive clone, so the most effective measure is not to fetch submodules where nobody needs them. In Argo CD, the key reposerver.enable.git.submodule in the ConfigMap argocd-cmd-params-cm controls the repo-server's behavior, and applicationsetcontroller.enable.git.submodule does the same for the ApplicationSet controller. Both default to "true". According to the source code, the repo-server reads the value from its environment at startup, so a change requires a restart. In GitLab Runner, GIT_SUBMODULE_STRATEGY fetches no submodules with none, only the top level with normal and all of them with recursive, and GIT_SUBMODULE_PATHS narrows the selection. The GitHub action actions/checkout only fetches submodules with submodules: true or recursive, and the default is false.
The limit of this measure is operational. If you really need submodules, you cannot simply switch them off. Then it helps to include submodules only from your own controlled sources and to vendor third-party content instead, that is, to take a reviewed state into your own repository. A submodule pointing to a foreign project brings code you have not seen into a process that can run commands during the clone, with every update.
Object checks on fetch with a hard limit for path names
Git can check received objects before accepting them. With transfer.fsckObjects=true, a fetch aborts on malformed objects according to the documentation, and individual findings can be made stricter via fetch.fsck.<msg-id>. For this case the finding largePathname is interesting. It reports tree entries whose name is longer than 4096 bytes, but by default it is only a warning and stops nothing. With fetch.fsck.largePathname=error it becomes an abort.
Why this works against exactly this mechanism follows from a second limit. core.maxTreeDepth limits the nesting depth when traversing a tree, to 2048 levels by default. If no name may be longer than 4096 bytes and no path deeper than 2048 levels, every path stays below roughly 8.4 megabytes and thus far below the four gibibytes the overflow needs. That is my derivation from documentation and source code, not a statement of the advisory. Before you rely on it, test the rule with your own test repository and a low limit such as error:100.
The measure has limits and a cost. It only applies to network transfers, and as far as I understand it does not check local clones from a path. Older repositories sometimes contain harmless format errors on which a strict check fails as well, and you then have to exempt them via fetch.fsck.skipList. And the setting must live where the cloning process reads it. According to its documentation, Argo CD sets HOME to /dev/null and does not support a global Git configuration, so there it belongs in the system configuration, which you mount via ConfigMap or bake into the image. Submodule clones read the same system and user configuration as the clone that triggers them, so the rule applies to them too.
Tighten index.maxPathLen after the update
After the update you can set the new limit yourself. Linux limits paths in file system calls to 4096 bytes via PATH_MAX anyway, so as far as I understand, a longer entry could not be checked out on a typical Linux system at all. In my assessment, a system-wide index.maxPathLen=4096 therefore costs nothing there and draws a second line below the built-in one. Test the value first for builds on Windows or macOS. Before the update Git does not know the setting, so it replaces neither the patch nor the fsck rule.
The cloning process with few privileges and little network
If foreign code does run, the environment decides the damage. CI jobs that build foreign repositories belong on short-lived runners that are discarded after each job, without a Docker socket and without privileged mode. In the Kubernetes executor that means runAsNonRoot: true, allowPrivilegeEscalation: false, dropped capabilities and automountServiceAccountToken: false, so that a hook finds no token for the Kubernetes API. An egress rule that only allows Git hosts, package sources and DNS makes it harder to exfiltrate credentials and to open a reverse connection. The same principle applies to the Argo CD repo-server. Outbound, it needs your Git, Helm and OCI sources plus Argo CD's Redis cache, but no open path to the internet.
None of these measures stops the code itself. A hook starts as an ordinary child process of Git, and every pod security setting has to allow that, because Git does not work without it. What the measures achieve is a small haul: few secrets in the process, no way into the cluster and an environment that disappears after the job. You can find more on egress rules and pod security in the series TYPO3 on Kubernetes under IT-Grundschutz.
What does not help here
A global core.hooksPath pointing to an empty directory sounds like a quick fix but does not work here. The attacker's configuration lives in the repository directory, and Git evaluates repository configuration after the global one, so it wins. Git configuration also offers further ways to start commands, for example via core.fsmonitor. safe.directory does not help either, because it only checks who owns a repository, and freshly cloned files belong to you.
seccomp, dropped capabilities and readOnlyRootFilesystem remain right but are ineffective against the execution, because starting a hook is a normal execve and the working tree has to be writable. Signed commits do not protect either, because the signature is usually only verified after the checkout has already run. A CVE gate in the pipeline only triggers once the scanner knows the flaw, and since the CVE record was in neither NVD nor OSV on November 11, many scanners are unlikely to report it yet. If you rely on that alone, you only learn about it from the release notes in the meantime.
Detection and verification
Whether you are affected is shown first by git --version, everywhere Git runs: on hosts, in every image that clones, in the Argo CD image and in your runners' helper image. Next, look at the configuration. Search pipelines for --recurse-submodules, for GIT_SUBMODULE_STRATEGY set to normal or recursive and for submodules: in checkout steps, and check the values in argocd-cmd-params-cm in Argo CD. A process with vulnerable Git and recursive clones of foreign content calls for immediate action.
Whether someone has exploited the flaw is harder to tell, and neither the advisory nor the release notes name indicators. From the mechanism I derive two hints. A submodule whose .git is a symlink instead of a file is not a normal state, and a search for entries named .git of type symlink in working trees and in the repo-server cache brings it to light. After the update, Git also aborts with “index entry path is too long” when it meets such a path. Since no ordinary project contains paths of that length, this message in job logs is a strong sign of a crafted repository. Out-of-memory aborts while cloning a small repository are a weaker signal in the same direction.
If you suspect an attack, treat the environment that cloned as compromised. Discard the runner or pod, rotate every credential the process could see, that is SSH keys, CI tokens and Argo CD's repository credentials, and check which artifacts, commits or manifests came from that environment. An affected developer machine deserves the same care as any other machine that ran foreign code.
Recommendation for operators
Act now if Argo CD with default settings syncs repositories that are not maintained by your platform team alone, or if CI jobs recursively clone third-party repositories. Switch off submodule support wherever nobody needs it, put the fsck rule into the system configuration and rebuild the affected images as soon as patched Git is available. Developers who regularly clone foreign projects with submodules should update their local Git today.
Monitoring is enough if you only clone your own repositories without submodules and no recursive strategy is active anywhere. The update still belongs in the next maintenance window, if only because the same release closes five more flaws, including the heap overflow during merges that hits Git servers.
Regardless of the individual case, what I say about every flaw applies: patches and updates belong automated. Git in particular shows why this has to reach into the images, because the vulnerable copy rarely sits in the host package and almost always in a base image nobody has rebuilt for months. When Renovate or Dependabot propose new base images, image digests and Helm charts such as Argo CD's as merge requests on their own and the pipeline tests them against staging, a security release like Git 2.56.1 is a matter of hours rather than weeks. A cooldown for routine updates protects against compromised releases, and you exempt security updates from it. And if the stricter fast-import check breaks a migration script, that shows up in testing and not in production. If you update by hand, you quickly end up with weeks without a patch during the holiday season, followed by a bulk update where nobody knows which change caused what.
Frequently asked questions about CVE-2026-111760
I never clone recursively. Am I affected?+
Not through this path, as long as your tools do not fetch submodules either. Check Argo CD, your CI variables and checkout steps. Install the update anyway.
Is updating the host package enough?+
Only if Git runs nowhere else. Every container image brings its own Git, and so do Argo CD, runner helper images and GitHub Desktop. These copies need their own rebuild.
Does a global core.hooksPath help?+
No. The attacker's configuration lives in the repository and takes precedence over the global one. It also offers other ways to start commands.
How do I recognize an attack?+
A submodule whose .git is a symlink instead of a file is a strong signal. Patched Git also aborts with “index entry path is too long” when it meets such a path.
Conclusion
CVE-2026-111760 is a gap between checking and using. Git checked one name and wrote another, because the length fell into a field that was too narrow on the way. That makes the fix instructive: it ensures that an entry whose length cannot be represented is never created, instead of re-checking at every later point. The lesson reaches far beyond Git. Wherever a system checks input in one form and processes it in another, the same class of bugs lies in wait, as the signature bypass in Keycloak shows the same week. For operations one simple rule remains: a recursive clone of foreign content means executing foreign data, and it deserves the same caution as any other foreign code.
Sources
- Git advisory GHSA-vp2w-7xxj-jvxr (CVE-2026-111760)
- Git v2.50.2 release notes
- kernel.org: Git source tarballs 2.50.1 and 2.50.2 (comparison of read-cache.c, unpack-trees.c, builtin/checkout.c, t/t1600-index.sh)
- GitHub Blog: Git security vulnerabilities announced
- Git for Windows 2.56.1
- Amazon Linux Security Center: CVE-2026-111760
- Debian Security Tracker: CVE-2026-111760
- CVE.org: CVE-2026-111760 (status reserved)
- GitLab Critical Patch Release 19.5.4, 19.4.6, 19.3.8
- GitHub advisory GHSA-m7r7-8v36-j77r (CVE-2026-105437)
- The Hacker News: Git Patches Flaw That Lets Malicious Repositories Execute Code During Recursive Clones
- SecurityOnline: Git 2.56.1 Patches Six Security Flaws
- Git docs: git-config (index.maxPathLen, core.maxTreeDepth, transfer.fsckObjects, fetch.fsck, submodule.recurse)
- Git docs: git-fsck, message ID largePathname
- Git docs: gitsubmodules
- Argo CD: util/git/client.go (submodule update)
- Argo CD: argocd-cmd-params-cm.yaml
- Argo CD docs: Git configuration
- GitLab docs: Configure runners, GIT_SUBMODULE_STRATEGY
- actions/checkout: action.yml
I check where your pipelines and your Argo CD recursively clone foreign repositories, harden the Git configuration and make sure patched Git reaches every image.
Audits of CI runners and Argo CD for submodules and Git versions, system-wide Git hardening with fsck limits, egress rules for cloning services and automated image rebuilds with Renovate.
Platform operations instead of consulting on paper: I continuously check, patch and harden your infrastructure.
About the author

Kai Ole Hartwig
Programming since 2002 – self-taught, set up my own business with KO-Web in 2012. Over 100 projects, with a focus on security, performance, automation and quality. Today freelance: DevSecOps consulting, training and software development.
