diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-08 13:32:20 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-08 13:32:20 +0200 |
| commit | c734feaf769c936698a52f38a2099152a6098c43 (patch) | |
| tree | fd4b483e0f3091be4ca7884f4ecf5c7532f2046b /tests/test_redact.py | |
| parent | 3126606d5e761e151fee0caab34d2cca2b9b3ee7 (diff) | |
| download | abusectl-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.py | 42 |
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() |
