Skip to content

Commit 111db48

Browse files
committed
Add incident response and passport alt detection
This change introduces the Incident Response plugin to correlate Automod, raid, impersonation, scam, and nuke signals into scored incidents with severity policies, lockouts, and dashboard management. It also adds Passport alt-network signal collection and matching for likely alt accounts, including optional MaxMind geo lookups and dashboard controls for dismissing/clearing data. In parallel, Automod gains Domain Intelligence heuristics for risky domains and related rule config, with schema, DB migrations, permission defaults, and tests for the new scoring and clustering logic.
1 parent 9adc108 commit 111db48

50 files changed

Lines changed: 3659 additions & 51 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ DREAMLINER_ONE_SKU_ID=1537178843033501727
3535
# Discofy" message context command to submit quotes.
3636
DISCOFY_API_KEY=
3737

38+
# --- Passport Alts (network-signal alt detection) ---
39+
# Optional path to a local MaxMind GeoLite2-City .mmdb file. When set, verifications also record
40+
# a coarse city/region used as a secondary alt-matching signal. When unset, matching falls back to
41+
# IP/subnet only — no third-party lookups are ever made. Get a free file+license at
42+
# https://www.maxmind.com/en/geolite2/signup
43+
# GEOIP_DB_PATH=
44+
3845
# --- Social Notifications (YouTube uploads) ---
3946
# YouTube Data API v3 key (Google Cloud Console → enable "YouTube Data API v3" → Credentials).
4047
# Required to resolve creator handles/URLs and poll for new uploads.

