feat: DCC stripping in both directions to prevent IP leaks

Block all non-ACTION CTCP/DCC from client-to-server (outbound) and add
security logging when inbound CTCP/DCC is stripped. Hard boundary with
no config toggle -- DCC exposes the client's real IP which defeats the
stealth proxy architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
user
2026-02-21 19:30:44 +01:00
parent f4f3132b6b
commit 0064e52fee
4 changed files with 77 additions and 0 deletions

View File

@@ -154,6 +154,16 @@ class TestSuppress:
msg = _msg("TOPIC", ["#test", "new topic"], prefix="user!i@h")
assert _suppress(msg) is False
def test_suppresses_dcc_send(self) -> None:
msg = _msg("PRIVMSG", ["nick", "\x01DCC SEND file 3232235777 5000 1024\x01"],
prefix="user!i@h")
assert _suppress(msg) is True
def test_suppresses_dcc_chat(self) -> None:
msg = _msg("PRIVMSG", ["nick", "\x01DCC CHAT chat 3232235777 5000\x01"],
prefix="user!i@h")
assert _suppress(msg) is True
# -- Router._proxy_for ------------------------------------------------------
@@ -545,6 +555,46 @@ class TestRouteClientMessage:
sent = net.send.call_args[0][0]
assert sent.prefix == "me!u@h"
@pytest.mark.asyncio
async def test_blocks_outbound_dcc_send(self) -> None:
router = Router(_config(), _backlog())
net = _mock_network("libera")
router.networks["libera"] = net
msg = _msg("PRIVMSG", ["user/libera", "\x01DCC SEND file 3232235777 5000 1024\x01"])
await router.route_client_message(msg)
net.send.assert_not_awaited()
@pytest.mark.asyncio
async def test_blocks_outbound_dcc_chat(self) -> None:
router = Router(_config(), _backlog())
net = _mock_network("libera")
router.networks["libera"] = net
msg = _msg("PRIVMSG", ["user/libera", "\x01DCC CHAT chat 3232235777 5000\x01"])
await router.route_client_message(msg)
net.send.assert_not_awaited()
@pytest.mark.asyncio
async def test_passes_outbound_action(self) -> None:
router = Router(_config(), _backlog())
net = _mock_network("libera")
router.networks["libera"] = net
msg = _msg("PRIVMSG", ["#test/libera", "\x01ACTION waves\x01"])
await router.route_client_message(msg)
net.send.assert_awaited_once()
@pytest.mark.asyncio
async def test_passes_outbound_normal_privmsg(self) -> None:
router = Router(_config(), _backlog())
net = _mock_network("libera")
router.networks["libera"] = net
msg = _msg("PRIVMSG", ["#test/libera", "just a normal message"])
await router.route_client_message(msg)
net.send.assert_awaited_once()
# -- _dispatch (inbound: network -> clients) ---------------------------------