-
-
Notifications
You must be signed in to change notification settings - Fork 48
Anti-Phish #180
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?
Anti-Phish #180
Changes from all commits
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 | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,8 @@ | ||||||||||||||||||
| { | ||||||||||||||||||
| "folders": [ | ||||||||||||||||||
| { | ||||||||||||||||||
| "path": "." | ||||||||||||||||||
| } | ||||||||||||||||||
| ], | ||||||||||||||||||
| "settings": {} | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+1
to
+8
|
||||||||||||||||||
| { | |
| "folders": [ | |
| { | |
| "path": "." | |
| } | |
| ], | |
| "settings": {} | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| const heuristics = require('../phishingHeuristics'); | ||
| const { checkPhishing, setCustomPatterns } = require('../phishingService'); | ||
|
|
||
| describe('phishing heuristics', () => { | ||
| test('isIpAddress detects IPv4 and IPv6', () => { | ||
| expect(heuristics.isIpAddress('127.0.0.1')).toBe(true); | ||
| expect(heuristics.isIpAddress('::1')).toBe(true); | ||
| expect(heuristics.isIpAddress('example.com')).toBe(false); | ||
| }); | ||
|
|
||
| test('domainSimilarity returns info for typos', () => { | ||
| const res = heuristics.domainSimilarity('paypal.com', ['paypal.com']); | ||
| expect(res).toBeNull(); | ||
| const res2 = heuristics.domainSimilarity('paypa1.com', ['paypal.com']); | ||
| expect(res2).not.toBeNull(); | ||
| expect(res2.match).toBe('paypal.com'); | ||
| }); | ||
|
|
||
| test('extractFirstUrlFromMessage picks a URL', () => { | ||
| expect(heuristics.extractFirstUrlFromMessage('no link here')).toBeNull(); | ||
| expect( | ||
| heuristics.extractFirstUrlFromMessage('visit https://example.com now') | ||
| ).toBe('https://example.com'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('checkPhishing integration', () => { | ||
| test('flags obvious phishing link', async () => { | ||
| const { isPhishing, reasons } = await checkPhishing({ url: 'http://paypal.com.example.tk/login' }); | ||
| expect(isPhishing).toBe(true); | ||
| expect(reasons.some(r => r.includes('Suspicious TLD'))).toBe(true); | ||
| }); | ||
|
|
||
| test('returns false for benign https site', async () => { | ||
| const { isPhishing } = await checkPhishing({ url: 'https://example.com' }); | ||
| expect(isPhishing).toBe(false); | ||
| }); | ||
|
Comment on lines
+28
to
+37
|
||
|
|
||
| test('custom patterns can match', async () => { | ||
| setCustomPatterns(['evil']); | ||
| const { isPhishing, reasons } = await checkPhishing({ url: 'https://good.com/evil' }); | ||
| expect(isPhishing).toBe(true); | ||
| expect(reasons.some(r => r.includes('Custom pattern'))).toBe(true); | ||
| }); | ||
|
|
||
| test('override configuration works', async () => { | ||
| // artificially lower threshold so non-secure example flags | ||
| const { isPhishing } = await checkPhishing( | ||
| { url: 'https://example.com' }, | ||
| { config: { thresholds: { phishingScore: 0 } } } | ||
| ); | ||
| expect(isPhishing).toBe(true); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -245,6 +245,26 @@ | |
| ] | ||
| }, | ||
| { | ||
| <<<<<<< HEAD | ||
| "name": "phishing-log-channel-id", | ||
| "humanName": { | ||
| "de": "Phishing-Protokoll-Kanal", | ||
| "en": "Phishing Log Channel" | ||
| }, | ||
| "default": { | ||
| "en": "", | ||
| "de": "" | ||
| }, | ||
| "description": { | ||
| "en": "Optional channel where auto phishing detections are logged.", | ||
| "de": "Optionaler Kanal, in dem automatische Phishing-Erkennungen protokolliert werden." | ||
| }, | ||
| "type": "channelID", | ||
| "allowNull": true | ||
| }, | ||
| { | ||
| ======= | ||
| >>>>>>> bdf48c957889f18888d1525806101cb792e35246 | ||
| "name": "scam_link_level", | ||
|
Comment on lines
+248
to
268
|
||
| "humanName": { | ||
| "de": "Level der Scam-Link-Erkennung", | ||
|
|
@@ -316,6 +336,26 @@ | |
| "content": "string" | ||
| }, | ||
| { | ||
| <<<<<<< HEAD | ||
| "name": "phishing-custom-patterns", | ||
| "humanName": { | ||
| "de": "Eigene Phishing-Muster", | ||
| "en": "Custom phishing patterns" | ||
| }, | ||
| "default": { | ||
| "en": [], | ||
| "de": [] | ||
| }, | ||
| "description": { | ||
| "en": "Add your own regexes/keywords/strings to flag as phishing (one entry per line, RegExp syntax supported)", | ||
| "de": "Füge eigene Regexe/Stichwörter/String hinzu, die als Phishing markiert werden sollen (jeweils eine pro Zeile, RegExp-Syntax möglich)" | ||
| }, | ||
| "type": "array", | ||
| "content": "string" | ||
| }, | ||
| { | ||
| ======= | ||
| >>>>>>> bdf48c957889f18888d1525806101cb792e35246 | ||
| "name": "action_on_posting_blacklisted_word", | ||
|
Comment on lines
+339
to
359
|
||
| "humanName": { | ||
| "de": "Aktion bei gesperrtem Wort", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,6 +70,28 @@ | |
| "allowEmbed": true | ||
| }, | ||
| { | ||
| <<<<<<< HEAD | ||
| "name": "phishing-log-entry", | ||
| "humanName": {}, | ||
| "default": { | ||
| "en": "**Phishing detected** by %user% in %channel%:\n%content%", | ||
| "de": "**Phishing erkannt** von %user% in %channel%:\n%content%" | ||
| }, | ||
| "description": { | ||
| "en": "Log message sent when auto phishing detection triggers", | ||
| "de": "Protokollnachricht, die gesendet wird, wenn die automatische Phishing-Erkennung auslöst" | ||
| }, | ||
| "type": "string", | ||
| "allowEmbed": true, | ||
| "params": [ | ||
| { "name": "user", "description": { "en": "Tag of the user who posted the link" } }, | ||
| { "name": "channel", "description": { "en": "Channel where the link was posted" } }, | ||
| { "name": "content", "description": { "en": "Original message content" } } | ||
| ] | ||
| }, | ||
| { | ||
| ======= | ||
| >>>>>>> bdf48c957889f18888d1525806101cb792e35246 | ||
| "name": "submitted-report-message", | ||
|
Comment on lines
+73
to
95
|
||
| "humanName": {}, | ||
| "default": { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,11 @@ const {Op} = require('sequelize'); | |
| const {localize} = require('../../../src/functions/localize'); | ||
| const {embedType} = require('../../../src/functions/helpers'); | ||
| const {scheduleJob} = require('node-schedule'); | ||
| <<<<<<< HEAD | ||
| // import phishing service so that we can feed it custom patterns from configuration | ||
| const { setCustomPatterns } = require('../phishingService'); | ||
| ======= | ||
| >>>>>>> bdf48c957889f18888d1525806101cb792e35246 | ||
|
Comment on lines
+6
to
+10
|
||
| const memberCache = {}; | ||
| const durationParser = require('parse-duration'); | ||
|
|
||
|
|
@@ -33,6 +38,16 @@ exports.run = async (client) => { | |
| }); | ||
| } | ||
|
|
||
| <<<<<<< HEAD | ||
| // configure phishing service with custom patterns from settings | ||
| const customPatterns = client.configurations['moderation']['config']['phishing-custom-patterns']; | ||
| if (Array.isArray(customPatterns) && customPatterns.length > 0) { | ||
| setCustomPatterns(customPatterns); | ||
| client.logger.info('[moderation] loaded ' + customPatterns.length + ' custom phishing pattern(s)'); | ||
| } | ||
|
|
||
| ======= | ||
| >>>>>>> bdf48c957889f18888d1525806101cb792e35246 | ||
|
Comment on lines
+41
to
+50
|
||
| const verificationConfig = client.configurations['moderation']['verification']; | ||
| if (!verificationConfig.enabled || !verificationConfig['restart-verification-channel']) return; | ||
| const channel = await client.channels.fetch(verificationConfig['restart-verification-channel']).catch(() => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,6 +2,11 @@ const {moderationAction} = require('../moderationActions'); | |||||||||||||||
| const {embedType} = require('../../../src/functions/helpers'); | ||||||||||||||||
| const {localize} = require('../../../src/functions/localize'); | ||||||||||||||||
| const stopPhishing = require('stop-discord-phishing'); | ||||||||||||||||
| <<<<<<< HEAD | ||||||||||||||||
| // built-in phishing service (uses ML/heuristics plus configurable patterns) | ||||||||||||||||
| const { checkPhishing } = require('../phishingService'); | ||||||||||||||||
| ======= | ||||||||||||||||
| >>>>>>> bdf48c957889f18888d1525806101cb792e35246 | ||||||||||||||||
|
Comment on lines
+5
to
+9
|
||||||||||||||||
| <<<<<<< HEAD | |
| // built-in phishing service (uses ML/heuristics plus configurable patterns) | |
| const { checkPhishing } = require('../phishingService'); | |
| ======= | |
| >>>>>>> bdf48c957889f18888d1525806101cb792e35246 | |
| // built-in phishing service (uses ML/heuristics plus configurable patterns) | |
| const { checkPhishing } = require('../phishingService'); |
Copilot
AI
Feb 27, 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.
Unresolved Git merge conflict markers in the scam-link handling block will cause a syntax error and prevent moderation from running. Resolve the conflict and ensure the intended phishing checks execute in the correct order.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| // Central configuration for phishing detection heuristics | ||
|
|
||
| module.exports = { | ||
| // list of well-known domains to compare against for typosquatting | ||
| legitDomains: [ | ||
| 'paypal.com', 'google.com', 'bankofamerica.com', 'apple.com', 'amazon.com', | ||
| 'microsoft.com', 'facebook.com', 'twitter.com', 'instagram.com', 'netflix.com', | ||
| 'chase.com', 'wellsfargo.com', 'citibank.com', 'usbank.com', 'capitalone.com' | ||
| ], | ||
|
|
||
| phishingKeywords: [ | ||
| 'login', 'secure', 'account', 'verify', 'update', 'banking', 'password', | ||
| 'signin', 'auth', 'recovery', 'billing', 'payment', 'support', 'helpdesk' | ||
| ], | ||
|
|
||
| suspiciousTLDs: ['tk', 'ml', 'ga', 'cf', 'gq', 'xyz', 'top', 'club', 'site', 'online'], | ||
|
|
||
| urlShorteners: ['bit.ly', 'tinyurl.com', 'goo.gl', 't.co', 'ow.ly', 'is.gd', 'buff.ly'], | ||
|
|
||
| // scoring thresholds / weights - can be tuned externally | ||
| thresholds: { | ||
| phishingScore: 50 // score above which we consider a link phishing | ||
| }, | ||
|
|
||
| weights: { | ||
| ipAddress: 30, | ||
| shortener: 20, | ||
| typosquatting: 40, | ||
| idnHomograph: 35, | ||
| idnSimilarity: 20, | ||
| suspiciousTld: 15, | ||
| keyword: 10, | ||
| longUrl: 10, | ||
| nonHttps: 25, | ||
| dnsFailure: 20, | ||
| atSymbol: 30, | ||
| encodedChars: 15, | ||
| redirect: 20, | ||
| googleSafeBrowsing: 50, | ||
| virusTotal: 30, | ||
| phishTank: 60 | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,101 @@ | ||||
| const stringSimilarity = require('string-similarity'); | ||||
| const tld = require('tldjs'); | ||||
| const punycode = require('punycode'); | ||||
| const { URL } = require('url'); | ||||
|
|
||||
|
Comment on lines
+4
to
+5
|
||||
| const { URL } = require('url'); |
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.
Merge conflict markers are present in README.md, which will render incorrectly on GitHub. Resolve the conflict and keep the intended documentation changes.