DM Spam Protection Feature
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user