Repository navigation
Update dependency gitpython to v3.1.62 [SECURITY] - #44
Open
renovate[bot] wants to merge 1 commit into
Open
renovate[bot] wants to merge 1 commit into
renovate[bot] wants to merge 1 commit into
Conversation
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
May 8, 2026 11:50
4872032 to
98b4183
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
May 10, 2026 07:50
98b4183 to
f6919e1
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
July 25, 2026 11:08
f6919e1 to
5211e35
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
July 30, 2026 00:03
5211e35 to
dbeec01
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
August 7, 2026 04:11
dbeec01 to
9640080
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
August 9, 2026 12:09
9640080 to
a095e3a
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
September 12, 2026 15:34
a095e3a to
fb09c97
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
October 8, 2026 03:42
fb09c97 to
052f48e
Compare
renovate
Bot
force-pushed
the
renovate/pypi-gitpython-vulnerability
branch
from
October 24, 2026 04:07
052f48e to
ac66fb0
Compare
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

0 New Issues
0 Fixed Issues
0 Accepted Issues
No data about coverage (0.00% Estimated after merge)
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
==3.1.37→==3.1.62GitPython has Command Injection via Git options bypass
CVE-2026-42215 / GHSA-rpm5-65cw-6hj4
More information
Details
Summary
GitPython blocks dangerous Git options such as
--upload-packand--receive-packby default, but the equivalent Python kwargsupload_packandreceive_packbypass that check. If an application passes attacker-controlled kwargs intoRepo.clone_from(),Remote.fetch(),Remote.pull(), orRemote.push(), this leads to arbitrary command execution even whenallow_unsafe_optionsis left at its default value ofFalse.Details
GitPython explicitly treats helper-command options as unsafe because they can be used to execute arbitrary commands:
git/repo/base.py:145-153marks clone options such as--upload-pack,-u,--config, and-cas unsafe.git/remote.py:535-548marks fetch/pull/push options such as--upload-pack,--receive-pack, and--execas unsafe.The vulnerable API paths check the raw kwarg names before they're its normalized into command-line flags:
Repo.clone_from()checkslist(kwargs.keys())ingit/repo/base.py:1387-1390Remote.fetch()checkslist(kwargs.keys())ingit/remote.py:1070-1071Remote.pull()checkslist(kwargs.keys())ingit/remote.py:1124-1125Remote.push()checkslist(kwargs.keys())ingit/remote.py:1197-1198That validation is performed by
Git.check_unsafe_options()ingit/cmd.py:948-961. The validator correctly blocks option names such asupload-pack,receive-pack, andexec.Later, GitPython converts Python kwargs into Git command-line flags in
Git.transform_kwarg()atgit/cmd.py:1471-1484. During that step, underscore-form kwargs are dashified:upload_pack=...becomes--upload-pack=...receive_pack=...becomes--receive-pack=...Because the unsafe-option check runs before this normalization, underscore-form kwargs bypass the safety check even though they become the exact dangerous Git flags that the code is supposed to reject.
In practice:
remote.fetch(**{"upload-pack": helper})is blocked withUnsafeOptionErrorremote.fetch(upload_pack=helper)is allowed and reaches helper executionThe same bypass works for:
This does not appear to affect every unsafe option. For example,
exec=is already rejected because the raw kwarg nameexecmatches the blocked option name before normalization.Existing tests cover the hyphenated form, not the vulnerable underscore form. For example:
test/test_clone.py:129-136checks{"upload-pack": ...}test/test_remote.py:830-833checks{"upload-pack": ...}test/test_remote.py:968-975checks{"receive-pack": ...}Those tests correctly confirm the literal Git option names are blocked, but they do not exercise the normal Python kwarg spelling that bypasses the guard.
PoC
python3 -m venv .venv-sec .venv-sec/bin/pip install setuptools gitdb source ./.venv-sec/bin/activatecontrol=blockedproof_exists True ...id, working directory, argv, and selected environment variable namesExample output:
This PoC does not require a malicious repository. The PoC uses that fresh blank repository. The only attacker-controlled input is the kwarg that GitPython turns into
--upload-pack.Impact
Who is impacted:
**kwargsWhat the attacker needs to control:
upload_packorreceive_packin the kwargs passed toRepo.clone_from(),Remote.fetch(),Remote.pull(), orRemote.push()From a severity perspective, this could lead to
The highest-risk environments are network-reachable services and automation systems that expose these GitPython kwargs across a trust boundary while relying on the default unsafe-option guard for protection.
Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Unsafe option check validates multi_options before shlex.split transformation
CVE-2026-42284 / GHSA-x2qx-6953-8485
More information
Details
Summary
_clone()validatesmulti_optionsas the original list, then executesshlex.split(" ".join(multi_options)). A string like"--branch main --config core.hooksPath=/x"passes validation (starts with--branch), but after split becomes["--branch", "main", "--config", "core.hooksPath=/x"]. Git applies the config and executes attacker hooks during clone.Details
The vulnerable code is in
git/repo/base.pyline 1383:Then validation runs on the original list at line 1390:
Then execution uses the transformed result at line 1392:
The check at
git/cmd.pyline 959 usesstartswith:"--branch main --config ..."does not start with"--config", so it passes. Aftershlex.split,"--config"becomes its own token and reaches git.Also affects
Submodule.update()viaclone_multi_options.PoC
Output:
Impact
Any application passing user input to
multi_optionsinclone_from(),clone(), orSubmodule.update()is vulnerable. Attacker embeds--config core.hooksPath=<dir>inside a string starting with a safe option. Check does not block it. Git executes attacker code. Same class as CVE-2023-40267.Severity
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython reference APIs has a path traversal vulnerability that allows arbitrary file write and delete outside the repository
CVE-2026-44243 / GHSA-7545-fcxq-7j24
More information
Details
🧾 Summary
A vulnerability in GitPython allows attackers who can supply a crafted reference path to an application using GitPython to write, overwrite, move, or delete files outside the repository’s
.gitdirectory via insufficient validation of reference paths in reference creation, rename, and delete operations.📦 Affected Versions
<= 3.1.46and currentmain(3.1.47in local checkout)🧠 Details
Vulnerability Type
Path Traversal leading to Arbitrary File Write and Arbitrary File Deletion
Root Cause
Reference paths are validated when they are resolved for reading, but are not consistently validated before filesystem write, rename, and delete operations.
SymbolicReference._check_ref_name_valid()rejects traversal sequences such as.., butSymbolicReference.create,Reference.create,SymbolicReference.set_reference,SymbolicReference.rename, andSymbolicReference.deletestill construct filesystem paths from attacker-controlled ref names without enforcing repository boundaries.Affected Code
Attack Vector
Local attack through application-controlled input passed into GitPython reference APIs
Authentication Required
None at the library boundary. In practice, exploitation requires the ability to influence ref names supplied by the consuming application.
🧪 Proof of Concept
Setup
Exploit
Result
💥 Impact
What can an attacker do?
Security Impact
Who is affected?
🛠️ Mitigation / Fix
Recommended Fix
Severity
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:H/VA:H/SC:N/SI:N/SA:N/E:PReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Newline injection in config_writer().set_value() enables RCE via core.hooksPath
CVE-2026-44244 / GHSA-v87r-6q3f-2j67
More information
Details
GitConfigParser.set_value()passes values to Python'sconfigparserwithout validating for newlines. GitPython's own_write()converts embedded newlines into indented continuation lines (e.g.\nbecomes\n\t), but Git still accepts an indented[core]stanza as a section header — so the injectedcore.hooksPathbecomes effective configuration. Any Git operation that invokes hooks (commit, merge, checkout) will then execute scripts from the attacker-controlled path.The vulnerability is not merely malformed config output: GitPython's own writer converts embedded newlines into indented continuation lines, but Git still accepts an indented
[core]stanza as a section header, so the injectedcore.hooksPathbecomes effective configuration.This was found while auditing MLRun's
project.push()method, which passesauthor_nameandauthor_emaildirectly toconfig_writer().set_value()with no sanitization. Both parameters cross a trust boundary — they are caller-supplied API inputs that end up in.git/config.PoC (standalone, no MLRun required):
Tested on GitPython 3.1.46, git 2.39+.
Impact: This is persistent repo config poisoning. Any user who can supply
author_nameorauthor_emailto an application callingconfig_writer().set_value()can redirect Git hook execution to an arbitrary path. In a multi-user or hosted environment (e.g. a shared MLRun server where multiple users push to the same repositories), one user can poison the.git/configof a shared repo and have their hooks run in the context of every subsequent Git operation by any user. On single-user deployments, the impact depends on whether the application later invokes Git hooks automatically.Remediation:
set_value()should raise on CR, LF, or NUL in values rather than silently pass them through:Rejecting is safer than stripping — a stripped newline might indicate the caller is passing unsanitized input at a higher level, and silent normalization masks that.
Affected wherever
config_writer().set_value(section, key, user_input)is called with external input.** GitPython is a dependency of DVC, MLflow, Kedro, and others — worth auditing theirset_value()call sites for externally influenced inputs.Severity
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Newline injection in config_writer() section parameter bypasses CVE-2026-42215 patch, enabling RCE via core.hooksPath
GHSA-mv93-w799-cj2w
More information
Details
Summary
The patch for CVE-2026-42215 (GitPython 3.1.49) validates newlines only in the value parameter of set_value(). The section and option parameters are passed to configparser without any newline validation. An attacker who controls the section argument can inject \n to write arbitrary section headers into .git/config, including a forged [core] section with hooksPath pointing to an attacker-controlled directory, leading to RCE when any git hook is triggered.
Details
File: git/config.py — GitPython 3.1.49 (latest patched version)
_write() formats section headers as "[%s]\n" % name. When section = "user]\n[core", this writes [user]\n[core]\n — two valid section headers — into .git/config.
PoC
Impact
Same attack outcome as CVE-2026-42215 (RCE via core.hooksPath injection). The patch is incomplete — only value is validated while section and option remain injectable.
Severity
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Command Injection via git long-option prefix abbreviation bypass of CVE-2026-42215 blocklist
CVE-2026-67325 / GHSA-2f96-g7mh-g2hx
More information
Details
Command injection via long-option prefix abbreviation bypassing
check_unsafe_options(incomplete fix of CVE-2026-42215 / GHSA-rpm5-65cw-6hj4)Component: gitpython-developers/GitPython (PyPI: GitPython)
Affected: all versions carrying the 3.1.47 blocklist fix, through current
main(verified at commit20c5e275,3.1.50-42)Reporter: hackkim
Summary
The 3.1.47 fix for CVE-2026-42215 blocks dangerous git options (
--upload-pack,--config,-c,-ufor clone;--upload-packfor fetch/pull;--receive-pack,--execfor push) so callers cannot reach command-executing options unless they passallow_unsafe_options=True.The fix canonicalizes an option name along one axis (underscore→hyphen via
dashify) and checks it against an exact-match dict. It does not account for git's unambiguous long-option prefix abbreviation. Git accepts any unambiguous prefix of a long option (--upload-p,--upload-pa,--upload-pacall resolve to--upload-pack). So a kwarg key likeupload_pcanonicalizes toupload-p, misses the blocklist dict, and is emitted to git as--upload-p=<value>→ executed as--upload-pack=<value>→ command injection, in the defaultallow_unsafe_options=Falseconfiguration.The asymmetry (root cause)
The guard normalizes only
_→-and does exact dict membership. Git's CLI parser accepts a broader grammar (prefix abbreviation) than the guard models, so abbreviated keys slip through and reach git as the blocked option.Affected code (commit
20c5e275)git/cmd.py:948-960_canonicalize_option_namegit/cmd.py:963-974check_unsafe_optionsgit/cmd.py:1511transform_kwarg--<dashify(name)>=<value>to the CLIgit/repo/base.py:1411,1413git/remote.py:1074,1128,1201Bypass keys (verified)
upload_p,upload_pac--upload-packreceive_p--receive-packexe--execconf,confi--configMinimal PoC
Self-contained, no network egress (a local bare repo acts as the "remote"). Tested on current
main(git 2.50.1):Equivalent at the shell:
git clone --upload-p=/tmp/evil.sh src outrunsevil.sh.Confirmed behavior:
upload_pack(exact) → blocked;upload_p(abbrev) → passes guard, reaches git, executes. The fix works for the form it models but not the abbreviated form.allow_unsafe_options=Trueopt-out behaves as documented (out of scope).Honest scope note
Like the parent CVE, exploitation requires a host application that flows attacker-controlled kwarg keys into a GitPython clone/fetch/pull/push. Where the host passes only fixed/validated keys, this is not reachable — the vulnerability is in the library's documented defense-in-depth control (
allow_unsafe_options=False), which this variant defeats.On the
--configfamily:confbypasses the option blocklist, but weaponizing--config protocol.ext.allow=alwaysvia anext::URL is independently blocked by GitPython's protocol allowlist (allow_unsafe_protocols=False). The directly weaponizable family isupload-pack/receive-pack/exec. Reported transparently — not claiming Critical.Suggested remediation (any one)
startswithon the blocked canonical name, afterdashify).--end-of-optionsor invoke git in a way that disables long-option abbreviation.Remediation should also cover the
-c/--configfamily abbreviations, even though theext::route is currently gated by the protocol allowlist.Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: command injection via unguarded Git options in
Repo.archive(),git.ls_remote(), and arbitrary file overwrite viaRepo.iter_commits()/Repo.blame()CVE-2026-67323 / GHSA-956x-8gvw-wg5v
More information
Details
Summary
GitPython already know that --upload-pack / --exec are command-exec vectors, they are denylist in
git/remote.py:535 and check by Git.check_unsafe_options() (git/cmd.py:963), the thing is this
check him he is only call from fetch, pull, push and clone_from, everything else who build a git
argv from caller values just go through, no check, three examples
Code analysis
Repo.archive (git/repo/base.py:1623) do self.git.archive("--", treeish, *path, **kwargs), the
treeish is after the --, but the kwargs get dashify by transform_kwarg (git/cmd.py:1487) and
they land before it, so {"remote": ".", "exec": ""} give
git archive --remote=. --exec= -- , the --remote spawn the upload-archive helper and
--exec choose which binary that is, done, default git config, no protocol.ext.allow needed, and
archive already document caller kwargs (format, prefix, path) so pass a dict is normal usage
repo.git.ls_remote(url, upload_pack=""), same builder, same result, it's exactly the kwarg
gap that CVE-2026-42215 close for fetch/pull/push/clone_from, except the dynamic
repo.git.(**user_dict) surface him he never got the fix
Repo.iter_commits / Repo.blame (git/objects/commit.py:348, git/repo/base.py:1199) put the rev
before the --, no leading-dash check, a "branch name" like --output=/etc/whatever become
git rev-list --output=... --, and git he open and truncate that file before he even validate the
revision, the file is gone even if the command error right after
PoC
Released 3.1.50, git 2.51.0, stock config (
git config --get protocol.ext.allowreturns nothing here).Common setup for the three:
Severity
CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: git-config section-name injection enables arbitrary config directives (core.sshCommand RCE)
CVE-2026-69097 / GHSA-3rp5-jjmw-4wv2
More information
Details
Summary
In GitPython
<= 3.1.52, the config writer neutralizes only CR, LF, and NUL in configuration names, but writes section names into the[...]header with no other escaping. A section/subsection name that contains] [ "closes the intended header and opens a second same-line section, injecting an arbitrary config directive — with no newline required. Because a submodule name is attacker-controlled data (it comes from a repository's.gitmodules, or from an application that lets a user name a submodule) and is written verbatim into the parent repository's trusted.git/config, an attacker can setcore.sshCommand(oralias.*,core.pager,core.fsmonitor) and achieve remote code execution on the victim's next git operation. Likely CWE-74 (Injection).This is a distinct variant of the injection addressed by GHSA-mv93-w799-cj2w / GHSA-v87r-6q3f-2j67: those fixed newline injection into config values/names (patched in 3.1.50); the
[r\n\x00]guard added for them does not stop a same-line section break inside a name.Details
The only guard applied to section/option names before writing is
_assure_config_name_safe, which uses a regex that matches solely CR/LF/NUL:git/config.py:75,897-899(GitPython 3.1.52):The name is then serialized into the header with no escaping of
],[,", space,=or#:git/config.py:693:For submodules the name is wrapped as
submodule "<name>"(git/objects/submodule/util.py:39,return f'submodule "{name}"'), which supplies the balancing quote. A submodule named:therefore serializes to the header
[submodule "x"] [core] sshCommand=CMD #"]. git parses everything after the first]on that line as a fresh section, yieldingcore.sshCommand=CMD(the trailing#"]is an inline comment). No CR/LF/NUL appears, so_assure_config_name_safenever fires.The attacker-controlled name reaches this sink through documented public entry points that write it into the parent repository's
.git/config:Repo.create_submodule(name=<untrusted>, ...)→Submodule.add→git/objects/submodule/base.py:619writer.set_value(sm_section(name), "url", url)— a single call, no hostile remote required.Repo.clone_from(<hostile url>)+repo.submodule_update(init=True)→git/objects/submodule/base.py:855writer.set_value(sm_section(self.name), "url", self.url), whereself.nameis read unvalidated from the cloned repo's.gitmodules.Asymmetry: the sibling class is blocked — a newline in a config value, e.g.
set_value("core", "editor", "x\n\tsshCommand=CMD"), raisesValueError. The section-name bracket payload is not caught by the same guard.PoC
Single self-contained script, run against the pinned release in an ephemeral environment. Non-destructive: the injected value is an inert marker, verified parse-only with
git config --get; no ssh/fetch/push is run and nothing is executed.Run:
Observed output:
The benign name yields a single clean
[submodule "docs"]section; the malicious name yields an injectedcore.sshCommand. Deterministic across runs. The payload must use balanced double-quotes (an unbalanced"makes git reject the header); thesubmodule "<name>"wrapper balances them automatically.Impact
Arbitrary attacker-controlled write into the victim's repository-local
.git/config, which git fully trusts.core.sshCommandis executed as the ssh transport command on the victim's next ssh git operation (fetch/pull/push), giving remote code execution; other injectable keys (alias.*,core.pager,core.fsmonitor) fire on more common operations. Reachable in default configuration through two realistic paths:Repo.create_submodule(name=...)(single call); orRepo.clone_fromof an untrusted repository followed bysubmodule_update— the canonical submodule threat model, where the malicious name is read from the cloned.gitmodules.No non-default git settings are required. Primarily a Unix vector: on Windows the
"in the resulting.git/modules/<name>directory name can abort the fresh-clone write branch (the direct config-API andcreate_submodulesinks are unaffected).Recommended fix
Reject or escape configuration section/subsection/option names that contain
],[,", or leading/trailing whitespace (or apply git's own section-name escaping) in_assure_config_name_safe/write_section, rather than only CR/LF/NUL. Validating submodule names before they reachsm_sectionwould additionally close the clone-driven path.Severity
CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Incomplete unsafe_git_clone_options denylist omits --template enabling arbitrary command execution via clone hooks
GHSA-6p8h-3wgx-97gf
More information
Details
Summary
GitPython's
unsafe_git_clone_optionsdenylist omits--template.git clone --template=<dir>copies<dir>/hooks/into the new repository and runs them (post-checkoutfires during clone), so a caller who can influence clone options can achieve arbitrary command execution in the defaultallow_unsafe_options=Falseconfiguration.Root Cause
base.py:145-152definesunsafe_git_clone_options = ["--upload-pack","-u","--config","-c"]—--templateis absent. The guard candidate['--template']passescheck_unsafe_options(verified). git copies the hook directory and executespost-checkoutat checkout time. git'sprotocol.allow/GIT_ALLOW_PROTOCOLdo not gate--template; the incomplete denylist is the only defense.Impact
Arbitrary OS command execution during clone (default config). Requires an attacker-readable directory containing an executable hook — a genuine second precondition (realistic via shared filesystems, upload dirs,
/tmp, or attacker-writable network paths), reflected as AC:H.Proof of Concept
Attack Chain
<dir>/hooks/post-checkout(chmod +x). Guard: n/a (filesystem).Repo.clone_from(url, path, template='<dir>'). Guard:check_unsafe_options(candidates=['--template'], unsafe=unsafe_git_clone_options). Bypass proof:--templatenot on the denylist -> passes (verified candidate['--template'], no error).post-checkoutat checkout. Impact: ACE, default config (verified marker created).Bypass Evidence
Live-verified on HEAD (tag 3.1.53): guard candidate
['--template']passed with no error; stagedpost-checkouthook executed duringclone_from, creating the marker. Independent of the value-smuggle bypass (--templateis a legitimate long option that survives any single-char-value fix). Not covered by any existing advisory.Affected Versions
<= 3.1.53Suggested Fix
Add
--template(and audit for other hook/exec-influencing options) tounsafe_git_clone_options.Reported by zx (Jace) — GitHub: @manus-use
Severity
CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:HReferences
This data is provided by the GitHub Advisory Database (CC-BY 4.0).
GitPython: Arbitrary file overwrite via git diff --output argument injection in Diffable.diff (key- and value-controlled)
GHSA-fjr4-x663-mwxc
More information
Details
Summary
Diffable.diff()forwards**kwargsstraight intodiff/diff_treewith nocheck_unsafe_optionsguard.Diffableis mixed intoCommit,Tree,IndexFile, andSubmodule, giving a broad surface.git diff --output=<path>writes real patch content to an attacker-chosen path, enabling arbitrary file overwrite.Root Cause
diff.py:188-283builds and runs the diff command with nocheck_unsafe_optionsanywhere in the method (grep-confirmed). Additionallydiff.py:265doesargs.insert(0, other), placing the caller-suppliedotherref BEFORE the--separator, so a value of--output=/pathis parsed by git as an option — a value-only control path requiring no kwarg key.Impact
Overwrite/corrupt any file at process privilege with attacker-chosen path (e.g.
~/.ssh/authorized_keys, configs, lockfiles). Content is real diff/patch bytes (attacker-influenced). Per the skill's rule, controlling WHICH file is overwritten = I:H regardless of content constraints.Proof of Concept
Attack Chain
commit.diff(other=<user ref>)withother = "--output=/home/app/.ssh/authorized_keys". Guard: none inDiffable.diff. Bypass proof: nocheck_unsafe_optionsin the method body (grep);otherinserted pre---at diff.py:265.git diff-tree <sha> --output=/home/app/.ssh/authorized_keys -r ...-> git opens+truncates the target then writes diff content. Impact: overwrite/corrupt any file at process privilege (attacker chooses the path). Verified argv and victim overwrite live.Bypass Evidence
Live-verified on HEAD (tag 3.1.53): both key-control (
output=) and value-control (other='--output=...') overwrote a victim file with real diff-tree content; argv confirmed['git','diff-tree','<sha>','--output=/victim','-r',...]. This is the same value-control model GHSA-956x deemed fix-worthy foriter_commits(rev='--output=')— butdiffis a distinct, unguarded sink NOT touched by that fix.Affected Versions
<= 3.1.53Suggested Fix
Add
check_unsafe_optionstoDiffable.diff(mirroringiter_commits/archive), and/or place--end-of-optionsbefore theotherref so it cannot be parsed as an option.Reported by zx (Jace) — GitHub: @manus-use
Severity
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:HReferences