GHSA-9RJ7-RF2P-W77R
Vulnerability from github – Published: 2026-08-07 15:36 – Updated: 2026-08-07 15:36Summary
Repo.init() forwards **kwargs verbatim to git init with no unsafe-option guard and no allow_unsafe_options parameter. git init --template=<dir> copies <dir>/hooks/* into the new repo's .git/hooks, so an attacker-controlled template kwarg plants a hook that executes on the next git operation → arbitrary code execution. --template is already recognized as unsafe for clone (it is on unsafe_git_clone_options, and GHSA-6p8h-3wgx-97gf covers the clone path), but Repo.init is a distinct method that never received a guard and needs an independent fix.
Root Cause
Repo.init(path, mkdir, odbt, expand_vars, **kwargs) is a bare git.init(**kwargs) (git/repo/base.py:1435) with no check_unsafe_options and no allow_unsafe_options.
Impact
Arbitrary code execution (hook fires on next git op) at the privileges of the host process. Two preconditions raise attack complexity (AC:H): the app must forward a template= kwarg (KEY control) AND the attacker must stage an executable hook directory at a known path — the same profile GHSA-6p8h-3wgx-97gf accepted as HIGH for the clone path. Default allow_unsafe_options is irrelevant here because Repo.init has no guard at all.
Proof of Concept
# attacker stages /evil/hooks/post-commit (executable)
from git import Repo
Repo.init(path, template="/evil")
# next commit runs /evil/hooks/post-commit -> ACE
Attack Chain
- Entry: attacker stages
/evil/hooks/post-commit(executable) and gets the app to callRepo.init(path, template='/evil'). - Check: NONE on
Repo.init. Bypass proof: base.py:1435 is a baregit.init(**kwargs). argv (observed):['git','init','--template=/evil']. - Sink: git copies
/evil/hooks/post-commit→<repo>/.git/hooks/post-commit. - Impact: next commit runs the hook → arbitrary code execution.
Bypass Evidence
Independently reproduced (gate harness): Repo.init(dst, template='<evil>') → argv ['git','init','--template=<evil>'] unguarded; hook copied into .git/hooks/post-commit; after git commit the INIT_ACE marker was created. --separate-git-dir=<path> is a parallel arbitrary-redirect vector through the same unguarded sink (value control only).
Affected Versions
GitPython <= 3.1.57 (unguarded git.init(**kwargs) present verbatim on the latest release tag).
Suggested Fix
Add a check_unsafe_options guard (with an allow_unsafe_options parameter) to Repo.init, consulting a denylist that includes --template and --separate-git-dir (path-taking / hook-installing options).
Reported by zx (Jace) — GitHub: @manus-use
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 3.1.57"
},
"package": {
"ecosystem": "PyPI",
"name": "GitPython"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.1.58"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [],
"database_specific": {
"cwe_ids": [
"CWE-88",
"CWE-94"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-07T15:36:43Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n`Repo.init()` forwards `**kwargs` verbatim to `git init` with no unsafe-option guard and no `allow_unsafe_options` parameter. `git init --template=\u003cdir\u003e` copies `\u003cdir\u003e/hooks/*` into the new repo\u0027s `.git/hooks`, so an attacker-controlled `template` kwarg plants a hook that executes on the next git operation \u2192 arbitrary code execution. `--template` is already recognized as unsafe for clone (it is on `unsafe_git_clone_options`, and GHSA-6p8h-3wgx-97gf covers the clone path), but `Repo.init` is a distinct method that never received a guard and needs an independent fix.\n\n## Root Cause\n`Repo.init(path, mkdir, odbt, expand_vars, **kwargs)` is a bare `git.init(**kwargs)` (git/repo/base.py:1435) with no `check_unsafe_options` and no `allow_unsafe_options`.\n\n## Impact\nArbitrary code execution (hook fires on next git op) at the privileges of the host process. Two preconditions raise attack complexity (AC:H): the app must forward a `template=` kwarg (KEY control) AND the attacker must stage an executable hook directory at a known path \u2014 the same profile GHSA-6p8h-3wgx-97gf accepted as HIGH for the clone path. Default `allow_unsafe_options` is irrelevant here because `Repo.init` has no guard at all.\n\n## Proof of Concept\n```python\n# attacker stages /evil/hooks/post-commit (executable)\nfrom git import Repo\nRepo.init(path, template=\"/evil\")\n# next commit runs /evil/hooks/post-commit -\u003e ACE\n```\n\n## Attack Chain\n1. Entry: attacker stages `/evil/hooks/post-commit` (executable) and gets the app to call `Repo.init(path, template=\u0027/evil\u0027)`.\n2. Check: NONE on `Repo.init`. Bypass proof: base.py:1435 is a bare `git.init(**kwargs)`. argv (observed): `[\u0027git\u0027,\u0027init\u0027,\u0027--template=/evil\u0027]`.\n3. Sink: git copies `/evil/hooks/post-commit` \u2192 `\u003crepo\u003e/.git/hooks/post-commit`.\n4. Impact: next commit runs the hook \u2192 arbitrary code execution.\n\n## Bypass Evidence\nIndependently reproduced (gate harness): `Repo.init(dst, template=\u0027\u003cevil\u003e\u0027)` \u2192 argv `[\u0027git\u0027,\u0027init\u0027,\u0027--template=\u003cevil\u003e\u0027]` unguarded; hook copied into `.git/hooks/post-commit`; after `git commit` the `INIT_ACE` marker was created. `--separate-git-dir=\u003cpath\u003e` is a parallel arbitrary-redirect vector through the same unguarded sink (value control only).\n\n## Affected Versions\n`GitPython \u003c= 3.1.57` (unguarded `git.init(**kwargs)` present verbatim on the latest release tag).\n\n## Suggested Fix\nAdd a `check_unsafe_options` guard (with an `allow_unsafe_options` parameter) to `Repo.init`, consulting a denylist that includes `--template` and `--separate-git-dir` (path-taking / hook-installing options).\n\n---\nReported by **zx (Jace)** \u2014 GitHub: @manus-use",
"id": "GHSA-9rj7-rf2p-w77r",
"modified": "2026-08-07T15:36:43Z",
"published": "2026-08-07T15:36:43Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/security/advisories/GHSA-9rj7-rf2p-w77r"
},
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/pull/2204"
},
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/commit/d9ddb55bdc66"
},
{
"type": "PACKAGE",
"url": "https://github.com/gitpython-developers/GitPython"
},
{
"type": "WEB",
"url": "https://github.com/gitpython-developers/GitPython/releases/tag/3.1.58"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
],
"summary": "GitPython: Unguarded git option forwarding in Repo.init enables arbitrary command execution via --template clone hooks"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.