Skip to content

Preserve Git config escape semantics - #2213

Merged
Byron merged 1 commit into
mainfrom
config-sanitizer-follow-up
Aug 11, 2026
Merged

Preserve Git config escape semantics#2213
Byron merged 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.

@Byron
Byron force-pushed the config-sanitizer-follow-up branch from 1c015bd to ef3fb51 Compare August 10, 2026 12:52
@Byron Byron linked an issue Aug 11, 2026 that may be closed by this pull request
@Byron
Byron force-pushed the config-sanitizer-follow-up branch 2 times, most recently from fa66d1e to cc7b973 Compare August 11, 2026 11:57
@Byron
Byron marked this pull request as ready for review August 11, 2026 11:57
Copilot AI lite review requested due to automatic review settings August 11, 2026 11:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates GitConfigParser’s read/write behavior to preserve Git’s config quoted-escape semantics (e.g., \n, \", \\) without corrupting non-ASCII text, and adds safety checks to prevent rewriting configs that would emit unsafe control characters.

Changes:

  • Replace the unicode_escape-based escape decoder with a Git-oriented backslash escape translation that avoids non-ASCII mojibake.
  • Decode well-formed single-line quoted values with backslash escapes, while preserving malformed quoted values containing unescaped quotes.
  • Add write-time validation to abort rewrites if any stored value contains CR or NUL; expand writer quoting triggers and add regression tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
git/config.py Adjusts quoted escape decoding, expands quoting rules during serialization, and rejects CR/NUL values before truncating/writing.
test/test_config.py Extends writer escaping tests and adds coverage for preserving escaped + non-ASCII values, plus CR/NUL rewrite rejection.
Suppressed comments (1)

test/test_config.py:236

  • The subprocess assertion compares git config --get output bytes to value.encode() (UTF-8). Since GitConfigParser writes the file using defenc, and git config will output the raw bytes it reads, this comparison should also use defenc to avoid false failures when defenc != 'utf-8'.
                self.assertEqual(
                    subprocess.run(
                        ["git", "config", "--file", config_path, "--get", "section.%s" % key],
                        stdout=subprocess.PIPE,
                        check=True,
                    ).stdout,
                    value.encode() + b"\n",
                    "git should read rewritten values with the same semantics",

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/test_config.py
<!-- agent -->
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.

Quote values containing Git comment delimiters (# and ;) or leading/trailing
whitespace so Git does not truncate or trim their data. Escape LF, tab,
backspace, quote, and backslash, while rejecting carriage returns and NULs
before opening the destination.

Regression coverage round-trips these values through both GitPython and git
config and verifies unsafe control characters cannot alter the original file.
Behavior follows Git config.c parse_value() and write_pair().

Assisted-by: GPT 5.6
Co-authored-by: GPT 5.6 <codex@openai.com>
Copilot AI review requested due to automatic review settings August 11, 2026 12:20
@Byron
Byron force-pushed the config-sanitizer-follow-up branch from cc7b973 to eefa7e4 Compare August 11, 2026 12:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (1)

git/config.py:786

  • In the CR/NUL preflight check, _value_to_string(raw_value) is called twice per value. This is redundant work and can be surprisingly expensive if _value_to_string does non-trivial formatting. Compute it once and reuse it for both checks.
                    for raw_value in values:
                        if "\r" in self._value_to_string(raw_value) or "\x00" in self._value_to_string(raw_value):
                            raise ValueError("Git config values must not contain CR or NUL")

@Byron
Byron merged commit 4b9afe9 into main Aug 11, 2026
54 checks passed
@Byron
Byron deleted the config-sanitizer-follow-up branch August 11, 2026 13:11
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.

Using # or ; in config values

2 participants