Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The scanner

The scanner gives every new and edited message in the scanned channels a score from 0 to 100, decides what to do from the server’s thresholds, does it, and posts a mod card. It runs in memory, needs no network (no link is ever opened), and takes about a millisecond per message. The rules live in the polar-scan crate, which has no Discord dependency so it can later scan game chat and e-mail too.

message ──► skip rules ──► signals (links, threat lists, content, behaviour, adult links)
                               │
                               ▼
                         fusion: score 0–100
                               │
          thresholds + log-only mode + NSFW-link setting ──► action
                               │
          delete / timeout / kick / ban (Discord) ──► punishment history, counters
                               │
                               ▼
                   card queue ──► log channel ──► buttons (Scam / Not scam / Undo)

1. Which messages are scanned

A message is skipped when any of these holds:

  • it is not in a server (DMs), comes from a bot, a system user or a webhook, or is not an ordinary message or reply (joins, pins, boosts, …);
  • it has no text and no attachments; an edit is scanned only when its text changed;
  • the scanner is off in this server (/scanner disable);
  • the channel is exempt, or, in Only monitored channels mode, not monitored (/scanner channel, /scanner channel-mode); a thread follows its parent channel’s rule;
  • the author has an exempt role (/scanner exempt-role);
  • the author is a Moderator or higher (the server owner and members with Administrator are Owner).

For a forwarded message (empty text), the text of the forwarded messages is scanned, and forwarded attachments count too.

2. Signals

Every rule that matches adds a signal: a reason the card can show and a weight. Weights are log-odds: positive weights make a scam more likely, and several weak signals add up.

