Cette page documente les protections techniques intégrées à Guardian Bot suite à l’audit de sécurité du 26 mars 2026. Elle s’adresse aux administrateurs qui souhaitent comprendre le fonctionnement interne du bot.
Vue d’Ensemble
Guardian Bot implémente une défense en profondeur (defense-in-depth) avec plusieurs couches indépendantes. Si une couche est contournée, les suivantes prennent le relais.
Message entrant
│
▼
┌─────────────────────┐
│ Liste blanche │ ◄── Domaines/IPs de confiance
└──────┬──────────────┘
│ non-whitelisté
▼
┌─────────────────────┐
│ Décompression safe │ ◄── Protection contre les Decompression Bombs
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Résolution récursive│ ◄── Résolution des redirections (max 3 sauts)
└──────┬──────────────┘
│
▼
┌─────────────────────┐
│ Scan multi-sources │ ◄── PhishTank + GSB + VirusTotal
└──────┬──────────────┘
│ menace détectée
▼
┌─────────────────────┐
│ Trust Score & Action│ ◄── Sanction adaptée au score
└─────────────────────┘
1. Anti-Phishing Récursif
Problème adressé
Les attaquants utilisent des chaînes de redirections pour masquer les URLs malveillantes. Un scanner naïf vérifie bit.ly/xyz (inoffensif) mais ne voit jamais evil-phishing.com vers lequel il redirige.
Solution implémentée
Guardian résout récursivement chaque URL en suivant les redirections HTTP jusqu’à l’URL finale.
# Résolution avec protection contre les cycles et limite de profondeur
MAX_HOPS = 3
TIMEOUT_PER_HOP = 5 # secondes
async def resolve_redirects(url: str) -> tuple[str, list[str]]:
visited = set()
chain = [url]
current = url
for _ in range(MAX_HOPS):
if current in visited:
break # Cycle détecté
visited.add(current)
response = await http_head(current, timeout=TIMEOUT_PER_HOP,
allow_redirects=False)
if response.status not in (301, 302, 303, 307, 308):
break
next_url = response.headers.get("Location")
if not next_url:
break
current = normalize_url(next_url, base=current)
chain.append(current)
return current, chain
Traitement des domaines opaques
Certains domaines de redirection ne révèlent pas l’URL de destination sans interaction utilisateur (ex: get-qr.com, captcha gates). Guardian gère ce cas spécifiquement :
- Résolution possible → scan de l’URL finale contre les bases de phishing
- Résolution impossible (domaine opaque) → avertissement généré sans blocage automatique pour éviter les faux positifs
Les faux positifs sont prioritaires à éviter. Un avertissement sans blocage sur un domaine opaque est préférable à un blocage injustifié d’une URL légitime.
Détection de cycles
Un ensemble visited est maintenu pour chaque résolution. Si une URL apparaît deux fois dans la chaîne, la résolution s’arrête immédiatement pour éviter les boucles infinies.
2. Protection contre les Decompression Bombs
Problème adressé
Une Decompression Bomb est un fichier compressé de petite taille (quelques Ko) qui se décompresse en plusieurs gigaoctets. Si un attaquant envoie une image .zip ou un fichier encodé avec du contenu malveillant, un scanner naïf pourrait tenter de décompresser le contenu en mémoire et crasher le bot.
Solution implémentée
Guardian applique des limites strictes lors du traitement des pièces jointes :
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 Mo maximum
MAX_DECOMPRESSED_SIZE = 50 * 1024 * 1024 # 50 Mo limite décompression
MAX_IMAGE_PIXELS = 4096 * 4096 # 16 MP limite de rendu
Contrôles appliqués :
-
Vérification de taille avant téléchargement : Le header
Content-Length est vérifié. Si la taille dépasse MAX_FILE_SIZE, le fichier est ignoré sans téléchargement.
-
Limite de pixels pour les images QR : Avant de passer une image au décodeur QR, les dimensions sont vérifiées. Une image de 1px × 4 milliards de pixels serait refusée.
-
Timeout de décompression : Chaque opération de décompression s’exécute dans un contexte avec timeout (
asyncio.wait_for). Si la décompression dépasse la limite de temps, l’opération est annulée.
-
Isolation des erreurs : Les exceptions de décompression sont capturées localement et logguées sans faire crasher le worker principal.
3. Protection Anti-Spam des APIs Externes
Problème adressé
Guardian consulte jusqu’à 3 APIs externes (PhishTank, Google Safe Browsing, VirusTotal) par URL scannée. Sans limitation, un attaquant pourrait envoyer des milliers de messages contenant des URLs pour épuiser les quotas API ou surcharger le bot.
Solution implémentée
Un asyncio.Semaphore limite le nombre de requêtes API simultanées :
# Antiraid + scan concurrent limité
api_semaphore = asyncio.Semaphore(5) # Max 5 requêtes parallèles
async def scan_url_with_ratelimit(url: str) -> ScanResult:
async with api_semaphore:
return await _scan_url_internal(url)
Mécanismes combinés :
| Mécanisme | Implémentation | Objectif |
|---|
| Semaphore global | Semaphore(5) | Limite les appels API simultanés |
| Cache de résultats | TTL 1h par URL | Évite de scanner la même URL deux fois |
| Trust Score probabiliste | Score × 0.3 | Réduit les scans sur membres de confiance |
| Whitelist préemptive | Vérification avant réseau | Court-circuite tous les scans |
| Timeout par requête | 5s max par hop | Empêche les attentes infinies |
Scan probabiliste
Pour éviter de scanner chaque URL envoyée par un membre de confiance, Guardian applique un échantillonnage probabiliste basé sur le Trust Score :
Probabilité de scan = max(0.1, 1.0 - (trust_score / 100) * 0.7)
Un membre avec un score de 90 n’a que 37% de probabilité d’être scanné sur chaque message. Un membre avec un score de 10 est scanné 93% du temps.
4. Protection contre les Fuites Mémoire
Problème adressé
Les sessions longues (captcha, anti-spam) accumulent des données en mémoire si elles ne sont pas nettoyées. Un attaquant peut créer des milliers de sessions captcha non terminées pour épuiser la RAM du bot.
Solution implémentée
Des tâches de nettoyage périodiques tournent en arrière-plan pour chaque cog concerné :
# Exemple dans captcha.py
@tasks.loop(minutes=10)
async def cleanup_expired_sessions(self):
now = asyncio.get_event_loop().time()
expired = [
session_id
for session_id, session in self.active_sessions.items()
if now - session.created_at > SESSION_TIMEOUT
]
for session_id in expired:
del self.active_sessions[session_id]
Cogs avec nettoyage automatique :
| Cog | Données nettoyées | Intervalle |
|---|
captcha.py | Sessions captcha expirées | 10 minutes |
automod.py | Historique de messages anti-spam | 5 minutes |
report.py | Cooldowns de signalement expirés | 30 minutes |
antiraid.py | Fenêtres de détection de raid | 1 minute |
5. Isolation de la Base de Données
Problème adressé
Les accès directs à la base de données depuis plusieurs cogs simultanés peuvent créer des conditions de course et des connexions pendantes en cas d’erreur.
Solution implémentée
Toutes les requêtes passent par un pool centralisé avec gestion de contexte :
# utils/database.py
class Database:
def __init__(self, pool):
self._pool = pool # Accès restreint via propriété
@property
def pool(self):
return self._pool
async def fetch_one(self, query: str, *args):
async with self._pool.acquire() as conn:
return await conn.fetchrow(query, *args)
Les requêtes indépendantes sont parallélisées avec asyncio.gather pour réduire la latence :
# Chargement parallèle au lieu de séquentiel
trust_score, warnings, infractions = await asyncio.gather(
db.fetch_trust_score(guild_id, user_id),
db.fetch_warnings(guild_id, user_id),
db.fetch_infractions(guild_id, user_id)
)
6. Vérification par Captcha
Algorithme
Le captcha mathématique utilise secrets.choice (CSPRNG) au lieu de random pour éviter la prédiction de réponses :
import secrets
def generate_captcha() -> tuple[str, int]:
a = secrets.choice(range(1, 20))
b = secrets.choice(range(1, 20))
op = secrets.choice(['+', '-', '×'])
# ...
return question, correct_answer
Un asyncio.Lock par session empêche les conditions de course si l’utilisateur clique plusieurs fois simultanément.
Limites
- 3 tentatives maximum par session
- Timeout de 5 minutes par session
- Expiration automatique : les sessions non terminées sont nettoyées toutes les 10 minutes
- Résultat : +15 Trust Score (succès) ou -20 Trust Score + expulsion (échec)
7. Hiérarchie et Prévention des Escalades
Guardian vérifie systématiquement la hiérarchie des rôles avant toute action modération :
Vérification avant ban/kick/mute :
1. La cible est-elle un bot ? → Refus
2. La cible est-elle le propriétaire du serveur ? → Refus
3. Le rôle le plus haut de la cible ≥ rôle le plus haut du bot ? → Refus
4. Le rôle le plus haut de la cible ≥ rôle le plus haut du modérateur ? → Refus
5. Action autorisée ✓
Détection d’abus de modérateurs
Si un modérateur effectue trop d’actions en peu de temps (seuil configurable, défaut : 3 actions/10s), Guardian :
- Logue l’événement comme suspect
- Notifie les administrateurs
- Peut restreindre les permissions du compte modérateur si le seuil est dépassé
8. Global Ban et Score de Confiance
Chaque entrée dans la liste noire globale porte un score de confiance :
| Catégorie | Score de confiance | Description |
|---|
scammer | 90% | Arnaqueur confirmé |
raider | 85% | Raideur identifié |
spammer | 75% | Spammeur documenté |
other | 60% | Catégorie générique |
Les serveurs peuvent configurer un seuil minimal de confiance en dessous duquel le ban automatique à l’arrivée n’est pas déclenché, évitant les faux positifs sur des entrées peu fiables.
Résumé des Paramètres de Sécurité
| Protection | Paramètre clé | Valeur par défaut |
|---|
| Redirections max | MAX_HOPS | 3 |
| Timeout par hop | TIMEOUT_PER_HOP | 5s |
| Taille fichier max | MAX_FILE_SIZE | 10 Mo |
| Pixels image max | MAX_IMAGE_PIXELS | 16 MP (4096²) |
| Semaphore API | api_semaphore | 5 parallèles |
| Cache scan URL | TTL | 1 heure |
| Sessions captcha max | — | 3 tentatives / 5 min |
| Nettoyage sessions | Intervalle | 10 minutes |
| Seuil spam (défaut) | messages/fenêtre | 5 msgs / 5s |
| Itérations PBKDF2 | Sauvegardes | 480 000 |