@@ -25,9 +25,244 @@ This edition covers what happened during the months of February and March 2026.
2525### Reviews
2626-->
2727
28- <!-- -
2928### Support
30- -->
29+
30+ + [ git-am applies commit message diffs] ( https://lore.kernel.org/git/bcqvh7ahjjgzpgxwnr4kh3hfkksfruf54refyry3ha7qk7dldf@fij5calmscvm )
31+
32+ On February 6, 2026, Matthias Beyer forwarded to the Git mailing list a
33+ surprising warning that had just circulated on Mastodon:
34+
35+ > PSA: Did you know that it's ** unsafe** to put code diffs into your
36+ > commit messages?
37+ >
38+ > Such diffs will be applied by patch(1) (also git-am(1)) as part of
39+ > the code change!
40+ >
41+ > This is how a sleep(1) made it into i3 4.25-2 in Debian unstable.
42+
43+ The incident had originated in the i3 window manager project, where a
44+ commit message contained an unindented diff for illustration purposes.
45+ When Debian packagers later applied the patch using ` patch(1) ` , the diff
46+ in the commit message was applied as actual code, sneaking a spurious
47+ ` sleep(1) ` call into the Debian unstable package. Matthias asked the
48+ list whether this was a known issue and whether it could be an attack
49+ vector.
50+
51+ To understand why this happens, it helps to know how ` git-am ` parses
52+ its input. When processing a patch email, it must split the stream into
53+ two parts: the commit message and the actual patch to apply. It does
54+ this by treating the first occurrence of any of the following lines as
55+ the boundary between the two:
56+
57+ - a line consisting of only three dashes (` --- ` ),
58+ - a line beginning with ` diff - ` , or
59+ - a line beginning with ` Index: ` .
60+
61+ Everything before that boundary becomes the commit message; everything
62+ after is fed to the patch application machinery. Crucially, ` git-am `
63+ scans from the top of the email, so the very first such line it
64+ encounters terminates the commit message regardless of whether that
65+ line was meant to be part of the message text.
66+
67+ This design dates back to the tool's origins. As Jeff King (also known
68+ as "Peff") quickly explained in reply to Matthias, ` git-am ` was
69+ originally designed to handle patches sent by all kinds of people, not
70+ just Git users. A contributor might have generated a diff with plain
71+ GNU ` diff ` and typed the rest of the email by hand, without any ` --- `
72+ separator. The tool was therefore intentionally permissive: it would
73+ find a ` diff - ` line anywhere in the email and treat it as the start
74+ of the patch. Peff demonstrated this with a live example. He fed `git
75+ am` a hand-typed email containing a GNU diff, and it produced the
76+ expected commit.
77+
78+ This historical context also explained why ` git-am ` is notoriously
79+ hard to fix: "I don't think there is a way to unambiguously parse the
80+ single-stream output that format-patch produces," Peff wrote, noting
81+ that he could find at least three earlier discussions of the same
82+ problem (in 2015, 2022, and 2024). The stream is simply ambiguous by
83+ design. Even the ` --- ` marker itself cannot be used to robustly split
84+ things, since ` --- ` on a line by itself is a valid diff hunk line
85+ indicating that the string ` -- ` was removed from a file.
86+
87+ Matthias proposed parsing from the end of the email rather than from
88+ the top. Peff replied that this would still be ambiguous for the same
89+ reasons, and would introduce new corner cases.
90+
91+ Jacob Keller noted early on that the issue was certainly surprising but
92+ that he was unsure it constituted a security attack vector, since
93+ someone should be reading the commit message before applying. But
94+ Matthias pushed back: the whole point was that nobody realized the
95+ behavior was there. He called it "sheer luck" that it was only a
96+ ` sleep(1) ` and not something more malicious crafted as a diff in the
97+ commit message.
98+
99+ Florian Weimer wondered whether the ` git-format-patch ` output was
100+ really ambiguous, given that the patch section is normally preceded by
101+ a diffstat block. Peff replied that the diffstat is optional and is not
102+ even parsed by the receiving side at all.
103+
104+ Jakob Haufe added an important nuance: even if ` git-am ` were fixed to
105+ require indented diffs, it would only partially mitigate the problem,
106+ because ` patch(1) ` (which many distributions use to apply upstream
107+ fixes to packages) is even more permissive. It will strip a consistent
108+ level of indentation from diffs before applying them. He quoted the
109+ ` patch(1) ` manual page: "If the entire diff is indented by a
110+ consistent amount, [ ...] this is taken into account." The i3 incident
111+ had in fact been triggered by ` patch(1) ` , not ` git-am ` .
112+
113+ Kristoffer Haugsbakk synthesized this into a clear summary of the
114+ situation and immediately proposed documenting it.
115+
116+ Matthias also highlighted the broader applicability beyond email
117+ workflows: Linux distributions like NixOS routinely fetch patches
118+ directly from upstream Git repositories and apply them to packages
119+ using ` patch(1) ` . He noted that even after 15 years of using Git and
120+ being comfortable with email patch workflows, he himself had not known
121+ about this behavior.
122+
123+ Several directions were then explored to look for solutions.
124+
125+ Peff observed the irony that ` git-format-patch ` does have a ` --attach `
126+ option which puts the message and the patch in separate MIME parts —
127+ making them unambiguous in principle. However, ` git-mailinfo ` (which
128+ powers ` git-am ` under the hood) decodes both parts into a single
129+ stream and still treats a ` diff ` line in the message part as the start
130+ of a patch. Fixing this would require careful surgery to avoid
131+ breaking the existing forgiving handling of patches received as a
132+ single attachment.
133+
134+ Patrick Steinhardt suggested that even if parsing cannot be made
135+ unambiguous, ` git-am ` could at least detect the ambiguity and bail by
136+ default with an ` --accept-ambiguous-patch ` override. Jacob Keller
137+ proposed going further: a new "unambiguous mode" where
138+ ` git-format-patch ` would produce output that new versions of ` git-am `
139+ could distinguish unambiguously, while old versions would still handle
140+ the common case the same way as before.
141+
142+ Jacob had also sketched a concrete scheme: add a new unambiguous
143+ marker after the ` --- ` separator, so that old versions of ` git-am `
144+ would still cut at the ` --- ` and ignore everything up to the diff, while
145+ new versions would wait for the new marker and correctly ignore any
146+ diff appearing before it. Since the new marker would come after ` --- ` ,
147+ it would not be inserted into the commit message when applied.
148+
149+ Peff replied that this was trickier than it sounded: the new marker
150+ would have to be something that could never appear legitimately in a
151+ commit message, and both sides would need to complain if they saw
152+ multiple markers. He explored further options: reversible quoting of
153+ ` --- ` and ` diff ` lines in the commit message (analogous to the ` >From `
154+ quoting used in mbox files), applied only when the message would
155+ otherwise be ambiguous. This way, if an older ` git-am ` received the
156+ mail, the worst case would be visible quoting in the commit message —
157+ ugly but readable. Junio Hamano, the Git maintainer, added another
158+ thought: refusing to accept unsigned patches at all.
159+
160+ Peff also proposed a simpler receiver-side improvement: a
161+ ` git am --strict ` mode that would always require a ` --- ` separator
162+ before the diff, on the assumption that well-formatted patches from Git
163+ always have one. This would not help with diffs that legitimately
164+ appear before the ` --- ` , but would eliminate the most common accidental
165+ cases.
166+
167+ None of these ideas led to an immediate implementation, as they all
168+ involve backward compatibility tradeoffs that would need careful
169+ thought.
170+
171+ On February 8, Kristoffer sent a documentation patch titled "doc: add
172+ caveat about roundtripping format-patch" which introduced a new
173+ ` Documentation/format-patch-caveats.adoc ` file explaining the
174+ behavior. The caveat was designed to be included in the documentation
175+ for ` git-am ` , ` git-format-patch ` , and ` git-send-email ` .
176+
177+ Junio reviewed
178+ [ version 1] ( https://lore.kernel.org/git/format-patch_caveats.281@msgid.xyz )
179+ and offered a correction to the wording: rather than saying that an
180+ unindented diff in the commit message "will not only cut the message
181+ short but cause that very diff to be applied, along with the patch in
182+ the patch section," Junio noted that the outcome is not so
183+ deterministic. The diff in the commit message might get applied, or
184+ the patch machinery might trip on something and fail outright. He also
185+ flagged that the space after the ` --- ` in the cover letter was
186+ inconsistent with the project's conventions.
187+
188+ Phillip Wood reviewed the patch and found the mention of
189+ ` git-send-email ` a bit distracting, since that command merely runs
190+ ` git-format-patch ` and does not do any formatting itself. He also
191+ suggested wording improvements: replacing "One might want to use [ ...]
192+ patch(1)" with "Given these limitations, one might be tempted to [ ...] ".
193+
194+ Kristoffer incorporated all of this in
195+ [ version 2] ( https://lore.kernel.org/git/V2_format-patch_caveats.34b@msgid.xyz ) ,
196+ which dropped the ` git-send-email ` mention from the introductory
197+ paragraph (while keeping the CAVEATS section in its documentation, for
198+ users who encounter it there), removed example code blocks in favor of
199+ clearer prose, and used the list of message-terminating patterns
200+ already present in ` git-am ` 's documentation. Junio reviewed it and
201+ queued it with the comment "Nicely written."
202+
203+ A third version,
204+ [ version 3] ( https://lore.kernel.org/git/pull.2220.v3.git.git.1772559813151.gitgitgadget@gmail.com ) ,
205+ was submitted and received Junio's approval to go to ` next ` .
206+
207+ Meanwhile, Phillip had observed that since the parsing cannot be fixed,
208+ "perhaps we should update our sample ` commit-msg ` hook to reject
209+ messages that will cause problems." On February 7, he sent a 3-patch
210+ series titled "commit-msg.sample: reject messages that would confuse
211+ ` git am ` ". The series:
212+
213+ 1 . Added a ` .gitattributes ` rule for sample hooks (which are shell
214+ scripts but have ` .sample ` extensions).
215+ 2 . Extended the sample ` commit-msg ` hook to scan the body of the commit
216+ message for unindented ` diff - ` and ` Index: ` lines and reject the
217+ commit with a helpful error message.
218+ 3 . Added a further check to detect ` --- ` separator lines in the message
219+ body, which would cause ` git-am ` to silently truncate the commit
220+ message.
221+
222+ Peff reacted with measured skepticism to patch 3 in
223+ [ version 1] ( https://lore.kernel.org/git/cover.1770476279.git.phillip.wood@dunelm.org.uk ) :
224+ he and Junio both pointed out that they themselves sometimes use ` --- `
225+ intentionally in commit messages to add notes that will appear in the
226+ formatted patch email but not end up in the final commit message when
227+ applied. Junio explained the trick: "when I know what I want to write
228+ below the three-dash lines, I would commit with ` --- ` and additional
229+ notes below it, so that I do not forget during format-patch. When the
230+ commit is turned into a patch email [ ...] ` am ` cuts at the first one,
231+ and ` apply ` knows that the garbage lines at front, including
232+ three-dash lines, do not matter until it sees ` ^diff ` , this works out
233+ perfectly well."
234+
235+ Peff confirmed he used the same trick. Phillip, acknowledging that at
236+ least three developers relied on this behavior, decided to drop patch 3
237+ entirely, reducing the series from three patches to two, in
238+ [ version 2] ( https://lore.kernel.org/git/cover.1770993281.git.phillip.wood@dunelm.org.uk ) .
239+ He also refined the diff detection in the body: the v2 correctly skips
240+ the first paragraph of the message (which becomes the email Subject
241+ header and so does not go through the patch boundary detection), skips
242+ lines below a scissors line, and handles the ` core.commentChar ` and
243+ ` core.commentString ` configuration options for determining which lines
244+ are comments. Junio reviewed version 2 with detailed questions about
245+ the scissors-line logic.
246+
247+ Kristoffer verified that version 2 worked with `git commit
248+ --cleanup=scissors --verbose` and was satisfied.
249+
250+ The discussion did not lead to a fundamental fix to the ambiguous
251+ parsing in ` git-am ` , which remains an open problem with no obvious
252+ backward-compatible solution. But it produced two concrete
253+ improvements that were accepted and are now in ` master ` : a CAVEATS
254+ section in the documentation for ` git-am ` , ` git-format-patch ` , and
255+ ` git-send-email ` spelling out exactly how commit messages can
256+ inadvertently interfere with patch application, and an enhanced sample
257+ ` commit-msg ` hook that rejects messages containing unindented diffs.
258+
259+ The thread also served as a useful reminder that this problem is not
260+ limited to email workflows: any project that generates patches from
261+ Git commits using ` git-format-patch ` and applies them with ` patch(1) `
262+ or ` git-am ` is exposed to it. The practical advice for authors is
263+ simple: if you include diffs in commit messages for illustrative
264+ purposes, make sure to indent them consistently, and be aware that
265+ even that does not protect you from ` patch(1) ` .
31266
32267## Developer Spotlight: Olamide Caleb Bello
33268
0 commit comments