Skip to content

fix: harden config parsing boundaries - #2211

Merged
Byron merged 2 commits into
mainfrom
config-sanitize-more
Aug 10, 2026
Merged

fix: harden config parsing boundaries#2211
Byron merged 2 commits into
mainfrom
config-sanitize-more

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.

Summary

  • Preserve parsed multiline Git config values when an unrelated write reserializes the file, keeping each value structurally intact.
  • Disable include merging for .gitmodules, so repository-controlled submodule configuration cannot cause unrelated local files to be opened.

Advisories

Both advisories remain private/under triage, so reproduction and exploit details are intentionally omitted here.

Advisory summary

GHSA-284h-m62q-gf8w

  • Severity: High
  • Package: GitPython (pip)
  • Affected versions: <= 3.1.58
  • Patched versions: not yet assigned
  • CVE: not yet assigned

GHSA-7833-fr7j-v32q

  • Severity: High
  • Package: GitPython (pip)
  • Affected versions: <= 3.1.58
  • Patched versions: not yet assigned
  • CVE: not yet assigned

Baselines

  • Git config.c at cf5497b14c5a serializes embedded LF with an escape rather than as a physical config line.
  • Existing GitPython hardening in 41ecc6a4 disables include merging for another write-sensitive config parser.

Validation

  • Focused regressions for both advisories
  • pytest -q test/test_config.py (35 passed, 2 skipped)
  • pytest -q (passed after each commit)
  • Targeted Ruff lint and format checks
  • git diff --check

@Byron Byron changed the title fix: preserve multiline config values when writing fix: harden config parsing boundaries Aug 10, 2026
Byron and others added 2 commits August 10, 2026 13:21
<!-- agent -->
GitConfigParser decoded valid multiline values into embedded newlines, but
_write() serialized those newlines as indented physical lines. Rewriting an
otherwise unchanged config could therefore change its meaning to Git.

Serialize resident multiline values with Git-compatible escapes inside a
quoted continuation, preserving GitPython read compatibility while keeping
each option structurally intact. This addresses GHSA-284h-m62q-gf8w.

The regression starts with an inert multiline value, performs an unrelated
write, and verifies with both GitPython and git config that it remains one
value and does not create another option.

Git baseline: config.c parse_value() and write_pair() at cf5497b14c5a escape
embedded LF as \\n rather than emitting it as a physical config line.

Assisted-by: GPT 5.6
Co-authored-by: GPT 5.6 <codex@openai.com>
<!-- agent -->
Submodule configuration is read from .gitmodules, whose contents may come
from an untrusted repository. Its parser inherited merge_includes=True and
could therefore open files named by include directives during ordinary
submodule enumeration.

Disable include merging at the SubmoduleConfigParser construction site. This
matches Repo.config_writer() hardening from 41ecc6a and addresses
GHSA-7833-fr7j-v32q without changing include behavior for trusted config
parsers.

The regression points .gitmodules at a non-config file and verifies the
submodule entry remains readable without opening the included path.

Assisted-by: GPT 5.6
Co-authored-by: GPT 5.6 <codex@openai.com>
@Byron
Byron force-pushed the config-sanitize-more branch from 5326ebf to ef7568e Compare August 10, 2026 11:31
@Byron
Byron marked this pull request as ready for review August 10, 2026 11:55
Copilot AI lite review requested due to automatic review settings August 10, 2026 11:55
@Byron
Byron merged commit a5e047d into main Aug 10, 2026
54 checks passed
@Byron
Byron deleted the config-sanitize-more branch August 10, 2026 11:57

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef7568e3b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread git/config.py
Comment on lines +710 to +711
value = value.replace("\\", "\\\\").replace('"', '\\"')
value = '"%s\\\n"' % value.replace("\n", "\\n").replace("\t", "\\t").replace("\b", "\\b")

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.

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.

Useful? React with 👍 / 👎.

Comment thread git/config.py
value = self._value_to_string(v)
if any(char in value for char in '\n\t\b\\"'):
value = value.replace("\\", "\\\\").replace('"', '\\"')
value = '"%s\\\n"' % value.replace("\n", "\\n").replace("\t", "\\t").replace("\b", "\\b")

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.

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.

Useful? React with 👍 / 👎.

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

Hardens Git config parsing/serialization to prevent unsafe boundary crossings when rewriting config files and when parsing repository-controlled .gitmodules, aligning GitPython behavior more closely with Git’s escaping and include-handling expectations.

Changes:

  • Update GitConfigParser writer to escape/quote values containing special characters (notably multiline values) instead of emitting physical continuation lines.
  • Disable [include] merging when parsing .gitmodules via SubmoduleConfigParser.
  • Add regression tests covering multiline rewrite safety, special-character escaping, and .gitmodules include isolation.

Reviewed changes

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

File Description
git/config.py Adjusts config serialization to quote/escape special characters to preserve multiline values safely.
git/objects/submodule/base.py Forces .gitmodules parsing to use merge_includes=False to avoid opening arbitrary local files.
test/test_config.py Adds tests to ensure multiline rewrite doesn’t create unintended options and that special chars round-trip and match git config.
test/test_submodule.py Adds a test ensuring .gitmodules does not merge includes (and thus doesn’t parse unrelated files).

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

Comment thread git/config.py
Comment on lines +708 to +712
value = self._value_to_string(v)
if any(char in value for char in '\n\t\b\\"'):
value = value.replace("\\", "\\\\").replace('"', '\\"')
value = '"%s\\\n"' % value.replace("\n", "\\n").replace("\t", "\\t").replace("\b", "\\b")
fp.write(("\t%s = %s\n" % (key, value)).encode(defenc))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

While it's correct, it seems there is no imminent security risk from this, so let's leave it to the advisory-bots to find something related to this.

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