Skip to content

Harden config and Git option validation - #2204

Draft
Byron wants to merge 3 commits into
mainfrom
security-fixes
Draft

Harden config and Git option validation#2204
Byron wants to merge 3 commits into
mainfrom
security-fixes

Conversation

@Byron

@Byron Byron commented Aug 2, 2026

Copy link
Copy Markdown
Member

Tasks

This section is for Byron only. Models continuing this PR must not add, remove, check, uncheck, rename, or reorder checkboxes here.

  • refackiew

Everything below this line was generated by Codex GPT-5.

Created by Codex on behalf of Byron. Byron will review before this is ready to merge.

Security advisories

Exploit payloads and unnecessary reproduction detail are intentionally omitted from this public summary.

Behavior

Config writers reject option-name characters that can alter config syntax while retaining GitPython's established safe character set. Unsafe Git option checks now inspect the exact joined short-option form emitted when splitting is disabled. IndexFile.from_tree, reset, and merge_tree reject caller-controlled --index-output forms unless explicitly opted out.

Implementation

The config mutation APIs share a syntax-safe option-name validator. _option_candidates now constructs joined candidates for unsplit one-character kwargs, covering every existing guarded caller. Both read-tree sinks use a shared unsafe-option list, with reset delegating to the guarded from_tree path.

Each change includes a regression test and a broader class audit. The audits covered all config option-creating APIs, all unsafe-option guard call sites, and all read-tree invocations in the repository; no additional same-class sink was found.

Git baseline

Behavior was checked against the local Git reference checkout at commit cf5497b14c5a24f10c13f7e0ee85cb95af13ea6a (v2.55.0.windows.3-16-gcf5497b14). Its config parser, clustered short-option loop, and read-tree --index-output parsing informed the GitPython-specific implementations.

Validation

  • pytest -q test/test_config.py test/test_git.py test/test_index.py: 164 passed, 2 skipped, 3 expected xfailed
  • Ruff lint: passed
  • Ruff format check: passed
  • basedpyright on the three production modules: 0 errors; one environment-only missing-source warning for typing_extensions
  • git diff --check: passed

codex added 3 commits August 2, 2026 09:23
GHSA-jm78-9fvv-mhgr reports that config option names containing Git
syntax can be serialized as unintended directives. A regression test showed
that set, set_value, and add_value accepted delimiter, comment, bracket, and
whitespace characters in option names.

Restrict written option names to GitPython's established safe character set of
letters, digits, hyphens, underscores, and dots. This blocks characters that
can change config syntax while preserving option names historically supported
by the writer and SectionConstraint.

A broader audit confirmed that every public option-creating config API and
SectionConstraint delegate reaches this validator; no separate config writer
sink was found. The behavior was checked against Git cf5497b14, and the full
config test module plus dotted-option regression pass.
GHSA-wvpp-8hx9-p66j reports that unsafe-option checks omitted the value
joined to a one-character option when split_single_char_options was false. A
regression test reproduced the mismatch: GitPython checked only -n even though
it emitted a joined -nVALUE token that Git parses as clustered short options.

Collect the exact joined token for unsplit one-character keyword arguments so
the existing clustered-short-option validation sees every option character.
The split form and long-option behavior remain unchanged.

A broader audit confirmed that all guarded keyword-forwarding APIs use
_option_candidates, including clone, ls-remote, fetch, pull, push, archive,
revision, diff, checkout-index, and tag paths. Git cf5497b14 confirms repeated
short-option parsing within a joined token. Focused candidate and unsafe-option
tests pass.
GHSA-4gmw-gg2m-w46p reports that caller-controlled treeish arguments could
be parsed by git read-tree as --index-output and select an arbitrary output
path. A regression test showed that from_tree reached Git instead of raising
UnsafeOptionError; the same unchecked path was reachable through reset and
both merge_tree treeish positions.

Add the project-standard unsafe-option guard and explicit opt-out to
from_tree, merge_tree, and reset. Check positional and keyword candidates so
abbreviations and alternate forwarding forms are covered before read-tree
runs.

A broader audit found only two read-tree sinks in the codebase; both are now
guarded, and reset delegates to the guarded from_tree path. The only remaining
index-output use is GitPython's controlled temporary index. Git cf5497b14
confirms read-tree parses this path-taking option before tree arguments.
Focused index tests and Ruff checks pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants