Skip to content

Safety Systems

Johnny Xmas edited this page Sep 22, 2026 · 1 revision

Safety Systems

JohnnyBot ships three automatic protection systems that run without being invoked. They cover different threats and are independent of each other β€” each can be tuned or turned off on its own.

Navigation

Table of Contents

  1. What Each System Covers
  2. Required Bot Permissions
  3. Raid Protection
  4. Avatar Clustering
  5. Anti-Nuke Protection
  6. Message Spam Protection
  7. Configuration Reference
  8. Tuning Guidance
  9. Known Limits

What Each System Covers

System Threat Trigger Default Response
Raid protection Outside accounts flooding in 6 joins in 30s Pause invites + DMs for 60 min, alert
Anti-nuke A trusted account gone rogue 3 destructive audit-log actions in 60s, a prune, or a permission grant Strip the actor's roles, revert the change, alert
Spam protection What a raid does once inside Same link in 2+ channels in 30s, or 3+ mentions in one message Delete, timeout (or kick if the account is new), alert

The three are deliberately separate. Raid protection watches the door, anti-nuke watches people who already have keys, and spam protection watches what gets said. A real raid usually trips more than one.

Who each system can act against

This differs per system, and the difference is important:

System Moderators Server owner The bot itself
Raid protection Not targeted β€” the lockdown acts on the server, not on individuals. /raid kick_recent explicitly excludes them Excluded from /raid kick_recent n/a
Anti-nuke Deliberately NOT exempt Exempt from the role strip (it wouldn't reduce their power anyway); still alerts Always excluded
Spam protection Exempt Exempt via the moderator check, if they hold Manage Messages Bot messages are ignored

Anti-nuke not exempting moderators is the entire point of it. The threat it exists for is a trusted account β€” a moderator whose session was hijacked, or an admin whose token leaked. Exempting moderators would make it useless against the only thing it was built to catch. If you are a moderator and you delete three channels in a minute, it will strip your roles; that is working as intended, and another moderator restores them in seconds with /assign_role.

Where exemption does apply, it's based on the guild-wide Manage Messages permission β€” the same check every other moderator gate in the bot uses, with no role name to configure.


Required Bot Permissions

These systems act on members, so the bot's own role needs the matching permissions. Detection works regardless β€” if a permission is missing, the alert still fires and says the response was blocked, rather than failing silently.

Permission Needed For
Manage Server Pausing invites and DMs during a raid lockdown
Moderate Members Timing out spam offenders
Kick Members Kicking new-account spammers
Manage Roles Stripping a compromised account's roles, reverting a permission grant

The bot also cannot act on anyone whose highest role sits at or above its own. This is a Discord rule, not a bot limitation, and it matters most for anti-nuke: a compromised Administrator usually outranks the bot. In that case the alert explicitly says the roles could not be stripped and that manual action is needed. Position the bot's role high enough to be useful, but understand it can never outrank the server owner.

No new intents need enabling in the Developer Portal β€” the audit-log intent anti-nuke relies on is non-privileged.


Raid Protection

Detection

Every join is timestamped in a per-guild sliding window. When RAID_JOIN_THRESHOLD joins land inside RAID_JOIN_WINDOW_SECONDS, the lockdown fires.

Bot accounts are not exempt from this count, unlike the voice chaperone and DM auto-kick β€” bulk-joining bot accounts are a common raid vector.

Response

The bot sets Discord's own incident actions β€” invites_disabled_until and dms_disabled_until β€” for RAID_LOCKDOWN_MINUTES. This pauses new invite use and member-to-member DMs, cutting off both the inflow and the scam-DM payload.

The important property: Discord lifts the pause itself. There is no timer to survive, no state file to corrupt, and a bot restart mid-raid changes nothing. The bot reads the live state back from the guild, so it also sees a lockdown a moderator applied by hand in the Discord UI.

While a lockdown is active, continuing joins do not re-alert β€” you get one notification per incident, not one per raider.

Manual control

  • /raid lockdown enabled:True [minutes] β€” lock down immediately, without waiting for the threshold
  • /raid lockdown enabled:False β€” lift early
  • /raid status β€” current settings, whether a lockdown is active and until when, and joins in the current window

Reviewing the damage

/raid recent_joins [minutes] lists who arrived, flags accounts younger than RAID_NEW_ACCOUNT_HOURS, and appends avatar clustering (below).

/raid kick_recent minutes:<n> kicks recently-joined accounts that are also flagged as new. It defaults to a dry run β€” you see the list first, and must re-run with dry_run:False to actually kick. Moderators, the server owner, and anyone outranking you are always excluded.


Avatar Clustering

Raid accounts are bought or generated in bulk and reuse a small set of profile pictures. Clustering turns a flat list of arrivals into a verdict:

πŸ–ΌοΈ 12 of 14 share only 1 distinct avatar(s) β€” bulk-created accounts reuse profile pictures

That is a raid. Fourteen distinct avatars is probably a real surge of arrivals, and the difference is exactly what you need before reaching for /raid kick_recent.

Clustering appears in the automatic raid alert and in /raid recent_joins, as a separate follow-up message.

How it works

The bot downloads each member's avatar and hashes the image bytes (SHA-256). Identical images produce identical hashes regardless of which account uploaded them.

It deliberately does not group by Discord's avatar hash from the API, which would be free and need no network. Whether two accounts uploading the same image receive the same key is undocumented and was not verified β€” a feature built on that assumption could silently find nothing. The key is used as a download cache, so nothing is fetched twice.

What the numbers mean

  • Accounts with no avatar are counted but never clustered. They share a Discord default derived from their user id, so grouping them would be meaningless β€” but a lot of them at once is its own raid signal, reported separately.
  • A failed download is reported separately from "has no avatar set", and is excluded from the comparison counts. An account that has a picture the bot couldn't fetch is never described as having none.

Cost

Downloads are capped at 5 concurrent, skipped entirely past RAID_AVATAR_SCAN_LIMIT members, and cached per avatar for RAID_AVATAR_CACHE_TTL. The scan runs only after the lockdown is applied and the alert is sent, so no protective action ever waits on network calls.

Matching is exact. A re-encoded or resized variant of the same picture will not match β€” that would need perceptual hashing and an extra image dependency, which bulk-bought avatars don't warrant.


Anti-Nuke Protection

Raid protection assumes the threat is outside. Anti-nuke covers the opposite case: an account that already has your trust β€” a moderator whose session was hijacked, a compromised admin, or a rogue integration β€” acting directly against the Discord API, which JohnnyBot's own commands never see.

Detection

Driven by the audit-log gateway event, so it reacts in near real time with no polling.

Destructive burst β€” ANTI_NUKE_THRESHOLD of these by the same actor within ANTI_NUKE_WINDOW_SECONDS:

  • Channel deletion
  • Role deletion
  • Kick
  • Ban
  • Webhook creation (a common exfiltration/spam vector after a compromise)

Member prune β€” fires on a single occurrence. One prune entry can remove hundreds of members, so waiting for three of them is the wrong shape entirely.

Permission escalation β€” fires on a single occurrence, because one is already the attack:

  • A role edited to grant administrator, manage_guild, manage_roles, manage_channels, ban_members or kick_members it didn't have
  • A member assigned a role that already carries one of those

Response

By default (ANTI_NUKE_ACTION = 'strip_roles') the bot removes every role from the offending account, then alerts. Response time is the whole point β€” an alert alone leaves the window open for as long as it takes a human to notice, during which a compromised admin keeps deleting.

This is safe to run unattended because it is reversible: a moderator restores the account with /assign_role in seconds once they've confirmed it was a false alarm. It is deliberately not a kick, which would end the session, tip off the attacker, and lose the chance to investigate quietly.

The escalation itself is also reverted β€” the role's permissions are restored, or the privileged role is removed from whoever was granted it. Stripping only the actor would leave the payoff standing: the alt account keeps its administrator role and the attack succeeded anyway.

Set ANTI_NUKE_ACTION = 'alert' to notify without acting.

Exclusions that matter

Moderators are not exempt, by design β€” see Who each system can act against. A trusted account is the threat here, so exempting trusted accounts would defeat the system entirely.

The bot's own actions are never counted. Discord attributes every bot-token action to the bot, not to the moderator who ran the command β€” so without this, /server_restore, /raid kick_recent, and the purge commands would trip anti-nuke on themselves.

Granting a permission the actor already holds is treated as routine administration. You cannot escalate to something you already have. This keeps a real admin building out a Moderator role from locking themselves out of their own server. The classic attack β€” a manage_roles holder granting administrator to themselves or an alt β€” still fires, because the actor lacked administrator. An actor who already has it and then turns destructive is caught by the burst path instead.

The server owner is always exempt from the role strip. Stripping the owner's roles wouldn't reduce their power anyway.


Message Spam Protection

This covers what a raid does once the accounts are inside: blasting a scam link across every channel, and mass pings to drive attention to it.

Cross-channel link spam

Trigger: the same link-bearing message posted in more than one channel within SPAM_CROSSPOST_WINDOW_SECONDS.

This is behavioral rather than a domain blocklist, and that is a deliberate choice. Scam domains burn within days, so a blocklist is stale before it ships and needs constant maintenance. The cross-posting pattern holds whatever the domain is, so this catches a brand-new scam domain the first time it appears β€” with nothing to maintain.

The same text twice in the same channel is ordinary repetition and does not trigger. The cross-channel spread is the signal.

Matching ignores case and whitespace, so re-posting in ALL CAPS or with extra spaces doesn't evade it. Every tracked copy is deleted, not just the message that tripped the detector β€” otherwise the first post stays up in the channel it landed in.

Mention spam

Trigger: SPAM_MENTION_THRESHOLD or more distinct mentions (users + roles) in a single message. Discord de-duplicates mentions, so @bob @bob @bob counts once β€” the threshold is distinct targets.

Response

Delete, then punish, then alert:

  • Account older than SPAM_NEW_ACCOUNT_DAYS β†’ timeout for SPAM_TIMEOUT_MINUTES
  • Account younger β†’ kicked, on the reasoning that it's a throwaway created for the run

Both detectors use the same escalation. An established member always gets the reversible outcome.


Configuration Reference

All of these live in config.py (copied from config_example.py). Every value is read with a fallback, so a config.py that predates these features keeps working β€” the defaults below apply until you add them.

Raid protection

Setting Default Description
RAID_PROTECTION_ENABLED True Master switch for join-burst detection
RAID_JOIN_THRESHOLD 6 Joins within the window that trigger a lockdown
RAID_JOIN_WINDOW_SECONDS 30 The sliding window
RAID_LOCKDOWN_MINUTES 60 How long invites/DMs stay paused
RAID_NEW_ACCOUNT_HOURS 24 Accounts younger than this are flagged in review/kick
RAID_AVATAR_CLUSTERING_ENABLED True Master switch for avatar clustering
RAID_AVATAR_SCAN_LIMIT 50 Skip the scan past this many members
RAID_AVATAR_CACHE_TTL 300 Seconds to reuse a hashed avatar

Anti-nuke

Setting Default Description
ANTI_NUKE_ENABLED True Master switch
ANTI_NUKE_THRESHOLD 3 Destructive actions by one actor that trigger it
ANTI_NUKE_WINDOW_SECONDS 60 The window they must fall inside
ANTI_NUKE_ACTION 'strip_roles' 'strip_roles' or 'alert'

Spam protection

Setting Default Description
SPAM_PROTECTION_ENABLED True Master switch
SPAM_CROSSPOST_WINDOW_SECONDS 30 Same link in 2+ channels this fast
SPAM_MENTION_THRESHOLD 3 Distinct mentions in one message
SPAM_TIMEOUT_MINUTES 10 Timeout length for an offender
SPAM_NEW_ACCOUNT_DAYS 2 Younger offenders are kicked instead

Runtime toggles

/raid protection, /nuke_protection and /spam_protection flip these at runtime without a restart β€” useful mid-incident. Runtime toggles do not persist: config.py decides the value again on the next restart. Edit the file for a permanent change.


Tuning Guidance

Size the raid threshold against your own server's history, not a guess. Look at your member list sorted by join date and find the busiest 30 seconds you've had organically. Set the threshold above that. A server that regularly drops invite links in group chats, or gets traffic spikes from Server Discovery, will trip 6-in-30s legitimately.

The default is deliberately on the sensitive side because the raid response is cheap and self-reverting β€” a false positive costs you an hour of paused invites and a notification, not a mass kick. That asymmetry is why the automatic response never kicks anyone.

Anti-nuke's threshold of 3 is low on purpose. Deleting three channels in a minute is rare in normal administration, and the cost of a false positive (a moderator has to re-add their own roles) is much lower than the cost of a miss (half your server is gone). If your admins routinely do bulk restructuring, raise it or set ANTI_NUKE_ACTION = 'alert' during planned work.

Mention spam at 3 will occasionally catch a legitimate group ping. If your server does a lot of "hey @alice @bob @carol, meeting time", raise it to 5.

A slow-drip raid evades burst detection by design. Accounts joining every 20 seconds for an hour never cross 6-in-30s. Avatar clustering via /raid recent_joins is the tool for that case β€” check it if arrivals look off even when nothing fired.


Known Limits

  • Exact-match avatar comparison only. A resized or re-encoded copy of the same image will not cluster.
  • Burst-detection state is in memory. A bot restart resets the join and audit-log windows. Lockdown state is unaffected, since it lives on the guild itself.
  • The bot cannot act on anyone who outranks it. Most relevant for a compromised Administrator β€” the alert will say the response was blocked.
  • Anti-nuke sees only what the audit log records, and Discord writes those entries with a short delay under heavy load.
  • Nothing here is a substitute for Discord's own verification level and membership screening. These systems are a backstop for what gets through, not a replacement for the front door.

Related Pages