Описание
gitoxide: CommandForbiddenInModulesConfiguration Bypass in gix_submodule::File::update() Enables Arbitrary Command Execution via .gitmodules
Summary
gix_submodule::File::update() is the API that gates whether an attacker-supplied .gitmodules file may set update = !<shell command>. The function is designed to return Err(CommandForbiddenInModulesConfiguration) unless the !command value came from a trusted local source (.git/config). Git CVE CVE-2019-19604 illustrates why this check is necessary.
However, the guard is implemented incorrectly: it checks whether any section with the same submodule name exists from a non-.gitmodules source; it does not verify that the update value came from that section.
Once a submodule has been initialized (any workflow that writes submodule.<name>.url to .git/config), and the attacker subsequently adds update = !cmd to .gitmodules, the guard passes while the command value falls through to the attacker-controlled file.
On an identical repository state, git submodule update aborts with fatal: invalid value for 'submodule.sub.update', while gix::Submodule::update() returns Ok(Some(Update::Command("touch /tmp/pwned"))).
The vulnerable code was introduced in https://github.com/GitoxideLabs/gitoxide/commit/6a2e6a436f76c8bbf2487f9967413a51356667a0.
Details
The vulnerable method is gix_submodule::File::update: https://github.com/GitoxideLabs/gitoxide/blob/main/gix-submodule/src/access.rs#L168-L193:
PoC
git submodule init copies submodule.$name.url and writes active = true into .git/config (init_submodule(), builtin/submodule--helper.c:438-517). It does not unconditionally copy update.
Since CVE-2019-19604, git rejects .gitmodules files that contain update = !cmd at parse time. However, init is a one-time operation - once the .git/config section exists, subsequent changes to .gitmodules are not re-inited.
So, the attack sequence is:
- Attacker's repo ships a benign
.gitmodules(noupdatekey). - Victim clones and runs
git submodule init->.git/configcontains:[submodule "sub"] active = true url = /tmp/sub-origin - Attacker pushes a new commit adding
update = !cmdto.gitmodules. - Victim runs
git pull->.gitmodulesnow contains:[submodule "sub"] path = sub url = /tmp/sub-origin update = !touch /tmp/pwnedwhile.git/configis unchanged.
This is the precise state that bypasses gitoxide's guard:
- The .git/config entry - even though it contains only url and active - causes
append_submodule_overridesto create an override section. That section has foreign (non-.gitmodules) metadata, so the existence check at [B] returns true and the guard is disarmed. - However, because that override section has no update key, the value lookup at [A] skips past it and falls through to the .gitmodules section, returning the attacker's !touch /tmp/pwned.
The bug is the mismatch between what [A] and [B] actually inspect: [A] asks "which section provides the update value?" (answer: .gitmodules), while [B] asks "does any trusted section exist for this submodule?" (answer: yes). A correct guard would ask the same question as [A].
Git itself would refuse to operate on this repository at the next git submodule update. The vulnerability is in gitoxide-based consumers that call Submodule::update() and trust its output.
Option 1: Unit test (verified - passes, confirming the bug)
Drop into gix-submodule/tests/file/mod.rs inside mod update:
Option 2: End-to-end - git refuses, gitoxide accepts
Verified with git 2.51.2 and gix @ dd5c18d9e.
Final state:
Upstream git on this state:
Gitoxide on the same state:
The CommandForbiddenInModulesConfiguration guard never fires.
Impact
Direct
Any downstream code built on gix that:
- Calls
Submodule::update()to determine the update strategy, and - Trusts that
Update::Command(_)is safe to execute (becauseCommandForbiddenInModulesConfigurationexists as the documented guard)
…will execute attacker-controlled shell commands on submodule update against a previously-initialized submodule.
gix itself does not currently ship a submodule update implementation, so there is no RCE in the gix CLI today. However:
- The
Submodule::update()API is public atgix/src/submodule/mod.rs:108and delegates directly to the vulnerable function. - The error variant name (
CommandForbiddenInModulesConfiguration) and test suite (valid_in_overridesatgix-submodule/tests/file/mod.rs:272) explicitly document this as the security boundary. - Any third-party tool, IDE plugin, or CI integration building submodule-update on top of
gixinherits this vulnerability.
Indirect / second-order
- CI/forge integrations that auto-init submodules and then query the update mode
- Editor/IDE extensions using
gixfor submodule info - Gitoxide-based
initequivalents - any tool that implements its own init (writingurlto local config) creates the bypass state without needing the pull-after-init sequence
Ссылки
- https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-f26g-jm89-4g65
- https://nvd.nist.gov/vuln/detail/CVE-2026-40034
- https://github.com/GitoxideLabs/gitoxide/commit/6a2e6a436f76c8bbf2487f9967413a51356667a0
- https://github.com/GitoxideLabs/gitoxide/commit/dd5c18d9e526e8de462fa40aa047acd097cfa7dc
- https://red.anthropic.com/2026/cvd/findings/ANT-2026-6SNS6KMP
- https://www.vulncheck.com/advisories/gitoxide-command-injection-via-partial-gitmodules-override-in-gix-submodule
Пакеты
gix
>= 0.31.0, < 0.83.0
0.83.0
EPSS
8.5 High
CVSS4
7.8 High
CVSS3
CVE ID
Дефекты
Связанные уязвимости
gix-submodule before 0.29.0 (gitoxide before 0.5.21, gix before 0.84.0) incorrectly validates the update field in .gitmodules, allowing attackers to bypass the CommandForbiddenInModulesConfiguration guard when a submodule has been initialized with only partial configuration in .git/config. An attacker can inject arbitrary shell commands via the update field in .gitmodules that will be executed when Submodule::update() is called on a previously-initialized submodule, enabling remote code execution.
gix-submodule before 0.29.0 (gitoxide before 0.5.21, gix before 0.84.0) incorrectly validates the update field in .gitmodules, allowing attackers to bypass the CommandForbiddenInModulesConfiguration guard when a submodule has been initialized with only partial configuration in .git/config. An attacker can inject arbitrary shell commands via the update field in .gitmodules that will be executed when Submodule::update() is called on a previously-initialized submodule, enabling remote code execution.
gitoxide - Command Injection via Partial .gitmodules Override in gix-submodule
gix-submodule before 0.29.0 (gitoxide before 0.5.21, gix before 0.84.0 ...
EPSS
8.5 High
CVSS4
7.8 High
CVSS3