From dabb18abd46c60bc2fe82a76735db597cd4da4f8 Mon Sep 17 00:00:00 2001 From: Jean Date: Thu, 16 Jul 2026 03:25:27 +0000 Subject: [PATCH] HostServ: re-check the forbidden list when activating a vhost request REQUEST rejected a forbidden host, but ACTIVATE never re-checked, so a pattern forbidden after a request was filed was granted on approval. The activate path now re-reads the forbidden list. Operator SET stays an intentional override (unchanged), so the check lives in approve, not the shared prepare_vhost. --- modules/hostserv/src/approve.rs | 6 ++++++ src/engine/tests.rs | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/modules/hostserv/src/approve.rs b/modules/hostserv/src/approve.rs index a326840..18411f6 100644 --- a/modules/hostserv/src/approve.rs +++ b/modules/hostserv/src/approve.rs @@ -34,6 +34,12 @@ pub fn handle(me: &str, from: &Sender, args: &[&str], ctx: &mut ServiceCtx, net: return; } }; + // The forbidden list may have grown since the request was filed; a user's + // request must still obey it (an operator's own SET is a deliberate override). + if db.vhost_is_forbidden(&host) { + ctx.notice(me, from.uid, format!("Can't activate: \x02{host}\x02 is on the forbidden list.")); + return; + } match db.set_vhost(account, &host, from.nick, None) { Ok(()) => { for uid in net.uids_logged_into(account) { diff --git a/src/engine/tests.rs b/src/engine/tests.rs index fe7d4b5..322c3d5 100644 --- a/src/engine/tests.rs +++ b/src/engine/tests.rs @@ -2324,6 +2324,13 @@ assert!(says(&hs(&mut e, "000AAAAAB", "WAITING"), "No vhost requests"), "no request left after reject"); // A non-operator can't approve. assert!(says(&hs(&mut e, "000AAAAAV", "WAITING"), "Access denied"), "oper-gated"); + + // A host forbidden AFTER its request is filed must not slip through ACTIVATE. + hs(&mut e, "000AAAAAB", "REQUEST evilcorp.example"); + hs(&mut e, "000AAAAAB", "FORBID (?i)evilcorp"); + let out = hs(&mut e, "000AAAAAB", "ACTIVATE boss"); + assert!(says(&out, "forbidden list"), "activate refused for a now-forbidden host: {out:?}"); + assert!(!out.iter().any(|a| matches!(a, NetAction::SetHost { .. })), "no vhost applied when forbidden: {out:?}"); } // StatServ reports per-channel activity (#channel) and the global registry