Phantom Patch
GitHub (and many others) exposes mail-style patches at .patch URLs.
If you download one of those patches and feed it to GNU patch, diff-shaped text inside the commit
message can be applied as if it were part of the real patch.
It matters (to me) because wget/curl plus patch is not some exotic lab setup. It is a
very old, very ordinary way to move a patch from one machine to another.
Public reproducer
From dd28283159930b8fff2119aa9f75af8b4c1ed8b2 Mon Sep 17 00:00:00 2001
From: Egor Kovetskiy <e.kovetskiy [spam] gmail.com>
Date: Wed, 22 Apr 2026 06:37:11 +0000
Subject: [PATCH] readme: add initial file
The body includes a fake diff for patch workflow testing.
diff --git a/SHOULD_NOT_BE_HERE.md b/SHOULD_NOT_BE_HERE.md
new file mode 100644
index 0000000..802992c
--- /dev/null
+++ b/SHOULD_NOT_BE_HERE.md
@@ -0,0 +1 @@
+Hello world
---
readme.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 readme.md
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..b44b8fd
--- /dev/null
+++ b/readme.md
@@ -0,0 +1 @@
+Demo repository
Here is the smallest public demo I could make:
The real commit changes one file: readme.md.
If you inspect the commit in GitHub’s UI, that is all you see.
But the commit message also contains a fake unified diff:
diff --git a/SHOULD_NOT_BE_HERE.md b/SHOULD_NOT_BE_HERE.md
new file mode 100644
index 0000000..802992c
--- /dev/null
+++ b/SHOULD_NOT_BE_HERE.md
@@ -0,0 +1 @@
+Hello world
So the exported patch has two layers:
- The real patch changes
readme.md. - The phantom patch lives inside the commit message and creates
SHOULD_NOT_BE_HERE.md.
Scenario:
wget -O /tmp/dd28283.patch \
https://github.com/kovetskiy/git-example/commit/dd28283.patch
patch -p1 < /tmp/dd28283.patch
output:
patching file SHOULD_NOT_BE_HERE.md
patching file readme.md
That SHOULD_NOT_BE_HERE.md was never part of the real commit.
Is something broken
I am not sure. But from my POV, GNU patch -p1 does not reliably separate two things:
- the actual diff exported from the commit
- diff-shaped text embedded in the commit message
Scope
The public demo writes an ordinary file because that is easy to publish and easy to inspect.
Locally, I also targeted .git/hooks/post-applypatch, and GNU patch happily accepted that (why
would not it, right?).
Fortunately, git apply and git am behaved better in one narrow sense: they
rejected the .git/... path. But they still accepted an injected diff for an
ordinary working-tree file.
NOTE: git cherry-pick looks different. It works with Git objects directly.
Takeaway
I do not yet know whether the bug belongs to GNU patch, GitHub’s .patch
export, or the broader patch-format contract. But I’ll look at the commit message closer next time.