Summary
Commit.count() forwards **kwargs into rev_list with no check_unsafe_options guard (the guard exists only in the sibling iter_items, commit.py:341). git rev-list --output=<path> opens and truncates the target file to 0 bytes before revision parsing, so count(output='/victim') destroys/blanks an arbitrary file.
Root Cause
commit.py:290-291 calls self.repo.git.rev_list(self.hexsha, **kwargs) with no check_unsafe_options and no allow_unsafe_options parameter. The sibling iter_items (commit.py:341) is guarded; count is not. This is a distinct, uncovered sink — GHSA-956x-8gvw-wg5v fixed iter_commits/blame, not count.
Impact
Destroy/blank an arbitrary file at process privilege (integrity/availability). Reachability is key-control only (count uses self.hexsha, not a user ref), and the write is a 0-byte truncation (no content control), so MEDIUM.
Proof of Concept
commit.count(output='/path/to/victim') # victim truncated to 0 bytes (verified)
# control: commit.iter_commits(output=...) raises UnsafeOptionError
Attack Chain
- Entry: app forwards user options ->
commit.count(output='/victim'). Guard: none. Bypass proof: iter_commits(output=) raises UnsafeOptionError; count(output=) does not — verified side-by-side.
- Sink:
git rev-list <sha> --output=/victim -> file truncated to 0 bytes. Impact: destroy/blank arbitrary file.
Bypass Evidence
Live-verified on HEAD (tag 3.1.53): count(output=<victim>) truncated a pre-existing file to 0 bytes; guarded iter_commits(output=) raised UnsafeOptionError. Same CNA-accepted "app forwards user options dict" model as GHSA-956x-8gvw-wg5v's archive(**kwargs). Uncovered sink, not a duplicate.
Affected Versions
<= 3.1.53
Suggested Fix
Add check_unsafe_options to Commit.count (mirroring iter_items).
Reported by zx (Jace) — GitHub: @manus-use
Summary
Commit.count()forwards**kwargsintorev_listwith nocheck_unsafe_optionsguard (the guard exists only in the siblingiter_items, commit.py:341).git rev-list --output=<path>opens and truncates the target file to 0 bytes before revision parsing, socount(output='/victim')destroys/blanks an arbitrary file.Root Cause
commit.py:290-291callsself.repo.git.rev_list(self.hexsha, **kwargs)with nocheck_unsafe_optionsand noallow_unsafe_optionsparameter. The siblingiter_items(commit.py:341) is guarded;countis not. This is a distinct, uncovered sink — GHSA-956x-8gvw-wg5v fixediter_commits/blame, notcount.Impact
Destroy/blank an arbitrary file at process privilege (integrity/availability). Reachability is key-control only (
countusesself.hexsha, not a user ref), and the write is a 0-byte truncation (no content control), so MEDIUM.Proof of Concept
Attack Chain
commit.count(output='/victim'). Guard: none. Bypass proof:iter_commits(output=)raises UnsafeOptionError;count(output=)does not — verified side-by-side.git rev-list <sha> --output=/victim-> file truncated to 0 bytes. Impact: destroy/blank arbitrary file.Bypass Evidence
Live-verified on HEAD (tag 3.1.53):
count(output=<victim>)truncated a pre-existing file to 0 bytes; guardediter_commits(output=)raised UnsafeOptionError. Same CNA-accepted "app forwards user options dict" model as GHSA-956x-8gvw-wg5v'sarchive(**kwargs). Uncovered sink, not a duplicate.Affected Versions
<= 3.1.53Suggested Fix
Add
check_unsafe_optionstoCommit.count(mirroringiter_items).Reported by zx (Jace) — GitHub: @manus-use