From 396bdeac9e6aeda6987f4838d0d566d9c31db9d2 Mon Sep 17 00:00:00 2001 From: MrIron Date: Tue, 15 Sep 2026 09:59:24 +0200 Subject: [PATCH] m_jupe: require PRIV_LOCAL_JUPE on the implicit-local JUPE form In mo_jupe() all privilege checks lived inside the "if (target)" block. The four-parameter form omits , defaults to a local jupe (JUPE_LOCAL), and fell through to jupe_add()/jupe_activate() with no HasPriv() check. An oper whose Operator block or class explicitly revokes local_jupe could therefore still create, activate and deactivate local jupes by omitting the target parameter: JUPE +evil.server 3600 :reason (was ungated) JUPE +evil.server my.server.name 3600 :reason (required PRIV_LOCAL_JUPE) On a hub an active local jupe refuses the named server at link time and squits it if introduced, so this granted real operational power contrary to explicit configuration. Only opers reach mo_jupe(), CONFIG_OPERCMDS (default off) gates the whole path, and LOCAL_JUPE is in the default privilege set, so it only bit where an admin had deliberately removed the privilege. Gate the implicit-local branch with PRIV_LOCAL_JUPE, mirroring mo_gline()'s unconditional PRIV_LOCAL_GLINE check on its local path. Closes #106. --- ircd/m_jupe.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ircd/m_jupe.c b/ircd/m_jupe.c index 51f219f53..e17e37cf5 100644 --- a/ircd/m_jupe.c +++ b/ircd/m_jupe.c @@ -210,6 +210,8 @@ int mo_jupe(struct Client* cptr, struct Client* sptr, int parc, char* parv[]) return send_reply(sptr, ERR_DISABLED, "JUPE"); if (parc == 4) { + if (!HasPriv(sptr, PRIV_LOCAL_JUPE)) + return send_reply(sptr, ERR_NOPRIVILEGES); expire_off = atoi(parv[2]); reason = parv[3]; flags |= JUPE_LOCAL;