Skip to content

Commit f6a043a

Browse files
authored
Merge pull request #2271 from Keerthana-64/commit-identity-headers
fix(commit): reject identity fields that alter commit headers
2 parents b5d6a79 + fbbecc6 commit f6a043a

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

‎git/objects/commit.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,10 @@ def create_from_tree(
662662
:return:
663663
:class:`Commit` object representing the new commit.
664664
665+
:raise ValueError:
666+
If the name or email of the author or committer contains ``<``, ``>`` or a
667+
line feed, as these would change the identity headers of the commit.
668+
665669
:note:
666670
Additional information about the committer and author are taken from the
667671
environment or from the git configuration. See :manpage:`git-commit-tree(1)`
@@ -786,6 +790,16 @@ def create_from_tree(
786790
# { Serializable Implementation
787791

788792
def _serialize(self, stream: BytesIO) -> "Commit":
793+
# An identity is written as "name <email> date" on a single header line, so a
794+
# line feed or an angle bracket inside a name or email moves those boundaries:
795+
# it can add header lines, end the headers early, or present another email.
796+
# Git drops these three characters when it writes an identity; refuse them
797+
# here before anything is written.
798+
for actor in (self.author, self.committer):
799+
for value in (actor.name, actor.email):
800+
if value and any(char in value for char in "<>\n"):
801+
raise ValueError("Commit identity %r must not contain '<', '>' or a line feed" % value)
802+
789803
write = stream.write
790804
write(("tree %s\n" % self.tree).encode("ascii"))
791805
for p in self.parents:

‎test/test_commit.py‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,49 @@ def test_invalid_commit(self):
424424
self.assertEqual(cmt.author.name, "E.Azer Ko�o�o�oculu", cmt.author.name)
425425
self.assertEqual(cmt.author.email, "azer@kodfabrik.com", cmt.author.email)
426426

427+
@with_rw_directory
428+
def test_identity_cannot_alter_headers(self, rw_dir):
429+
"""A name or email must not add header lines or present another identity."""
430+
rw_repo = Repo.init(osp.join(rw_dir, "test_identity_headers"))
431+
path = osp.join(str(rw_repo.working_tree_dir), "hello.txt")
432+
touch(path)
433+
rw_repo.index.add([path])
434+
tree = rw_repo.index.write_tree()
435+
service = Actor("Service", "service@example.com")
436+
forged = "committer Forged <forged@example.com> 0 +0000"
437+
438+
for name, email in (
439+
# A line feed ends the header line, so the remainder would become headers
440+
# of its own, which Git reads before the committer written after them.
441+
("User <user@example.com> 0 +0000\n" + forged, "user@example.com"),
442+
("User", "user@example.com> 0 +0000\n" + forged),
443+
# Angle brackets delimit the email, so these would present another one.
444+
("Forged <forged@example.com>", "user@example.com"),
445+
("User", "forged@example.com> <user@example.com"),
446+
("User>", "user@example.com"),
447+
("User", "<user@example.com"),
448+
):
449+
with self.subTest(name=name, email=email):
450+
identity = Actor(name, email)
451+
with self.assertRaises(ValueError):
452+
Commit.create_from_tree(rw_repo, tree, "message", head=True, author=identity, committer=service)
453+
with self.assertRaises(ValueError):
454+
Commit.create_from_tree(rw_repo, tree, "message", head=True, author=service, committer=identity)
455+
456+
# Nothing was committed along the way.
457+
assert not rw_repo.head.is_valid()
458+
459+
# Other punctuation is still written as given.
460+
author = Actor("Dr. J. O'Neil-Smith, Jr.", "user+tag@example.com")
461+
commit = Commit.create_from_tree(rw_repo, tree, "message", head=True, author=author, committer=service)
462+
stored = Commit(rw_repo, commit.binsha)
463+
self.assertEqual(stored.author, author)
464+
self.assertEqual(stored.committer, service)
465+
self.assertEqual(stored.message, "message")
466+
467+
with self.assertRaises(ValueError):
468+
commit.replace(author=Actor("User\n" + forged, "user@example.com"))
469+
427470
def test_gpgsig(self):
428471
cmt = self.rorepo.commit()
429472
with open(fixture_path("commit_with_gpgsig"), "rb") as fd:

0 commit comments

Comments
 (0)