-
Notifications
You must be signed in to change notification settings - Fork 1
AntiScam Checks #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
62825bd
541bdb0
62b594a
75201e6
0864ae0
2c69b94
2a17bcb
dfad828
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,11 +2,62 @@ import {EmbedBuilder, Events, Message, PermissionFlagsBits} from "discord.js"; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import {guildDB} from "../db"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Colors from "../util/colors"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type Patterns = { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| regex: RegExp, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whitelist: string[], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| predicate: (links: RegExpMatchArray[], self: Patterns) => boolean, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reason: string, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maxCount?: number, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fakeDiscordRegex = new RegExp(`([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\\.(com|net|app|gift|ru|uk)`, "ig"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const okayDiscordRegex = new RegExp(`([a-zA-Z-\\.]+\\.)?discord((?:app)|(?:status))?\\.(com|net|app)`, "i"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const fakeSteamRegex = new RegExp(`str?e[ea]?mcomm?m?un[un]?[un]?[tl]?[il][tl]?ty\\.(com|net|ru|us)`, "ig"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const sketchyRuRegex = new RegExp(`([a-zA-Z-\\.]+).ru.com`, "ig"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const phishingPatterns = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| regex: /([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\.(com|net|app|gift|ru|uk)/ig, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In a regular expression literal,
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whitelist: ['discord.com', 'discordapp.com'], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| whitelist: ['discord.com', 'discordapp.com'], | |
| whitelist: ['discord.com', 'discordapp.com', 'betterdiscord.app'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URL.parse is a relatively new method introduced in Node.js v22.0.0 and v21.2.0. If this bot is deployed on an older LTS version of Node.js (such as v18 or v20), calling URL.parse will throw a TypeError and crash the application.\n\nUsing new URL() wrapped in a try/catch block is a safer, backward-compatible approach.
const hosts = links.map(match => {\n const url = match[0];\n const fullUrl = url.startsWith('http') ? url : 'https://' + url;\n try {\n return new URL(fullUrl).host;\n } catch {\n return null;\n }\n }).filter(Boolean) as string[];
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The whitelist check should use the full hostname, not just the host. URL.parse().host may include the port number, which could cause legitimate URLs with ports to be flagged. Consider comparing against hostname instead or normalize the comparison.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using self.whitelist.includes(host) will cause legitimate subdomains of whitelisted domains (such as canary.discord.com, ptb.discord.com, or media.discordapp.com) to be flagged as fake domains. This will result in false positives where legitimate messages are deleted and users are muted.\n\nChecking if the host matches exactly or ends with . + the allowed domain resolves this issue.
| return hosts.some(host => !self.whitelist.includes(host)); | |
| return hosts.some(host => !self.whitelist.some(allowed => host === allowed || host.endsWith('.' + allowed))); |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing error handling for URL parsing. If the URL is malformed, URL.parse or new URL() will throw an error. Wrap the URL parsing in a try-catch block to prevent the entire spam detection from failing on malformed URLs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
URL.parse is a relatively new method introduced in Node.js v22.0.0 and v21.2.0. If this bot is deployed on an older LTS version of Node.js (such as v18 or v20), calling URL.parse will throw a TypeError and crash the application.\n\nUsing new URL() wrapped in a try/catch block is a safer, backward-compatible approach.
const hosts = links.map(match => {\n const url = match[0];\n const fullUrl = url.startsWith('http') ? url : 'https://' + url;\n try {\n return new URL(fullUrl).host;\n } catch {\n return null;\n }\n }).filter(Boolean) as string[];
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This predicate function is duplicated in the second pattern (lines 30-37). Consider extracting this into a shared helper function to reduce code duplication and improve maintainability.
| const phishingPatterns = [ | |
| { | |
| regex: /([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\.(com|net|app|gift|ru|uk)/ig, | |
| whitelist: ['discord.com', 'discordapp.com'], | |
| predicate: (links, self) => { | |
| const hosts = links.map(match => { | |
| const url = match[0]; | |
| const fullUrl = url.startsWith('http') ? url : `https://${url}`; | |
| return URL.parse(fullUrl)?.host; | |
| }).filter(Boolean); | |
| return hosts.some(host => !self.whitelist.includes(host)); | |
| }, | |
| reason: 'Fake Discord Domain' | |
| }, | |
| { | |
| regex: /str?e[ea]?mcomm?m?un[un]?[un]?[tl]?[il][tl]?ty\.(com|net|ru|us)/ig, | |
| whitelist: ['steamcommunity.com'], | |
| predicate: (links, self) => { | |
| const hosts = links.map(match => { | |
| const url = match[0]; | |
| const fullUrl = url.startsWith('http') ? url : `https://${url}`; | |
| return URL.parse(fullUrl)?.host; | |
| }).filter(Boolean); | |
| return hosts.some(host => !self.whitelist.includes(host)); | |
| }, | |
| function hasNonWhitelistedHost(links: RegExpMatchArray[], whitelist: string[]): boolean { | |
| const hosts = links.map(match => { | |
| const url = match[0]; | |
| const fullUrl = url.startsWith('http') ? url : `https://${url}`; | |
| return URL.parse(fullUrl)?.host; | |
| }).filter(Boolean); | |
| return hosts.some(host => !whitelist.includes(host as string)); | |
| } | |
| const phishingPatterns = [ | |
| { | |
| regex: /([a-zA-Z-\\.]+)?d[il][il]?scorr?(cl|[ldb])([a-zA-Z-\\.]+)?\.(com|net|app|gift|ru|uk)/ig, | |
| whitelist: ['discord.com', 'discordapp.com'], | |
| predicate: (links, self) => hasNonWhitelistedHost(links, self.whitelist), | |
| reason: 'Fake Discord Domain' | |
| }, | |
| { | |
| regex: /str?e[ea]?mcomm?m?un[un]?[un]?[tl]?[il][tl]?ty\.(com|net|ru|us)/ig, | |
| whitelist: ['steamcommunity.com'], | |
| predicate: (links, self) => hasNonWhitelistedHost(links, self.whitelist), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using self.whitelist.includes(host) will cause legitimate subdomains of whitelisted domains (such as help.steamcommunity.com) to be flagged as fake domains.\n\nChecking if the host matches exactly or ends with . + the allowed domain resolves this issue.
return hosts.some(host => !self.whitelist.some(allowed => host === allowed || host.endsWith('.' + allowed)));There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In a regular expression literal, \\ represents a literal backslash. Therefore, -\\ inside the character class [a-zA-Z-\\.] creates a character range from - (ASCII 45) to \\ (ASCII 92). This range matches many unintended characters such as numbers, colons, slashes, and uppercase letters, which can lead to false positives.\n\nUsing [a-zA-Z.-]+ (placing the hyphen at the end of the character class) correctly matches only letters, dots, and hyphens without creating an unintended range.
| whitelist: [], | |
| regex: /([a-zA-Z.-]+)\\.ru\\.com/ig, |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The equality check should use strict equality. Replace '==' with '===' to avoid type coercion issues and follow JavaScript best practices.
| predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on. | |
| predicate: (links, self) => links.length === self.maxCount, // this should probably be more than 4 later on. |
Copilot
AI
Jan 19, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This generic URL regex pattern is too broad and will match many legitimate URLs. It will trigger on any message containing 4+ URLs regardless of their legitimacy. Consider adding common legitimate domains to the whitelist or refining this pattern to avoid false positives.
| whitelist: [], | |
| predicate: (links, self) => links.length == self.maxCount, // this should probably be more than 4 later on. | |
| whitelist: [ | |
| 'discord.com', | |
| 'discordapp.com', | |
| 'steamcommunity.com', | |
| 'github.com', | |
| 'gitlab.com', | |
| 'bitbucket.org', | |
| 'google.com', | |
| 'youtube.com', | |
| 'youtu.be', | |
| 'twitch.tv', | |
| 'twitter.com', | |
| 'x.com', | |
| 'reddit.com' | |
| ], | |
| predicate: (links, self) => { | |
| const suspiciousLinks = links.filter(match => { | |
| const url = match[0].toLowerCase(); | |
| return !self.whitelist.some(domain => url.includes(domain)); | |
| }); | |
| return suspiciousLinks.length === self.maxCount; // this should probably be more than 4 later on. | |
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using links.length == self.maxCount means that if a message contains more than 4 links (e.g., 5 or 10 links), the predicate will evaluate to false, allowing the spam message to bypass the filter.\n\nChanging this to >= ensures that any message with 4 or more links is caught.
| reason: 'Potential Scam Message', | |
| predicate: (links, self) => !!self.maxCount && links.length >= self.maxCount, // this should probably be more than 4 later on. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh!
There was an error while loading. Please reload this page.