// antinuke
Antinuke
Antinuke protects a guild from raids, rogue admins, and accidental nukes. Where automod watches message content and autopilot grades uploaded media, antinuke watches the audit log — every kick, ban, role create / delete / edit, channel create / delete, and webhook create / delete that lands on the server. When something looks like a sabotage attempt, antinuke reverts the change before it sticks.
Overview
Everything is off by default. A freshly-invited bot will not act on a single audit-log event until the master toggle is flipped in /antinuke config. The protection surface is split across two independent layers, each toggled on its own:
- Layer 1 — rate-limit watch.Counts each actor's kick / ban / role / channel / webhook actions inside a rolling window. When the count crosses the configured threshold, antinuke fires the strike + alert flow.
- Layer 2 — dangerous-permission watch. Fires instantly the moment a role with one of the dangerous permissions is created, edited, or granted to a member. The revert happens before the strike — the dangerous state never gets to settle.
Both layers route through the same actor-classification gate. Antinuke distinguishes humans, bots, and whitelistedactors — each gets a different response, because jailing a bot is meaningless and warning a whitelisted integration is noise. kesarAI's own audit entries are skipped because the same policy is enforced caller-side before the role change happens (see caller-side gate).
Tip. Start with the
MEDIUMpreset, set your quarantine role, and add the server owner plus one or two trusted admins to the user whitelist. That puts you in a state where someone running a fresh nuke script gets stopped in seconds and your day-to-day mod actions still go through.
Behavior matrix
What antinuke does on each type of action depends on who took it. The table below covers the three actor classes plus the bot itself:
| Actor | Sensitive role GRANT | Sensitive role REMOVE | Other reversible (channel delete, mass-ban) |
|---|---|---|---|
| Guild owner | allow | allow | allow |
User in whitelist / role in whitelist_role_ids | allow | allow | allow |
Bot in whitelist_bot_ids | allow | allow | allow |
| Non-whitelisted human | revert + strike + alert (jail at threshold) | strike only, no revert | revert if possible + strike + alert |
| Non-whitelisted bot | revert immediately, no strike, no jail, alert | no action | revert if possible, no strike, no jail, alert |
| kesarAI itself | listener skips — caller-side gate enforces whitelist on the invoker | skip | skip |
The asymmetry on the remove column is deliberate. Stripping a dangerous role from someone never makes the server less safe, so antinuke never re-adds it. But a non-whitelisted human stripping mod from another moderator is still suspicious — the strike count climbs even though no revert happens, and they jail at threshold if the behaviour keeps up.
Whitelists
Antinuke supports three independent whitelists, each addressing a different escape-hatch case. All three are configured in the Whitelist panel inside /antinuke config:
whitelist— user IDs. The standard exemption. Add the server owner, your head admins, and any trusted operator who needs to run mod actions in bulk.whitelist_role_ids— role IDs. Any member holding one of these roles is exempt. Useful when your staff roster turns over and you don't want to chase user IDs — put the staff role in the role whitelist and new staff inherit exemption on assignment.whitelist_bot_ids— bot IDs. The escape hatch for trusted integrations: a Carl-bot managing reaction roles, a ticket bot creating private channels, an audit-log mirror that creates webhooks. Without this list, every action a useful bot takes would trip the rate-limit layer.
The user whitelist also gates configuration access for the antinuke panel itself. Server owner, bot owner (via BOT_OWNER_ID env), or users in whitelist may open /antinuke config — Discord Administrator alone is notenough, which closes the “admin quietly whitelists themselves” loophole. The role whitelist and bot whitelist exempt their members from antinuke triggers but do not grant config access: whitelisting a wide-membership role would otherwise expose configuration to everyone holding it.
Caller-side gate
The listener has to skip kesarAI's own audit entries — otherwise the bot would revert its own legitimate role changes. That skip used to be a loophole: a mod with the role mod-permission could run /roleadd @target @Administrator and antinuke would shrug, because the audit entry showed kesarAI as the actor. The fix is to enforce the whitelist before the role change rather than after.
Every kesarAI command that grants a role calls ensure_antinuke_clearance(invoker, role) before the add_rolesrequest hits Discord. If the invoker isn't whitelisted and the role carries any dangerous permission, the call fails with an ephemeral denial and no role change happens. The gated entry points are:
/roleadd— direct role assignment via slash command./roleremove— symmetrical with the listener: today the gate no-ops on remove (matches the “remove is fine” column in the matrix), but the call site is in place for future tightening./permrole— the owner-only permanent-role grant (rejoin auto-restores are not re-gated — they reapply a previously-approved grant).add_roleandremove_roletools in/modai— the agentic planner can't launder a dangerous grant through its tool surface either.
The denial message kesarAI posts on a blocked grant:
Antinuke blocked this action. The role
@Moderatorcarries dangerous permissions (manage_roles,kick_members). You aren't on the antinuke whitelist.Ask the server owner or a whitelisted admin to grant this role directly, or be added via
/antinuke→ Whitelist.
The denial enumerates the specific dangerous flags the role carries — useful for catching a misconfigured role that's dangerous by accident. See the permissions guide for how the underlying role mod-permission works.
Rate-limit watch (Layer 1)
Layer 1 trips when an actor exceeds count occurrences of one event type inside a rolling window_seconds window. Each event type configures independently — you can run a tight kick / ban guard and leave webhook creation loose, or the other way around. The window accepts values from 60 to 3600 seconds.
The seven tracked event types:
kick_ban— kicks and bans share one bucket.role_creationsrole_deletionschannel_creationschannel_deletionswebhook_creationswebhook_deletions
Each entry stores {enabled, count, window_seconds, heat}. The defaults are sensible single-rule values: kick_ban fires at 3 in 300s (5 minutes), role and channel events at similar thresholds. To configure something custom — say, “ten bans in thirty minutes” — open the per-event modal and set count: 10 and window_seconds: 1800. The alert message reads the current threshold at fire time, so when you tighten the threshold the next alert reflects the new value with no caching.
Configs stored against the legacy per_minute / per_hour schema migrate automatically on first read. The migration prefers the tighter window: if both keys were set, the minute bucket wins; if only the hour bucket was set, that value becomes {count: per_hour, window_seconds: 3600}. The migration is one-way and idempotent.
Dangerous-permission watch (Layer 2)
Layer 2 catches Tier 3 permission changes the moment they show up in the audit log. A “dangerous role” is any role whose permission bitmask intersects this list:
administrator, manage_guild, manage_roles, manage_channels, manage_webhooks, manage_messages, manage_nicknames, manage_emojis_and_stickers, ban_members, kick_members, moderate_members, mention_everyone, view_audit_log.
The watch fires on three audit shapes:
- A role having a dangerous permission added via
role_update. The role's permissions are rolled back to the audit-log before-state. - A member being granted a role that already carries a dangerous permission via
member_role_update. The role is removed from the member. - A channel overwrite granting a dangerous permission to
@everyoneor a non-staff role viachannel_update. The overwrite is rolled back.
The revert always runs first, before strike or alert dispatch. The order matters: bundling the revert into the strike branch is how the pre-AN1 version of this cog let dangerous role grants slip through when alert dispatch failed for any reason. Now the revert is unconditional on every non-whitelisted actor; the strike and alert are downstream.
Strikes and jail
Strikes are a humans-onlymechanic. Bot actors get the revert and a one-line alert, full stop — there's no strike count for a bot because there's nothing useful to do at threshold. Managed roles can't be stripped and bots can't be timed out.
For human actors, every triggered event (rate-limit breach or dangerous-permission revert) appends a timestamped strike to data/antinuke_strikes.json. Strikes older than strike_decay_hours (default 24) prune on every read, so a quiet day naturally clears the count. When the live count reaches strike_threshold (default 2), antinuke jails the actor.
Jail is a two-step move: snapshot every non-default, non-managed role the member holds into data/antinuke_jails.json, then strip those roles and apply the configured quarantine role. The snapshot is what makes unjail clean — running /antinuke unjail user:@member pops the snapshot, removes the quarantine role, and re-applies the original roles in one pass. A jail can also be triggered manually with /antinuke jail user:@member; that path refuses on a bot target with “Bots cannot be jailed.”
If no quarantine role is configured, jail bails with “No quarantine role configured.”and the setup view offers to auto-create one — Discord-wide channel sweep that adds deny overwrites for chat-y permissions on every channel and category in the guild, while keeping view + history so the jailed user can read what's happening to them.
Configuration
The full configuration surface lives behind /antinuke config. The panel is gated to the server owner, bot owner, or users on the user-ID whitelist — Discord Administrator alone won't open it. The main panel exposes:
- Master toggle. The top-level
enabledswitch. Off by default. - Apply preset. Four buttons —
LOW,MEDIUM,HIGH,STRICT— that set every per-eventcountandwindow_secondsin one move, along withdangerous_perm_watchandstrike_threshold.LOWis gentle (kick / ban at 10 in 10 min, dangerous-perm watch off);STRICTis hair-trigger (kick / ban at 2 in 5 min, threshold1). The master toggle is never flipped on by a preset — applying a preset to a disabled config leaves the system disabled. - Triggers panel. Per-event-type toggle plus a modal that takes two inputs — Threshold count and Window (seconds, 60-3600). The panel embed renders the human-readable window (
5 min,30 min,1 h) so admins don't have to math out seconds. The dangerous-permission watch sits on this panel as a separate toggle. - Whitelists panel. Three native selects:
UserSelectforwhitelist,RoleSelectforwhitelist_role_ids, and a secondUserSelectfiltered to bots forwhitelist_bot_ids. No more typing IDs in chat. - Strikes panel. Modal with Strike threshold and Strike decay (hours) inputs. The decay defaults to
24; the threshold defaults to2. - Alerts panel. Pick a log channel for the antinuke alert embeds, plus up to ten user IDs to receive the same embed as a DM. The perpetrator can optionally also get a DM explaining why they were jailed.
- Quarantine role. Either pick an existing role or have the panel auto-create + auto-configure one. The auto-create path sweeps every channel in the guild and installs the deny overwrites described above.
Every change writes to data/antinuke_config.json immediately. Alerts go to the configured log channel and are mirrored to the audit log under the moderation category. Manual jail / unjail issued via the slash commands record there too, with the invoker, target, and reason all preserved.