ANT-2026-6SNS6KMP · gitoxidelabs/gitoxide

rce high

GHSA-f26g-jm89-4g65

Severity Claude high · Security research firm - · Maintainer high

Discovered by Claude Mythos Preview

REPORT

Anthropic's analysis of this finding, sealed at approval.

ANT-2026-6SNS6KMP: RCE when updating a Git submodule of a malicious repository

Updating a Git submodule from a malicious repository leads to remote code execution.

Target

Project: GitoxideLabs/gitoxide
Discovery: static analysis — not yet dynamically reproduced

Technical Details

Step [A] reads submodule.<name>.update newest-to-oldest across sections, so if the trusted override section has no update key the attacker's .gitmodules value is returned. Step [B] then disarms the guard because .any(|s| s.header().subsection_name() == Some(name) && !std::ptr::eq(s.meta(), ours)) only checks that a foreign-metadata section exists for that name, not that it supplied the value read in [A]. The two checks ask different questions, and the mismatch lets a .gitmodules-sourced !command pass as trusted.

Reproduction

This finding was identified by static analysis and has not yet been dynamically reproduced. The Technical Details section above describes the code path; a trigger input is not included.

[No reproducer or sanitizer output attached — request from security-cvd@anthropic.com if needed.]

Acknowledgement

This vulnerability was discovered by Claude, Anthropic's AI assistant, and triaged by the Anthropic security team in collaboration with Anthropic Research. Please direct questions to security-cvd@anthropic.com and reference ANT-2026-6SNS6KMP.


Reference: ANT-2026-6SNS6KMP
Anthropic CVD Policy: https://www.anthropic.com/coordinated-vulnerability-disclosure

UPSTREAM FIX

The change that resolved this finding.

diff --git a/gix-submodule/src/access.rs b/gix-submodule/src/access.rs
index 0b2f5b21c2d..0ae7d654867 100644
--- a/gix-submodule/src/access.rs
+++ b/gix-submodule/src/access.rs
@@ -166,7 +166,12 @@ impl File {
 
     /// Retrieve the `update` field of the submodule named `name`, if present.
     pub fn update(&self, name: &BStr) -> Result<Option<Update>, config::update::Error> {
-        let value: Update = match self.config.string(&format!("submodule.{name}.update")) {
+        let mut value_is_from_modules_file = None;
+        let our_meta = self.config.meta();
+        let value: Update = match self.config.string_filter(&format!("submodule.{name}.update"), |meta| {
+            value_is_from_modules_file = Some(std::ptr::eq(meta, our_meta));
+            true
+        }) {
             Some(v) => v.as_ref().try_into().map_err(|()| config::update::Error::Invalid {
                 submodule: name.to_owned(),
                 actual: v.into_owned(),
@@ -175,14 +180,7 @@ impl File {
         };
 
         if let Update::Command(cmd) = &value {
-            let ours = self.config.meta();
-            let has_value_from_foreign_section = self
-                .config
-                .sections_by_name("submodule")
-                .into_iter()
-                .flatten()
-                .any(|s| s.header().subsection_name() == Some(name) && !std::ptr::eq(s.meta(), ours));
-            if !has_value_from_foreign_section {
+            if value_is_from_modules_file.unwrap_or_default() {
                 return Err(config::update::Error::CommandForbiddenInModulesConfiguration {
                     submodule: name.to_owned(),
                     actual: cmd.to_owned(),

https://github.com/GitoxideLabs/gitoxide/commit/e3ca1e64b0bcd627c7c5d3620f891cc22d1d03c7

ADVISORY

https://github.com/GitoxideLabs/gitoxide/security/advisories/GHSA-f26g-jm89-4g65

TIMELINE

Dates from discovery through public reveal.

  1. 2026-03-29 Sent to maintainer
  2. 2026-03-29 Reported to tracker
  3. 2026-04-24 Patch released
  4. 2026-05-08 Maintainer acknowledged
  5. 2026-05-20 Publicly revealed
PROVENANCE

SHA-3-512 hash:

bc1d508742c8b9b677d57b6feae069ec5a97a697eba86a13021c648a8457d27998aac7f9245b25e244e4ae804163175bd310e4302c522abf267f76e64a845221

Committed 2026-05-08 09:37 PT

Revealed 2026-05-20 00:40 PT

Verify (download preimage.json)

Show preimage JSON
{
  "ant_id": "ANT-2026-6SNS6KMP",
  "bug_class": "Remote Code Execution",
  "claude_severity": "high",
  "commit_sha": null,
  "created_at": "2026-03-29T20:43:51+00:00",
  "description": "Updating a Git submodule from a malicious repository leads to remote code execution.",
  "discovered_at": "2026-03-10T00:00:00+00:00",
  "location": null,
  "poc_sha256": null,
  "preimage_version": 1,
  "project": "GitoxideLabs/gitoxide",
  "reproduction": null,
  "technical_details": "Step [A] reads `submodule.<name>.update` newest-to-oldest across sections, so if the trusted override section has no `update` key the attacker's .gitmodules value is returned. Step [B] then disarms the guard because `.any(|s| s.header().subsection_name() == Some(name) && !std::ptr::eq(s.meta(), ours))` only checks that a foreign-metadata section exists for that name, not that it supplied the value read in [A]. The two checks ask different questions, and the mismatch lets a .gitmodules-sourced `!command` pass as trusted.",
  "title": "RCE when updating a Git submodule of a malicious repository",
  "vendor_severity": null
}