config/default.server.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,45 @@ plugins:
190190
dm_flagged_member: false
191191
auto_action: none
192192
can_status: false
193+
incident_response:
194+
enabled: false
195+
config:
196+
correlation_window_ms: 1800000
197+
thresholds:
198+
medium: 6
199+
high: 14
200+
critical: 26
201+
sources:
202+
automod: true
203+
raid: true
204+
impersonation: true
205+
scam_protect: true
206+
nuke_detection: true
207+
nuke:
208+
channel_delete_count: 3
209+
channel_delete_window_ms: 30000
210+
role_delete_count: 3
211+
role_delete_window_ms: 30000
212+
ban_count: 5
213+
ban_window_ms: 60000
214+
kick_count: 5
215+
kick_window_ms: 60000
216+
webhook_count: 3
217+
webhook_window_ms: 60000
218+
admin_grant_new_account_hours: 72
219+
policy_low:
220+
actions: []
221+
notify_roles: []
222+
policy_medium:
223+
actions: []
224+
notify_roles: []
225+
policy_high:
226+
actions: []
227+
notify_roles: []
228+
policy_critical:
229+
actions: []
230+
notify_roles: []
231+
can_manage: false
193232
raid_mesh:
194233
enabled: false
195234
config: {}
@@ -283,6 +322,7 @@ plugins:
283322
disabled_title: Verification is off
284323
disabled_body: This server isn't using Passport right now.
285324
remember_verifications: true
325+
alt_detection: false
286326
min_account_age_seconds: 0
287327
bypass_role_ids: []
288328
timeout_action: none
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
CREATE TABLE IF NOT EXISTS `passport_network_signals` (
2+
`guild_id` text NOT NULL,
3+
`user_id` text NOT NULL,
4+
`ip_address` text NOT NULL,
5+
`country` text,
6+
`region` text,
7+
`city` text,
8+
`verified_at` integer NOT NULL,
9+
PRIMARY KEY(`guild_id`, `user_id`)
10+
);
11+
--> statement-breakpoint
12+
CREATE INDEX IF NOT EXISTS `passport_network_signals_guild`
13+
ON `passport_network_signals` (`guild_id`);
14+
--> statement-breakpoint
15+
CREATE TABLE IF NOT EXISTS `passport_alt_dismissals` (
16+
`guild_id` text NOT NULL,
17+
`user_id_a` text NOT NULL,
18+
`user_id_b` text NOT NULL,
19+
`dismissed_at` integer NOT NULL,
20+
`dismissed_by` text NOT NULL,
21+
PRIMARY KEY(`guild_id`, `user_id_a`, `user_id_b`)
22+
);
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
CREATE TABLE IF NOT EXISTS `incidents` (
2+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
3+
`guild_id` text NOT NULL,
4+
`entity_type` text NOT NULL,
5+
`entity_id` text NOT NULL,
6+
`severity` text DEFAULT 'low' NOT NULL,
7+
`risk_score` integer DEFAULT 0 NOT NULL,
8+
`status` text DEFAULT 'open' NOT NULL,
9+
`title` text NOT NULL,
10+
`signal_count` integer DEFAULT 0 NOT NULL,
11+
`source_count` integer DEFAULT 0 NOT NULL,
12+
`responded_severity` text,
13+
`actions_taken` text DEFAULT '[]' NOT NULL,
14+
`first_signal_at` integer NOT NULL,
15+
`last_signal_at` integer NOT NULL,
16+
`resolved_by` text,
17+
`resolved_at` integer,
18+
`created_at` integer NOT NULL,
19+
`updated_at` integer NOT NULL
20+
);
21+
--> statement-breakpoint
22+
CREATE INDEX IF NOT EXISTS `incidents_guild_status` ON `incidents` (`guild_id`, `status`);
23+
--> statement-breakpoint
24+
CREATE INDEX IF NOT EXISTS `incidents_guild_entity` ON `incidents` (`guild_id`, `entity_type`, `entity_id`);
25+
--> statement-breakpoint
26+
CREATE TABLE IF NOT EXISTS `incident_signals` (
27+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
28+
`incident_id` integer NOT NULL,
29+
`guild_id` text NOT NULL,
30+
`source` text NOT NULL,
31+
`signal_type` text NOT NULL,
32+
`weight` integer NOT NULL,
33+
`entity_type` text NOT NULL,
34+
`entity_id` text NOT NULL,
35+
`secondary_entity_type` text,
36+
`secondary_entity_id` text,
37+
`reason` text NOT NULL,
38+
`detail` text,
39+
`created_at` integer NOT NULL
40+
);
41+
--> statement-breakpoint
42+
CREATE INDEX IF NOT EXISTS `incident_signals_incident` ON `incident_signals` (`incident_id`);
43+
--> statement-breakpoint
44+
CREATE INDEX IF NOT EXISTS `incident_signals_guild_created` ON `incident_signals` (`guild_id`, `created_at`);
45+
--> statement-breakpoint
46+
CREATE TABLE IF NOT EXISTS `incident_lockdowns` (
47+
`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL,
48+
`guild_id` text NOT NULL,
49+
`channel_id` text NOT NULL,
50+
`incident_id` integer,
51+
`previous_overwrite` text NOT NULL,
52+
`locked_at` integer NOT NULL,
53+
`locked_by` text NOT NULL,
54+
`unlock_at` integer,
55+
`unlocked_at` integer,
56+
`unlocked_by` text
57+
);
58+
--> statement-breakpoint
59+
CREATE INDEX IF NOT EXISTS `incident_lockdowns_guild` ON `incident_lockdowns` (`guild_id`);
60+
--> statement-breakpoint
61+
CREATE INDEX IF NOT EXISTS `incident_lockdowns_unlock_at` ON `incident_lockdowns` (`unlock_at`);

package-lock.json

Lines changed: 36 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dreamliner",
3-
"version": "0.1.14",
3+
"version": "0.1.20",
44
"description": "Dreamliner — a Discord moderation bot with file-based configuration",
55
"type": "module",
66
"main": "dist/index.js",
@@ -34,6 +34,7 @@
3434
"ffmpeg-static": "^5.3.0",
3535
"gifenc": "^1.0.3",
3636
"google-translate-api-x": "^10.7.3",
37+
"maxmind": "^5.0.7",
3738
"opusscript": "^0.0.8",
3839
"prism-media": "^1.3.5",
3940
"yaml": "^2.7.1",

schema/guild-config.meta.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"version": 1,
3-
"generatedAt": "2026-09-13T13:41:48.616Z",
3+
"generatedAt": "2026-09-14T09:24:44.948Z",
44
"templatePath": "config/default.server.yaml",
55
"schemaPath": "schema/guild-config.schema.json",
66
"categories": [
@@ -36,6 +36,11 @@
3636
"name": "Impersonation Detection",
3737
"description": "Flags members whose name or avatar closely matches a protected role holder or watchlist entry."
3838
},
39+
{
40+
"key": "incident_response",
41+
"name": "Incident Response",
42+
"description": "Correlates Automod, Raid, Impersonation, and Scam Protect signals (plus its own server-nuke detectors) into scored Incidents, with an opt-in escalating response per severity."
43+
},
3944
{
4045
"key": "raid_mesh",
4146
"name": "Raid Defense Mesh",
@@ -312,6 +317,13 @@
312317
"category": "Protection",
313318
"categoryId": "protect"
314319
},
320+
{
321+
"key": "incident_response",
322+
"name": "Incident Response",
323+
"description": "Correlates Automod, Raid, Impersonation, and Scam Protect signals (plus its own server-nuke detectors) into scored Incidents, with an opt-in escalating response per severity.",
324+
"category": "Protection",
325+
"categoryId": "protect"
326+
},
315327
{
316328
"key": "raid_mesh",
317329
"name": "Raid Defense Mesh",

0 commit comments

Comments
 (0)