Root cause
Injection & unsafe execution
The new ls-remote option list contains upload-pack without its equivalent exec option.
How AI contributed: Flawed AI-written codePublished Jul 12, 2026 in
gitpython-developers/gitpythonAll 8 findings in this repository
Provisional finding. Full causal and release verification remains incomplete.
GitPython's option guard blocked one external-program option while leaving its equivalent spelling accepted. The repair adds the missing spelling to the blocked options.
GitPython passed caller-supplied options and revisions straight to git in several commands, so an application forwarding them could run a helper program or truncate files. An AI-written fix added option checks to those commands and a dedicated ls_remote wrapper, but the wrapper checked only options and never the remote URL, so an ext:: transport URL could still run a helper through ls_remote. A later fix added the missing protocol check, first released in 3.2.0.
Root cause
The new ls-remote option list contains upload-pack without its equivalent exec option.
Fix
ls_remote now checks remote URLs for unsafe transports such as ext:: unless the caller opts in.
Case facts
@@ -649,6 +649,11 @@ re_unsafe_protocol = re.compile(r"(.+)::.+") + unsafe_git_ls_remote_options = [+ # This option allows arbitrary command execution in git-ls-remote.+ "--upload-pack",+ ]+ def __getstate__(self) -> Dict[str, Any]: return slots_to_dict(self, exclude=self._excluded_) + # Match Git's leading transport selector, including an empty helper name.+ re_unsafe_protocol = re.compile(r"([A-Za-z0-9][A-Za-z0-9+.-]*|)::") unsafe_git_ls_remote_options = [ # This option allows arbitrary command execution in git-ls-remote.@@ -1129,16 +1130,28 @@ def ls_remote( self, *args: Any, allow_unsafe_options: bool = False,+ allow_unsafe_protocols: bool = False, **kwargs: Any, ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]: """List references in a remote repository. :param allow_unsafe_options: Allow unsafe options, like ``--upload-pack`` or ``--exec``.++ :param allow_unsafe_protocols:+ Allow unsafe protocols to be used, like ``ext``. Positional arguments+ and split short-option values are checked. """ if not allow_unsafe_options: candidate_options = self._option_candidates(args, kwargs) Git.check_unsafe_options(options=candidate_options, unsafe_options=self.unsafe_git_ls_remote_options)+ if not allow_unsafe_protocols:+ protocol_args = list(args)+ if kwargs.get("split_single_char_options", True):+ # Split short-option values can become the URL after parsing earlier options.+ protocol_args.extend(value for key, value in kwargs.items() if len(key) == 1)+ for arg in self._unpack_args(protocol_args):+ self.check_unsafe_protocols(arg) return self._call_process("ls_remote", *args, **kwargs) @propertyAI-assisted change 629f1ec5dd9d9926fc7323eb92868441bb054b3fa8af95059c8580a3388d1b2b · Fix 1884fef412d554f716ff865085fa9fc7b3b69339ce85a2b160589e250ea455e7