aboutsummaryrefslogtreecommitdiffstats
path: root/tests/test_redact.py
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-09-08 13:32:20 +0200
committerDanilo M. <danix@danix.xyz>2026-09-08 13:32:20 +0200
commitc734feaf769c936698a52f38a2099152a6098c43 (patch)
treefd4b483e0f3091be4ca7884f4ecf5c7532f2046b /tests/test_redact.py
parent3126606d5e761e151fee0caab34d2cca2b9b3ee7 (diff)
downloadabusectl-c734feaf769c936698a52f38a2099152a6098c43.tar.gz
abusectl-c734feaf769c936698a52f38a2099152a6098c43.zip
fix: redact fragment and strip userinfo, flag hidden query in path
The query string was not the only place a recipient identifier can hide. A fragment (#e=victim@...) is published as-is since we report the URL's literal text, not what a browser would send. Userinfo (user:pass@host) leaks a credential as well as an identifier, so it is stripped outright rather than redacted in place. A path segment can also smuggle an encoded query (%3Fe=victim@...); suspect_path_segments now flags a segment that decodes to something containing '=' or '@', still leaving the decision to redact or not to human review. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KphFXTc2QajxXsHWyvGJ4R
Diffstat (limited to 'tests/test_redact.py')
-rw-r--r--tests/test_redact.py42
1 files changed, 42 insertions, 0 deletions
diff --git a/tests/test_redact.py b/tests/test_redact.py
index 91ab291..23c2a45 100644
--- a/tests/test_redact.py
+++ b/tests/test_redact.py
@@ -102,5 +102,47 @@ class TestUrlValuedParameters(unittest.TestCase):
)
+class TestLeakResistance(unittest.TestCase):
+ def test_a_fragment_key_value_pair_is_redacted(self):
+ out = redact.url("http://a.invalid/p?x=1#e=victim@example.org")
+ self.assertNotIn("victim@example.org", out)
+
+ def test_userinfo_is_stripped_not_redacted_in_place(self):
+ out = redact.url("http://victim%40example.org:pw@a.invalid/p")
+ self.assertNotIn("victim%40example.org", out)
+ self.assertNotIn("pw", out)
+
+ def test_a_query_hidden_inside_a_path_segment_is_flagged(self):
+ found = redact.suspect_path_segments(
+ "http://a.invalid/p%3Fe=victim@example.org"
+ )
+ self.assertNotEqual(found, [])
+
+ def test_no_recipient_marker_survives_any_placement(self):
+ # The same address, placed everywhere a URL can hide one.
+ marker = "victim@example.org"
+ encoded = "victim%40example.org"
+ for raw in (
+ f"http://a.invalid/p?e={marker}",
+ f"http://a.invalid/p?x=1#e={marker}",
+ f"http://{encoded}:pw@a.invalid/p",
+ f"http://a.invalid/p?a=1&b=2#{marker}",
+ ):
+ with self.subTest(raw=raw):
+ out = redact.url(raw)
+ self.assertNotIn(marker, out)
+ self.assertNotIn(encoded, out)
+
+
+class TestHasUserinfo(unittest.TestCase):
+ def test_userinfo_present_is_reported(self):
+ self.assertTrue(
+ redact.has_userinfo("http://victim%40example.org:pw@a.invalid/p")
+ )
+
+ def test_no_userinfo_is_reported_absent(self):
+ self.assertFalse(redact.has_userinfo("http://a.invalid/p"))
+
+
if __name__ == "__main__":
unittest.main()