aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--tests/fixtures/leaky.eml20
-rw-r--r--tests/test_parse.py10
2 files changed, 29 insertions, 1 deletions
diff --git a/tests/fixtures/leaky.eml b/tests/fixtures/leaky.eml
new file mode 100644
index 0000000..840ce6d
--- /dev/null
+++ b/tests/fixtures/leaky.eml
@@ -0,0 +1,20 @@
+Received: from mx.example.org (mx.example.org [192.0.2.11])
+ by mail.example.org (Postfix) with ESMTP id III99
+ for <you@example.org>; Tue, 8 Sep 2026 16:00:02 +0200 (CEST)
+Received: from sender.example.invalid (unknown [203.0.113.42])
+ by mx.example.org (Postfix) with ESMTP id JJJ11
+ for <you@example.org>; Tue, 8 Sep 2026 16:00:01 +0200 (CEST)
+Return-Path: <bounce@sender.example.invalid>
+From: "Billing at billing@innocent.example" <phish@sender.example.invalid>
+To: <you@example.org>
+Subject: Confirm now
+Message-ID: <eee555@sender.example.invalid>
+Date: Tue, 8 Sep 2026 16:00:00 +0200
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+
+Plain value: http://a.example.invalid/p?e=you@example.org
+Valueless param: http://b.example.invalid/p?you@example.org
+In the fragment: http://c.example.invalid/p#e=you@example.org
+In userinfo: http://you%40example.org:pw@d.example.invalid/p
+Nested redirect: http://t.example.invalid/c?url=http%3A%2F%2Fe.example.invalid%2Fp%3Fe%3Dyou%40example.org
diff --git a/tests/test_parse.py b/tests/test_parse.py
index 28c8609..a29b7a1 100644
--- a/tests/test_parse.py
+++ b/tests/test_parse.py
@@ -213,12 +213,20 @@ class TestIocAssembly(unittest.TestCase):
def test_no_ioc_holds_a_recipient_address(self):
# The safety property, asserted over the whole output.
for name in ("simple.eml", "forged-chain.eml", "with-attachment.eml",
- "redirector.eml"):
+ "redirector.eml", "leaky.eml"):
iocs = parse.iocs(load(name), trusted=["192.0.2.0/24"])
blob = repr(iocs)
self.assertNotIn("you@example.org", blob)
self.assertNotIn("example.org", blob)
+ def test_the_address_does_not_survive_any_url_shape(self):
+ # leaky.eml carries you@example.org in five placements. Each one has
+ # been a real leak in this codebase or is one shape away from it.
+ iocs = parse.iocs(load("leaky.eml"), trusted=["192.0.2.0/24"])
+ blob = repr(iocs)
+ self.assertNotIn("you@example.org", blob)
+ self.assertNotIn("you%40example.org", blob)
+
if __name__ == "__main__":
unittest.main()