Skip to content

Preserve Git config escape semantics - #2213

Draft
Byron wants to merge 1 commit into
mainfrom
config-sanitizer-follow-up
Draft

Preserve Git config escape semantics#2213
Byron wants to merge 1 commit into
mainfrom
config-sanitizer-follow-up

Conversation

@Byron

@Byron Byron commented Aug 10, 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.

Reported issue

P1 Badge Preserve Git semantics for quoted escaped values

When an existing config contains a standard quoted escape such as k = "first\nsecond", _read() retains the quoted spelling because it does not decode single-line quoted values containing escapes. Re-escaping that representation here rewrites it as data, so an unrelated update makes Git see literal outer quotes and a literal \n rather than the original newline; quoted backslashes and escaped quotes are similarly altered. Normalize these parsed values before escaping, or preserve their original representation.

P1 Badge Keep non-ASCII text intact through the escape decoder

When a value combines non-ASCII text with any trigger for this branch, such as café\path or a non-ASCII value containing a tab, this forced multiline form is later processed by _read()'s string_decode, which encodes using the filesystem encoding and decodes with unicode_escape. On UTF-8 systems that changes café to café; GitPython therefore returns corrupted data after the first write, and a later unrelated write persists the mojibake into the config file. Escape decoding needs to operate without reinterpreting the encoded non-ASCII bytes.

Also:

The new writer escapes LF/tab/backspace/quote/backslash, but it still allows carriage returns and NULs to be written verbatim. Because the reader’s string_decode(...).decode('unicode_escape') can materialize \r or \x00 into actual \r/\x00 characters in-memory, an attacker-controlled config could be rewritten into a file containing unsafe control characters when an unrelated write occurs. Please either (a) escape \r during serialization (similar to \n) and (b) reject NULs on write, or otherwise ensure \r/NUL cannot reach disk.

Fix

  • Decode Git's quoted value escapes without passing non-ASCII text through unicode_escape.
  • Normalize existing single-line quoted escaped values before serialization.
  • Reject parsed CR and NUL values before opening or truncating the destination config.

Git behavior was checked against cf5497b14c5a, specifically config.c::parse_value().

Validation

  • pytest -q test/test_config.py — 37 passed, 2 skipped
  • ruff check git/config.py test/test_config.py
  • ruff format --check git/config.py test/test_config.py
  • mypy git/config.py
  • git diff --check
  • pre-commit run --all-files
  • Codex review completed for c35c2ac8; its P1 pre-truncation validation finding was fixed.
  • Codex review completed without findings for 1c015bd5.
  • Codex review completed without findings for ef3fb518 after the CI lint fix.

Decode Git-supported quoted value escapes directly instead of routing UTF-8 text through Python unicode_escape. This preserves newlines, quotes, backslashes, and non-ASCII text when an unrelated config update rewrites existing values.

Reject carriage returns and NULs at the shared writer sink so values parsed from existing files cannot place unsafe control characters into rewritten config files. Git itself does not accept \r as a config escape, so rejection is the compatible safe behavior.

Regression coverage rewrites representative quoted values, compares GitPython and git-config results, and verifies CR/NUL rejection. Validated with the complete config test module, Ruff, and mypy.

Behavior checked against Git cf5497b14c5a, particularly config.c parse_value(), which supports \n, \t, \b, \\, and \" and rejects unknown escapes.
@Byron
Byron force-pushed the config-sanitizer-follow-up branch from 1c015bd to ef3fb51 Compare August 10, 2026 12:52
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