View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0006660 | unreal | ircd | public | 2026-11-12 21:25 | 2026-11-12 21:25 |
| Reporter | mattf | Assigned To | |||
| Priority | normal | Severity | minor | Reproducibility | always |
| Status | new | Resolution | open | ||
| Product Version | 6.2.7.1 | ||||
| Summary | 0006660: +D delay-join: hidden member's AWAY/NICK/QUIT, JOIN 0 and topic leak to the wrong users | ||||
| Description | This was found through an agentic-driven scan of the source and is part of a batch of non-critical issues I found worth reporting. In a +D (delay-join) channel a member who has not spoken yet is hidden and should only be visible to channel staff. Four paths get that backwards or skip the check: 1. AWAY/NICK/QUIT. In src/send.c, sendto_local_common_channels() has: if (check_invisible && user_can_see_member(acptr, user, channel)) continue; The condition is inverted (there is a FIXME right above it): it skips the users who can see the hidden member and delivers to the ones who cannot. So a plain member who should not see the hidden user receives their AWAY/NICK/QUIT, and the staff who should see it do not. 2. JOIN 0. In src/modules/join.c the "JOIN 0 = leave all channels" path sends the PART with no visibility flag (sendto_channel with no CHECK_INVISIBLE), while a normal PART passes CHECK_INVISIBLE. So a hidden member who sends JOIN 0 reveals their PART to everyone. 3. TOPIC. In src/modules/topic.c the topic broadcast goes out without CHECK_INVISIBLE (the join path passes it, this one does not). A hidden member setting the topic reveals their nick!user@host, and the 333 reply replays it to later joiners. 4. Banned quit. In src/send.c, quit_sendto_local_common_channels() calls user_can_see_member_fast() with two arguments swapped relative to its signature (channel.c declares it as user, target, channel, target_member, user_member_modes). So when a hidden member is killed by a ban that carries a hidden reason (G-line/TKL/services kill), the check leaks their full nick!user@host and the quit reason to users who should not see them, and every relay server re-runs it, so the disclosure spreads network-wide. Similar issues: - https://bugs.unrealircd.org/view.php?id=5123 - https://bugs.unrealircd.org/view.php?id=5521 - https://bugs.unrealircd.org/view.php?id=6118 | ||||
| Steps To Reproduce | A +D channel has three clients: an op (can see hidden members), a plain member who has already spoken (cannot see hidden members), and a third client who joins but stays silent (the hidden member). 1. The hidden member sends AWAY, changes nick, or quits. The plain member receives it; the op does not. 2. The hidden member sends JOIN 0. The plain member sees ":nick PART #chan :Left all channels". 3. On a +D -t channel, the hidden member sets the topic. The plain member receives the TOPIC line with the hidden member's nick!user@host, and a later joiner gets it again via 333. 4. On a +D channel, an oper G-lines (or TKL/services-kills) the hidden member. The plain member receives a QUIT with the hidden member's full nick!user@host and the ban reason. | ||||
| Additional Information | Suggested fixes: 1. Add the missing "!": if (check_invisible && !user_can_see_member(acptr, user, channel)) continue; 2. Route the JOIN 0 PART through the same invisible-aware path as a normal PART (pass CHECK_INVISIBLE). 3. Pass CHECK_INVISIBLE on both topic broadcast sites. 4. Pass the leaver's member and the viewer's modes to user_can_see_member_fast() (the two are currently swapped). Let me know if this is desired and I could PR it myself. | ||||
| 3rd party modules | |||||
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2026-11-12 21:25 | mattf | New Issue |