Git --end-of-options: Preventing Argument Injection in Version Control
Git --end-of-options: Preventing Argument Injection in Version Control
The Role of --end-of-options in Git
Git introduced the --end-of-options flag in version 2.24.0 (November 2019) to provide a dedicated marker that stops option parsing. This was necessary because Git repurposed the standard POSIX -- marker to separate revisions from pathspecs, leaving a security gap where revisions starting with a dash could be misinterpreted as command-line options.
In standard Unix tools, -- signals the end of options. However, in Git, a command like git log main -- README.md uses -- to indicate that main is the revision and README.md is the file path. Because of this, if a script executes git log "$rev" and the variable $rev starts with a dash, Git will attempt to parse that revision as a flag rather than a reference.
To safely handle untrusted revisions and paths, the correct syntax is:
git log --end-of-options "$rev" -- "$path"
In this construction, --end-of-options protects the revision, and -- protects the pathspec.
Version-Specific Support
Support for --end-of-options was rolled out incrementally across subcommands, creating a fragmented compatibility landscape:
- Git 2.24.0: Initial introduction of the flag.
- Git 2.30.0: Added support for
git rev-parsedue to its custom argument parser. - Git 2.43.1 (February 2024): Added support for
git checkoutandgit reset, which previously rejected the flag because they handled--internally.
Argument Injection and CWE-88
Argument injection (CWE-88) occurs when a program passes untrusted strings into an argument list (argv array) and the receiving binary parses those strings as options because they start with a dash. This is distinct from command injection because it does not require a shell; the vulnerability exists even when using exec calls with an array of arguments.
Dangerous Git Options
Several Git options can be used as attack primitives if an attacker can inject them into a command:
--upload-pack=<cmd>: Used bygit cloneto specify a server-side binary.-c core.sshCommand=<cmd>: Overrides the SSH connection method for any Git invocation.
Historical Precedents
This vulnerability class has affected multiple version control systems (VCS) and tools:
- CVE-2019-13139 (Docker Build): A git-context URL fragment (
#ref:dir) was passed togit fetch, allowing the injection of--upload-pack. - August 2017 Disclosures: CVE-2017-1000117 (Git), CVE-2017-1000116 (Mercurial), CVE-2017-9800 (Subversion), and CVE-2017-12836 (CVS) all involved passing hostnames to
sshas arguments. A hostname starting with-oProxyCommand=allowed arbitrary command execution.
Impact on Package Managers
Most package managers (e.g., Bundler, npm, pip, Cargo, Go) routinely pass URLs or refs from manifests (like package.json or go.mod) to a Git subprocess. A survey of 19 package managers found that 17 fork the git binary by default.
Mitigation Strategies
Package managers employ different strategies to prevent argument injection:
- Using
--: Many tools (e.g., Bundler) add--before URLs. While this follows POSIX conventions, it is insufficient for Git revisions. - Input Validation: Some tools reject any branch name or URL that starts with a dash.
- Using
--end-of-options: As of July 2026, only Go'scmd/gouses--end-of-optionsacross the board (implemented as a fix for CVE-2025-68119). - Using Libraries: Tools like Cargo (via libgit2) and Poetry (via dulwich) avoid the argv boundary entirely by using Git libraries, though this requires them to manually track upstream security patches for the library implementation.
The Compatibility Trade-off
The primary reason many tools avoid --end-of-options is the minimum supported Git version. Relying on this flag requires a minimum of Git 2.24.0 for most commands, 2.30.0 for rev-parse, and 2.43.1 for checkout and reset. This would exclude users on older distribution-packaged Git versions, such as Ubuntu 18.04 (Git 2.17.0).
Summary of Security Recommendations
For developers wrapping Git in scripts or applications:
- Avoid passing untrusted input directly as arguments if they start with a dash.
- Use
--end-of-optionsif your environment guarantees Git $\ge$ 2.30.0 (or 2.43.1 for checkout/reset). - Combine markers: Use
git <cmd> --end-of-options <rev> -- <path>to fully isolate both the revision and the pathspec from the option parser.