diff --git a/CHANGELOG.md b/CHANGELOG.md index 18d6aec17..0aeed8477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Fixed +* **Moderation**: Role hierarchy check added to all moderation actions — moderators can no longer act on members with an equal or higher top role; guild owner bypass uses `owner_id` (cache-safe); bot-vs-target role check prevents `discord.Forbidden` errors when the bot's role is insufficient (#1227) * **Moderation**: Interaction deferral handling across all moderation modules; improved error handling in slowmode channel conversion; enhanced guild config caching and embed handling in CommunicationService and ExecutionService; added assertions for case and jail role in unjail operation * **Event handling**: First ready state marked even on setup failure to prevent unnecessary expensive checks on retries * **Error handling**: Enhanced error logging with context information; improved error handling in moderation coordinator with proper task cancellation diff --git a/src/tux/modules/moderation/__init__.py b/src/tux/modules/moderation/__init__.py index d2faaeb3e..6844ee2d1 100644 --- a/src/tux/modules/moderation/__init__.py +++ b/src/tux/modules/moderation/__init__.py @@ -97,6 +97,30 @@ async def moderate_user( **kwargs : Any Additional case data """ + # Role hierarchy checks: only apply when the target is a guild member + if ( + ctx.guild + and isinstance(user, discord.Member) + and isinstance(ctx.author, discord.Member) + ): + # Use owner_id (always available) instead of owner (may be None if uncached) + if ( + ctx.author.id != ctx.guild.owner_id + and user.top_role >= ctx.author.top_role + ): + await self._respond( + ctx, + "You cannot moderate a member with an equal or higher role than yours.", + ) + return + + if ctx.guild.me.top_role <= user.top_role: + await self._respond( + ctx, + "I cannot moderate this member because their role is equal to or higher than mine.", + ) + return + await self.moderation.execute_moderation_action( ctx=ctx, case_type=case_type,