DM Spam Protection Feature

This commit is contained in:
2026-05-09 00:39:51 +02:00
parent 7c4d0300fe
commit 12b2e2b782
2 changed files with 62 additions and 12 deletions
+59 -11
View File
@@ -39,6 +39,9 @@ class Config:
self.fail_open = self._parse_bool("FAIL_OPEN", True) self.fail_open = self._parse_bool("FAIL_OPEN", True)
self.debug = self._parse_bool("DEBUG", False) self.debug = self._parse_bool("DEBUG", False)
self.bot_webhook_url = os.getenv("BOT_WEBHOOK_URL", "")
self.bot_webhook_secret = os.getenv("BOT_WEBHOOK_SECRET", "")
def validate(self): def validate(self):
return [k for k in self.REQUIRED_KEYS if not os.getenv(k)] return [k for k in self.REQUIRED_KEYS if not os.getenv(k)]
@@ -95,6 +98,7 @@ if missing:
KNOWN_EXTERNAL_USERS = {} KNOWN_EXTERNAL_USERS = {}
RATE_LIMIT = defaultdict(list) RATE_LIMIT = defaultdict(list)
METRICS = defaultdict(int) METRICS = defaultdict(int)
DM_NOTIFY = defaultdict(list)
METRICS_LOCK = Lock() METRICS_LOCK = Lock()
CACHE_LOCK = Lock() CACHE_LOCK = Lock()
@@ -146,7 +150,10 @@ def periodic_cache_save():
def extract_domain(user_id): def extract_domain(user_id):
try: try:
return user_id.split(":")[1].lower().rstrip(".") parts = user_id.split(":")
if len(parts) < 2:
return "unknown"
return parts[1].lower().rstrip(".")
except Exception: except Exception:
return "unknown" return "unknown"
@@ -180,6 +187,30 @@ def is_local_room(room_id):
def get_role(user_id): def get_role(user_id):
return "admin" if user_id in config.admin_users else "user" return "admin" if user_id in config.admin_users else "user"
def notify_bot(event_type, sender, room_id):
if not config.bot_webhook_url:
return
try:
headers = {}
if config.bot_webhook_secret:
headers["Authorization"] = f"Bearer {config.bot_webhook_secret}"
requests.post(
config.bot_webhook_url,
json={
"type": event_type,
"sender": sender,
"room_id": room_id,
"timestamp": time.time()
},
headers=headers,
timeout=2
)
except Exception as e:
logger.error(f"Failed to notify bot: {e}")
# ============================================================ # ============================================================
# RATE LIMIT # RATE LIMIT
@@ -430,6 +461,11 @@ def invite(room_id, event_id):
# 🔒 Rate Limit # 🔒 Rate Limit
if is_rate_limited(domain, sender): if is_rate_limited(domain, sender):
log_event(
"rate_limited",
actor=sender,
domain=domain
)
return Response(status=429) return Response(status=429)
# 🟢 Whitelist # 🟢 Whitelist
@@ -466,17 +502,29 @@ def invite(room_id, event_id):
payload payload
) )
else: key = f"{sender}:{room_id}"
with METRICS_LOCK: last_list = RATE_LIMIT.get(f"dm_notify:{key}", [])
METRICS["invite_blocked"] += 1 last = last_list[-1] if last_list else 0
log_event( # 🔥 deduplicated notify
"invite_blocked", if time.time() - last > 5:
actor=sender, notify_bot("dm_spam", sender, room_id)
domain=domain, DM_NOTIFY[key].append(time.time())
reason="unknown_external_user"
) # 🔥 heavy detection unabhängig davon
return Response(status=403) if is_rate_limited(domain, sender):
notify_bot("dm_spam_heavy", sender, room_id)
with METRICS_LOCK:
METRICS["dm_detected"] += 1
log_event(
"dm_detected",
actor=sender,
domain=domain,
room_id=room_id
)
# 🟢 DEFAULT (alles andere erlauben) # 🟢 DEFAULT (alles andere erlauben)
remember_user(sender) remember_user(sender)
+3 -1
View File
@@ -2,6 +2,8 @@ TUWUNEL_URL=http://tuwunel:6167
LOCAL_DOMAIN=ztfr.eu LOCAL_DOMAIN=ztfr.eu
ADMIN_TOKEN=4h1bYSgYxfrotpjoXEzLO8LFyXKudqUA ADMIN_TOKEN=4h1bYSgYxfrotpjoXEzLO8LFyXKudqUA
BOT_WEBHOOK_URL=http://maubot:29316/_matrix/maubot/plugin/acb/webhook/dm_detected
BOT_WEBHOOK_SECRET=supersecret123
DOMAIN_WHITELIST=techniverse.net,daddelwerk.net DOMAIN_WHITELIST=techniverse.net,daddelwerk.net
@@ -9,4 +11,4 @@ BLOCK_EXTERNAL_DMS=true
ALLOW_ROOM_CREATION=false ALLOW_ROOM_CREATION=false
CACHE_TTL_SECONDS=604800 CACHE_TTL_SECONDS=604800
DEBUG=true DEBUG=true