Skip to content

Commit 2589bc3

Browse files
codexByron
authored andcommitted
Reject syntax-bearing git config option names
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.
1 parent 6e61b1d commit 2589bc3

2 files changed

Lines changed: 42 additions & 0 deletions

File tree

git/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@
7575
UNSAFE_CONFIG_CHARS_RE = re.compile(r"[\r\n\x00]")
7676
"""Characters that cannot be safely written in config names or values."""
7777

78+
VALID_CONFIG_OPTION_NAME_RE = re.compile(r"^[A-Za-z0-9_.-]+$")
79+
"""Pattern for option names that can be written without changing config syntax."""
80+
7881

7982
class MetaParserBuilder(abc.ABCMeta): # noqa: B024
8083
"""Utility class wrapping base-class methods into decorators that assure read-only
@@ -897,6 +900,8 @@ def _value_to_string_safe(self, value: Union[str, bytes, int, float, bool]) -> s
897900
def _assure_config_name_safe(self, name: "cp._SectionName", label: str) -> None:
898901
if isinstance(name, str) and UNSAFE_CONFIG_CHARS_RE.search(name):
899902
raise ValueError("Git config %s names must not contain CR, LF, or NUL" % label)
903+
if label == "option" and isinstance(name, str) and not VALID_CONFIG_OPTION_NAME_RE.fullmatch(name):
904+
raise ValueError("Git config option names may contain only letters, digits, '-', '_', or '.'")
900905
if label == "section" and isinstance(name, str):
901906
in_quotes = False
902907
escaped = False

test/test_config.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,43 @@ def test_set_value_rejects_unsafe_section_and_option_names(self, rw_dir):
194194
self.assertEqual(git_config.get_value("user", "name"), "safe")
195195
self.assertFalse(git_config.has_section("core"))
196196

197+
@with_rw_directory
198+
def test_writer_rejects_invalid_option_names(self, rw_dir):
199+
config_path = osp.join(rw_dir, "config")
200+
bad_options = (
201+
"name=value",
202+
"name#comment",
203+
"name;comment",
204+
"name with space",
205+
"name\twith-tab",
206+
"name[section",
207+
"name]section",
208+
"name:colon",
209+
'name"quote',
210+
"name\\escape",
211+
)
212+
213+
with GitConfigParser(config_path, read_only=False) as git_config:
214+
git_config.add_section("user")
215+
for bad_option in bad_options:
216+
with pytest.raises(ValueError, match="option name"):
217+
git_config.set("user", bad_option, "unsafe")
218+
with pytest.raises(ValueError, match="option name"):
219+
git_config.set_value("user", bad_option, "unsafe")
220+
with pytest.raises(ValueError, match="option name"):
221+
git_config.add_value("user", bad_option, "unsafe")
222+
223+
git_config.set_value("user", "safe-option1", "safe")
224+
git_config.set_value("user", "safe_option2", "safe")
225+
git_config.set_value("user", "3safe_option", "safe")
226+
git_config.set_value("user", "safe.option3", "safe")
227+
228+
with GitConfigParser(config_path, read_only=True) as git_config:
229+
self.assertEqual(git_config.get_value("user", "safe-option1"), "safe")
230+
self.assertEqual(git_config.get_value("user", "safe_option2"), "safe")
231+
self.assertEqual(git_config.get_value("user", "3safe_option"), "safe")
232+
self.assertEqual(git_config.get_value("user", "safe.option3"), "safe")
233+
197234
@with_rw_directory
198235
def test_writer_rejects_unquoted_section_terminators(self, rw_dir):
199236
config_path = osp.join(rw_dir, "config")

0 commit comments

Comments
 (0)