GroupSignalWeightNotes
LinksOn a threat list+7.0≈250 000 phishing domains from the configured lists. Sets a floor of 90.
On the server’s block list+6.0/scanner block. Sets a floor of 90.
Lookalike of a known brand+4.5Edit distance and Unicode homoglyphs: dіscord.com (Cyrillic і), steamcommunlty.com.
Masked link that hides where it goes+3.5[discord.com](https://evil.example)
Suspicious top-level domain+2.5
Punycode domain+2.0xn--…
IP address instead of a domain+1.5
Link shortener+1.5
On the server’s allow list−3.0/scanner allow. Turns off the link-shape checks for that domain, never the threat lists or the block list.
ContentScam phrase+1.0 to +3.0 eachAlso in leetspeak; at most 3 phrases count. “free nitro” is +3.0, “promo code” +1.0.
Critical phrase+3.0“I accidentally reported you”, “verify your account to avoid a ban”, … Sets a floor of 90, unless the message is clearly about a scam (a warning, a “PSA”, “someone sent me…”, the phrase in quotes); then it counts as an ordinary phrase.
“Move off Discord” step+1.5 each, +2.0 for two or more“add me on snap, it’s in my bio”, “texting from my alt because I’m shy”, “text me on my main account”, “saw you were online”
Link to a Discord user profile+1.0Often the last step of such a funnel.
Discord invite+1.5Not with /scanner invites allow:True.
Mass mention+2.05 or more user and role mentions.
@everyone / @here attempt+1.5
New account (under 7 days)+1.0Only together with a link or a scam phrase.
Joined under an hour ago+1.0Only together with a link or a scam phrase.
Recent punishments+0.5 each, at most +1.5Bans, kicks, timeouts and warnings in this server in the last 180 days.
BehaviourHacked-account burst+5.0The same content with a risky link in 3 or more channels within 120 s. Sets a floor of 97.
Flood+1.58 or more messages within 10 s.
AdultLink to an adult site0A separate category (below); never part of the score.

Official domains (discord.com, discord.gg, steamcommunity.com, github.com, youtube.com, …) are never flagged by the threat lists or the lookalike check, even if a list contains them. At most 20 links per message are looked at.

The behaviour tracker keeps, per member and server, a fingerprint (a 64-bit simhash) of their last messages for 2 minutes; two messages match when their fingerprints differ in at most 3 bits. Only links that could be malicious count towards a burst: an official or allow-listed link shared in several channels is not one, unless it is on a threat list.

3. Fusion

score = round(100 × 1 / (1 + e^-(−4 + sum of weights)))
score = max(score, floors)        floors: listed domain or critical phrase 90, burst 97

Without signals a message scores 2. One lookalike domain (+4.5) gives 62: a card. A lookalike plus “free nitro” (+3.0) gives 97. A domain on a threat list scores at least 90 whatever else the message says.

4. What happens

The server’s settings decide (/scanner thresholds, /scanner punishment, /scanner hacked, /scanner actions):

Condition (checked in this order)Action
Score below log (default 60)Nothing (at log level 9 the score is logged)
Log-only mode (/scanner actions enabled:False)Card only
Hacked-account burst and /scanner hacked is not Do nothingDelete every message of the burst; with Delete the messages and time out, also time the author out
Score at or above punish (default 97) and a punishment is setDelete and punish: timeout (default 1 hour), kick or ban
Score at or above delete (default 90)Delete
OtherwiseCard only

Adult-site links (/scanner nsfw-links): Ignore, Log, Delete or Delete and time out. Never applied in a channel Discord marks age-restricted (a thread follows its parent; a channel whose flag cannot be read counts as age-restricted), and only Log in log-only mode. When both the scam score and the adult-link setting call for an action, the stronger one is taken.

Repeat offenders: within 2 minutes after the scanner punished a member, further flags of that member only delete the message, so a spam wave gives one punishment and one ping.

Executing an action:

  • every Discord request carries the audit-log reason PolarBot scanner: score N;
  • a message that is already gone counts as deleted;
  • before a timeout, kick or ban, PolarBot checks that the member is not the server owner or the bot and that its own role is above the member’s top role; otherwise the card says why it could not act;
  • a timeout, kick or ban is recorded in the punishment history with the bot as moderator (a plain delete is not a punishment and is not recorded);
  • the server’s daily counters (flagged, acted on) are increased for /scanner status.

5. Cards

When the server has a log channel and a log level of at least 1, a flag becomes a card in the log channel. Without a log channel, or at log level 0, there is no card and no log entry (the action still happens). A card shows the author, the score, the action taken (or why it failed), the message text, up to 8 reasons (strongest first, an adult link always first), up to 20 domains (risky ones marked ⚠), a jump link while the message still exists, and how many further messages were merged into it. The mod role (/scanner mod-role) is pinged only for a punishment or a hacked account. User text is escaped and never pings anyone.

The card is the only record of a flag: everything the buttons need is on the card itself (the button state in its custom_id, the text and domains in the embed). Nothing about the message is stored in the database.

Buttons

Only Moderators and above can press them; the level is read fresh from the database. A card’s buttons work once: the first press disables them all, and a second press, even a simultaneous one, gets “already handled”.

ButtonDoes
ScamConfirms the flag: puts the card’s risky domains (those with a domain signal of their own, never official ones) on the server’s block list and saves an anonymized training example marked violation. On a log-only card it then offers the moderator (privately) Delete & time out, which checks the moderator’s rights against the member exactly like /timeout before acting.
Not scamSaves an anonymized training example marked ok, lifts the scanner’s timeout or ban, and deletes the scanner’s punishment-history row for it, so a false positive does not count against the member. Nothing is allow-listed (use /scanner allow).
UndoLifts the scanner’s timeout or ban (shown only when there is one to lift).

Training examples are anonymized before they are stored: mentions, IDs, the member’s names, invite codes, e-mail addresses and link paths are replaced with placeholders such as [user] and [path], no server or user ID is kept, and only the day is recorded. The operator exports (and thereby deletes) them with polarbot export-training.

6. Dry runs: /scanner analyze

/scanner analyze and the Analyze with PolarBot message command run the same link, content and threat-list rules with the server’s lists and settings on pasted text, a message link or a right-clicked message, and say what the scanner would do. They do not feed or read the behaviour tracker (no flood or burst signals), store nothing, delete nothing and post nothing. They also check up to three attachments by name and type only (nothing is downloaded): programs and installers (+4.0), scripts (+2.5), double extensions such as photo.png.exe (+5.0) and a content type that contradicts the extension (+2.5). The live scanner does not use the attachment rules yet.

7. Domain lists

The threat lists and the adult-site lists are plain domain lists or hosts files downloaded over https from the URLs in the config file. Each list is cached in the data directory and loaded at startup, so the scanner works right away after a restart, even offline. A new download replaces the cache only when it parses to at least one domain: an empty download, an HTML error page or garbage keeps the previous list. The in-memory index is swapped atomically, so the scanner never waits for an update.