
Middleware Laravel passif qui détecte et journalise les injections SQL, les XSS, les RCE, les scanners de bots et plus de 175 modèles d'attaques. Doté d'un tableau de bord intégré, d'alertes Slack, d'une API REST et d'un enrichissement géographique. IDS, pas WAF.
Détection d'intrusion passive pour Laravel — voyez chaque injection SQL, XSS, scanner et sonde de bot qui frappe votre application, consignée avec son contexte complet. C'est un IDS, pas un WAF : il ne bloque, ne filtre ni ne modifie jamais une requête.
Intégrez-le dans n'importe quelle application Laravel 10–13 et il commence à analyser chaque requête HTTP à la recherche de 150+ schémas d'attaque, en attribuant un score de confiance à chaque correspondance et en l'écrivant dans votre base de données — avec un tableau de bord intégré, des alertes Slack, un enrichissement géographique et des exports fail2ban/listes noires. Aucune requête n'est jamais bloquée. Pensez caméra de sécurité, pas serrure : il vous montre exactement qui sonde vos routes, à quelle fréquence et avec quelles techniques.
Extrait d'une application en production et éprouvé sur du trafic réel. 335 tests, aucune dépendance d'exécution en dehors de Laravel lui-même, et aucune connexion internet requise pour la détection.
composer require jayanta/laravel-threat-detection php artisan vendor:publish --tag=threat-detection-migrations php artisan migrate
Ajoutez ensuite le middleware à votre groupe `web` (une ligne dans `bootstrap/app.php` sur Laravel 11+,
ou `app/Http/Kernel.php` sur Laravel 10) — extrait complet dans [Démarrage rapide](#quick-start) ci-dessous.
Et voilà ; la détection est active.```bash
php artisan threat-detection:doctor # confirms it is actually recording
Ce paquet est un IDS applicatif passif — il observe et enregistre, il ne bloque pas. Il est conçu pour se placer aux côtés d'un WAF ou d'un service edge, pas pour le remplacer. Chaque couche voit quelque chose que les autres ne voient pas :
En bref : un edge/WAF est votre verrou sur la porte ; c'est la caméra de sécurité à l'intérieur, avec le contexte applicatif pour vous dire exactement ce qui est tenté sur quelle route, par qui, et à quelle fréquence. Utilisez-le pour alimenter de vraies décisions — bans fail2ban, limites de débit, blocage géographique — avec des données que votre couche edge ne voit jamais.
La question la plus courante à propos d'un détecteur qui ne bloque jamais. Quatre réponses, par ordre croissant d'effort :
Le paquet fournit l'intelligence ; vous fournissez le refus. Cette séparation est délibérée — le code d'application des règles qui vit dans votre application est un code que vous pouvez lire, tester et désactiver, et cela signifie qu'un bug de détection ne peut jamais faire tomber votre site.
threat_logs avec l'IP, l'URL, le type de menace, le niveau de gravité et un score de confianceAucune connexion Internet n'est nécessaire pour la détection.
composer require jayanta/laravel-threat-detection
### 2. Publier les migrations et les exécuter
> **Cette étape est requise.** Sans elle, le package détectera les menaces mais ne pourra pas les stocker dans la base de données. Si vous ignorez cette étape, votre table `threat_logs` n'existera pas et toutes les détections seront silencieusement perdues (vous ne verrez que des erreurs dans `storage/logs/laravel.log`).```bash
php artisan vendor:publish --tag=threat-detection-migrations
php artisan migrate
Cela crée deux tables: threat_logs (stocke les menaces détectées) et threat_exclusion_rules (stocke les règles de faux positifs).
Vérifiez que les tables ont été créées:```bash php artisan migrate:status
Recherchez `create_threat_logs_table`, `add_confidence_to_threat_logs_table` et `create_threat_exclusion_rules_table` - toutes doivent afficher `Ran`.
### 3. Enregistrer le middleware
Le middleware est ce qui analyse les requêtes. Vous devez l'ajouter à votre groupe de middleware `web`.
**Si vous utilisez Laravel 11 ou 12** - ouvrez `bootstrap/app.php` :```php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\JayAnta\ThreatDetection\Http\Middleware\ThreatDetectionMiddleware::class,
]);
})
Comment vérifier votre version de Laravel : Exécutez
php artisan --versiondans votre terminal.
Si vous utilisez Laravel 10 - ouvrez app/Http/Kernel.php :```php
protected $middlewareGroups = [
'web' => [
// ... existing middleware
\JayAnta\ThreatDetection\Http\Middleware\ThreatDetectionMiddleware::class,
],
];
### 4. (Facultatif) Publier le fichier de configuration```bash
php artisan vendor:publish --tag=threat-detection-config
Le package fonctionne avec des valeurs par défaut raisonnables. La publication de la configuration vous permet de personnaliser les modèles de détection, les modes de sensibilité, les notifications Slack, etc. Si vous ignorez cette étape, tout fonctionne quand même.
C'est tout. Votre application détecte désormais les menaces.
Après l'installation, déclenchez une menace de test et confirmez qu'elle a été journalisée.
php artisan serve
### Étape 2 : Ouvrez une URL de test dans votre navigateur
Ajoutez un paramètre de requête malveillant à **n'importe quelle route existante** de votre application (votre page d'accueil, une page produit, etc.). Par exemple :
**Injection SQL :**```
http://localhost:8000/?q=' UNION SELECT * FROM users--
XSS (Cross-Site Scripting):``` http://localhost:8000/?q=
**Traversée de répertoire:**```
http://localhost:8000/?file=../../etc/passwd
RCE (Exécution de code à distance):``` http://localhost:8000/?cmd=system('ls -la')
**Shellshock (CVE-2014-6271):**```
http://localhost:8000/?cmd=() { :;}; /bin/bash
Injection de commandes Windows:``` http://localhost:8000/?cmd=powershell -c whoami
**DROP TABLE (SQL DDL):**```
http://localhost:8000/?q=DROP TABLE users
Utilisez une route qui existe réellement dans votre application (comme
/). Si l'URL renvoie une 404, le middleware peut ne pas avoir été exécuté.
Option A - Commande Artisan (la plus rapide):```bash php artisan threat-detection:stats
Vous devriez voir un tableau avec `Total Threats`, les décomptes de sévérité et les principales IP.
**Option B - Tinker:**```bash
php artisan tinker
DB::table('threat_logs')->latest()->take(5)->get(['ip_address', 'type', 'threat_level', 'confidence_score']);
Option C - Fichier de journalisation Laravel:
Chaque menace détectée est écrite comme un avertissement dans storage/logs/laravel.log :```
[high] Threat Detected: [middleware] SQL Injection UNION from 127.0.0.1 (http://localhost:8000/?q=...) [confidence: 50%]
### À savoir lors des tests
| Comportement | Explication |
|----------|-------------|
| Une même menace n'est journalisée qu'une fois toutes les 5 minutes | Déduplication : la même IP et le même type de menace sont mis en cache pendant 5 minutes. Utilisez **différents types d'attaque** pour chaque test, ou attendez entre les tests. |
| Les requêtes `curl` déclenchent une détection supplémentaire | L'utilisation de `curl` journalise également une détection de user-agent "cURL Command" (faible sévérité). C'est attendu : le paquet détecte les outils automatisés. |
| Le paquet ne bloque jamais les requêtes | Votre application continue de fonctionner normalement. La détection est passive. |
| Aucune configuration Slack requise | Les notifications sont désactivées par défaut. |
| Aucune connexion internet requise | La détection de base est 100 % locale. Seule la commande facultative `threat-detection:enrich` appelle une API externe pour les données géographiques. |
### Dépannage
**Commencez ici — une seule commande résout la plupart de ces problèmes :**```bash
php artisan threat-detection:doctor
Il vérifie les éléments qui font échouer la détection silencieusement — là où le tableau de bord reste vide, ce qui ressemble exactement à "aucune attaque" — et affiche la correction exacte pour chacun. Il renvoie un code de sortie non nul en cas de véritable échec, il est donc sûr de l'exécuter dans un CI ou une étape de déploiement.``` Threat Detection — health check
PASS Detection is enabled for this environment FAIL 'threat_logs' is missing confidence_label — EVERY threat is being discarded Run: php artisan vendor:publish --tag=threat-detection-migrations && php artisan migrate WARN 1 custom pattern(s) shadow a built-in: Localhost SSRF Your copy runs instead of the maintained one, so later fixes to it never reach you.
Ce qu'il couvre : la détection activée pour cet environnement ; chaque colonne dont le writer a besoin (une colonne manquante rejette **toutes** les menaces) ; les colonnes du tableau de bord / de l'API ; la table des règles d'exclusion ; que le middleware soit réellement connecté à une route ou à un groupe ; la configuration publiée qui précède cette version ; les motifs personnalisés qui masquent les motifs intégrés ; un pilote de cache incapable de faire le comptage DDoS ; et un tableau de bord ou une API laissé ouvert sans authentification.
**« J'ai testé mais `threat-detection:stats` affiche zéro menace » / « Les menaces ne sont pas stockées dans la base de données »**
Si le doctor a réussi, l'installation est correcte et le problème vient de la requête de test elle-même. Trois choses qu'il ne peut pas vérifier pour vous :
| Vérification | Comment vérifier |
|-------|---------------|
| L'IP n'est pas dans la liste blanche | Si vous avez ajouté `THREAT_DETECTION_WHITELISTED_IPS` dans `.env`, retirez-le pendant les tests |
| Utiliser une route existante | L'URL de test doit correspondre à une route réelle (par ex. `/`). Une 404 signifie que le middleware n'a jamais été exécuté |
| Cache de déduplication | Même IP + même type d'attaque est mis en cache pendant 5 minutes - essayez un type d'attaque différent |
> Exécuter `php artisan migrate` seul ne suffit jamais : les fichiers de migration
> vivent dans le package et doivent d'abord être publiés dans le répertoire `database/migrations/` de votre application. Le doctor affiche la commande exacte lorsque c'est le problème.
**« L'API renvoie 401 Unauthorized »**
Voir [Authentification API](#api-authentication) ci-dessous.
**« Le tableau de bord affiche 404 »**
Le tableau de bord est désactivé par défaut. Ajoutez `THREAT_DETECTION_DASHBOARD=true` à `.env` et videz le cache des routes :```bash
php artisan route:clear
/wp-admin, /.env, /phpmyadmin, /actuator, etc.) avec plus de 50 chemins de sonde par défautapplication/json) est inspectéLe package fonctionne sans aucune modification de .env. Toutes les valeurs ci-dessous sont facultatives - ajoutez-les uniquement si vous souhaitez remplacer les valeurs par défaut.```env
THREAT_DETECTION_ENABLED=true
THREAT_DETECTION_MODE=balanced
### Modes de détection
| Mode | Seuil de confiance | Comportement |
|------|--------------------|--------------|
| `strict` | 0 (journalise tout) | Tous les patterns actifs, seuils les plus bas. Capture tout mais peut signaler du trafic légitime. |
| `balanced` | 10 | Par défaut. Score de confiance actif, seuils standards. Bon pour la plupart des applications. |
| `relaxed` | 40 | Seuls les patterns à haute sévérité déclenchent. Idéal pour les sites à fort contenu avec de fréquents faux positifs. |
### Environnements activés
Par défaut, la détection s'exécute dans `production`, `staging` et `local`. Pour modifier, publiez la configuration et éditez :```php
'enabled_environments' => ['production', 'staging', 'local'],
Pour désactiver la détection dans votre suite de tests, définissez APP_ENV=testing (pas dans la liste ci-dessus) ou ajoutez à votre phpunit.xml :```xml
### Référence de configuration
Publiez le fichier de configuration pour voir toutes les options disponibles :```bash
php artisan vendor:publish --tag=threat-detection-config
Sections clés de configuration : skip_paths (chemins à ignorer), only_paths (mode liste blanche), auth_paths (détection intelligente des routes de connexion), content_paths (masquer les alertes non élevées), safe_fields (exclure des champs spécifiques de l’analyse), safe_paths (exclusion de champs selon le chemin pour JSON imbriqué), probe_tracking (détection des 404 de sondage), context_weights (multiplicateurs de score), threat_levels (mappage des mots-clés de sévérité), api_route_filtering (masquer les alertes faibles/moyennes sur les routes API), queue (traitement asynchrone), retention (purge automatique), max_detections_per_request (limite de performance), / (mode d’authentification).
only_paths)Si votre application possède de nombreuses routes mais que seules quelques-unes vous intéressent, utilisez only_paths pour analyser uniquement ces routes. Toutes les autres routes sont automatiquement ignorées – aucun surcoût middleware du tout.```php
// config/threat-detection.php
'only_paths' => [
'admin/',
'api/',
'login',
'register',
],
Leave empty (default) to scan all routes (subject to `skip_paths`). When both are configured, `only_paths` is checked first, then `skip_paths` applies within the matched set.
### Queue Support
By default, threat logging happens synchronously in the request cycle. For high-traffic apps, you can offload DB writes and Slack notifications to a queue:
---
Laissez vide (par défaut) pour scanner toutes les routes (sous réserve de `skip_paths`). Lorsque les deux sont configurés, `only_paths` est vérifié en premier, puis `skip_paths` s'applique à l'ensemble correspondant.
### Prise en charge des files d'attente
Par défaut, la journalisation des menaces se fait de manière synchrone dans le cycle de requête. Pour les applications à fort trafic, vous pouvez décharger les écritures en base de données et les notifications Slack vers une file d'attente :```env
THREAT_DETECTION_QUEUE=true
THREAT_DETECTION_QUEUE_CONNECTION=redis
THREAT_DETECTION_QUEUE_NAME=threat-logs
Ceci envoie une tâche StoreThreatLog (3 nouvelles tentatives, backoff 10s/30s). La détection se fait toujours en temps réel - seul l'écriture est différée.
Supprime automatiquement les anciens journaux de menaces selon un planning quotidien :```env THREAT_DETECTION_RETENTION=true THREAT_DETECTION_RETENTION_DAYS=90
Requires Laravel's scheduler to be running (`php artisan schedule:run`). Runs daily at 02:00 via `threat-detection:purge`.
### Événement ThreatDetected
Toute menace confirmée déclenche un événement `ThreatDetected` que vous pouvez écouter :```php
// app/Providers/EventServiceProvider.php
use JayAnta\ThreatDetection\Events\ThreatDetected;
protected $listen = [
ThreatDetected::class => [
YourCustomListener::class,
],
];
L'événement transporte $threatLog (tableau complet de la ligne de base de données), $ipAddress, et $threatLevel. Utilisez-le pour déclencher des actions personnalisées - envoyer des alertes Telegram, mettre à jour une liste de blocage, alimenter un SIEM, etc.
Lorsqu'un client dépasse le seuil DDoS configuré (ddos.threshold requêtes dans
ddos.window secondes), un événement DdosThresholdExceeded est déclenché en même temps que l'entrée
du journal des menaces :```php
use JayAnta\ThreatDetection\Events\DdosThresholdExceeded;
protected $listen = [ DdosThresholdExceeded::class => [ YourFloodListener::class, ], ];
L'événement contient `$ipAddress`, `$requestCount`, `$threshold` et `$windowSeconds`. Il est
limité à une fois par IP par fenêtre de déduplication (même limite que la ligne de journal), donc un flot ne peut pas
submerger vos écouteurs. Utilisez-le pour les alertes ou pour alimenter un stock de bannissement externe; pour *refuser*
les clients dépassant le seuil, utilisez `ThreatDetection::isDdosThresholdExceeded($ip)` depuis votre propre
middleware à la place — voir [Agir sur les données](#acting-on-the-data-operator-side-blocking).
---
## Notifications Slack
Les alertes Slack sont désactivées par défaut. Pour activer :```env
THREAT_DETECTION_NOTIFICATIONS=true
THREAT_DETECTION_SLACK_WEBHOOK=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
THREAT_DETECTION_SLACK_CHANNEL=#threat-alerts
Seules les menaces de gravité élevée déclenchent des notifications par défaut (configurable via notify_levels dans la configuration).
Laravel 10: Utilise la classe de notification intégrée SlackMessage. Aucun paquet supplémentaire n'est nécessaire.
Laravel 11+: Le canal Slack intégré a été supprimé. Le paquet détecte automatiquement cela et envoie des webhooks HTTP POST bruts à votre URL Slack. Aucun paquet supplémentaire n'est nécessaire. Si vous préférez le canal de notification complet, installez :```bash composer require laravel/slack-notification-channel
---
## Tableau de bord
Le package est livré avec un tableau de bord en mode sombre intégré (Alpine.js + Tailwind CDN - aucune étape de build requise).```
+-------------------------------------------------------------------------+
| Threat Detection Dashboard |
+-------------------------------------------------------------------------+
| Total: 847 | High: 23 | Med: 156 | Low: 668 | IPs: 94 |
+-------------------------------------------------------------------------+
| [Timeline Chart - 7 Day Stacked Bar] |
+-------------------------------------------------------------------------+
| Search: [___________] Level: [All] |
| Time IP Type Level Confidence Actions |
| Mar 2 14:02 185.220.101.4 SQL Injection HIGH 80% [FP] |
| Mar 2 13:58 45.33.32.156 XSS Script Tag HIGH 65% [FP] |
| Mar 2 13:45 192.168.1.10 Scanner: Nikto MED 35% [FP] |
+-------------------------------------------------------------------------+
| Top IPs | Threats by Country |
| 185.220.101.4 [23] | US 234 |
| 45.33.32.156 [18] | CN 156 |
| 103.152.220.1 [12] | RU 98 |
+-------------------------------------------------------------------------+
Ajoutez à .env:```env
THREAT_DETECTION_DASHBOARD=true
Visitez : `http://your-app.test/threat-detection`
### Accéder pendant le développement local
Le tableau de bord utilise par défaut le middleware `['web', 'auth']`, donc les utilisateurs doivent être connectés. Si votre application n'a pas encore d'authentification, restreignez-le à votre propre machine à la place :```env
THREAT_DETECTION_DASHBOARD_GUARD=ip
THREAT_DETECTION_DASHBOARD_IPS=127.0.0.1
Toutes les options de garde, et la garde séparée sur les points de terminaison qui désactivent les détections, sont couvertes dans Tableau de bord et authentification de l'API.
Si le tableau de bord affiche des données vides, la page a chargé mais ses appels API n'ont pas abouti. Voir Authentification de l'API.
Le package fournit 15 points de terminaison REST pour créer des tableaux de bord personnalisés ou des intégrations.
Les routes API utilisent le middleware auth:sanctum par défaut. Le package gère cela de manière transparente :
['api'] uniquement. L'API fonctionne sans authentification.Si vous n'utilisez pas Sanctum mais souhaitez protéger votre API, vous avez deux options :
Option 1 - Utilisez la garde d'authentification intégrée :```env THREAT_DETECTION_API_GUARD=auth
**Option 2 - Modifier le middleware directement :**```php
// config/threat-detection.php
'api' => [
'enabled' => true,
'prefix' => 'api/threat-detection',
'middleware' => ['api', 'auth'], // or 'auth:your-guard'
],
Pour les tests locaux (si Sanctum bloque l'accès), modifiez temporairement :```php 'middleware' => ['api'], // remove 'auth:sanctum'
> Rétablissez l'authentification avant le déploiement en production.
### Référence des points de terminaison
| Méthode | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/threat-detection/threats` | Liste des menaces (paginée, filtrable) |
| GET | `/api/threat-detection/threats/{id}` | Détails d'une menace individuelle |
| POST | `/api/threat-detection/threats/{id}/false-positive` | Marquer la menace comme faux positif |
| GET | `/api/threat-detection/stats` | Statistiques générales |
| GET | `/api/threat-detection/summary` | Ventilation détaillée par type, niveau, IP |
| GET | `/api/threat-detection/live-count` | Menaces de la dernière heure |
| GET | `/api/threat-detection/by-country` | Groupées par pays |
| GET | `/api/threat-detection/by-cloud-provider` | Groupées par fournisseur cloud |
| GET | `/api/threat-detection/top-ips` | Principales IP fautives |
| GET | `/api/threat-detection/timeline` | Chronologie des menaces (pour graphiques) |
| GET | `/api/threat-detection/ip-stats?ip=x.x.x.x` | Statistiques pour une IP spécifique |
| GET | `/api/threat-detection/correlation` | Analyse de corrélation |
| GET | `/api/threat-detection/export` | Exporter en CSV |
| GET | `/api/threat-detection/exclusion-rules` | Liste des règles d'exclusion |
| DELETE | `/api/threat-detection/exclusion-rules/{id}` | Supprimer une règle d'exclusion |
### Paramètres de requête pour `/threats`
| Paramètre | Description |
|-----------|-------------|
| `keyword` | Recherche dans l'IP, l'URL, le type |
| `ip` | Filtrer par adresse IP |
| `level` | Filtrer par niveau de menace (`high`, `medium`, `low`) |
| `type` | Filtrer par type de menace |
| `country` | Filtrer par code pays |
| `is_foreign` | Filtrer les IP étrangères (`true`/`false`) |
| `cloud_provider` | Filtrer par fournisseur cloud |
| `is_false_positive` | Filtrer par statut de faux positif (`true`/`false`) |
| `date_from` / `date_to` | Filtre de plage de dates |
| `per_page` | Éléments par page (défaut : 20, max : 100) |
### Exemple de réponse API
**GET `/api/threat-detection/stats`:**```json
{
"success": true,
"data": {
"total_threats": 847,
"high_severity": 23,
"medium_severity": 156,
"low_severity": 668,
"unique_ips": 94,
"foreign_ips": 67,
"cloud_attacks": 12,
"today": 34,
"last_hour": 5
}
}
Vue.js :```javascript async mounted() { const response = await fetch('/api/threat-detection/stats'); this.stats = await response.json();
const threats = await fetch('/api/threat-detection/threats?per_page=20');
this.threats = await threats.json();
}
**React:**```jsx
useEffect(() => {
fetch('/api/threat-detection/stats')
.then(res => res.json())
.then(data => setStats(data));
}, []);
Si votre API utilise
auth:sanctum, incluez les en-têtes d'authentification ou configurez l'authentification SPA Sanctum pour les requêtes basées sur les cookies.
php artisan threat-detection:doctor
php artisan threat-detection:stats
php artisan threat-detection:enrich --days=7
php artisan threat-detection:purge --days=30
php artisan threat-detection:export-fail2ban --level=high --since=24h --min-hits=5 php artisan threat-detection:export-fail2ban --format=plain > /tmp/banlist.txt
php artisan threat-detection:export-blocklist --format=nginx > /etc/nginx/blocklist.conf php artisan threat-detection:export-blocklist --format=apache > .htaccess-deny php artisan threat-detection:export-blocklist --format=csv --since=7d
---
## Agir sur les données (blocage côté opérateur)
Le paquet ne bloque jamais une requête — c'est son identité, pas un réglage par défaut. Les exports ci-dessus
alimentent les couches de mise en application que vous exécutez déjà (fail2ban, nginx, un WAF de périphérie). Mais certains déploiements
n'ont aucune couche de ce type à alimenter — hébergement mutualisé, PaaS, conteneurs derrière un équilibreur de charge que vous
ne contrôlez pas. Pour ceux-là, le paquet expose ses *décisions* sous forme de helpers, et vous écrivez
vous-même le middleware de mise en application. Même architecture que les exports : **nous fournissons
l'intelligence, vous fournissez le refus.**```php
// app/Http/Middleware/EnforceThreatDecisions.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use JayAnta\ThreatDetection\Facades\ThreatDetection;
class EnforceThreatDecisions
{
public function handle(Request $request, Closure $next)
{
$ip = (string) $request->ip();
// Static operator denylist (config: blocklisted_ips).
// CIDR supported; whitelisted_ips wins on overlap.
if (ThreatDetection::isBlocklisted($ip)) {
abort(403);
}
// Volumetric flood: refuse over-threshold clients until the window resets.
if (ThreatDetection::isDdosThresholdExceeded($ip)) {
return response('Too Many Requests', 429, [
'Retry-After' => (string) config('threat-detection.ddos.window', 60),
]);
}
return $next($request);
}
}
Avant d’appliquer une règle basée sur l’IP, configurez
TrustProxies.Tout ce qui précède dépend de
$request->ip(). Derrière un équilibreur de charge, un CDN ou proxy inverse, cet appel ne renvoie l’IP du client que lorsque Laravel sait à quels proxies faire confiance. Si ce n’est pas le cas, deux problèmes surviennent à la fois : chaque requête semble provenir du proxy, donc une entrée de liste de blocage bloque tout votre trafic ou rien du tout — et pire, si l’application fait confiance à un en-tête transféré qu’elle ne devrait pas approuver, un attaquant définitX-Forwarded-Foret traverse directement la liste de blocage.C’est encore plus important ici que pour
whitelisted_ips. Une erreur de correspondance de liste blanche signifie seulement que le package analyse une requête qu’il aurait pu ignorer : il échoue en mode sûr. Une liste de blocage utilisée pour refuser du trafic échoue en mode ouvert — vous croyez qu’une adresse est bloquée alors qu’elle ne l’est pas. Vérifiezapp/Http/Middleware/TrustProxies.php(ou l’appeltrustProxiesdansbootstrap/app.phpsous Laravel 11+) avant de vous appuyer sur l’un ou l’autre helper pour l’application des règles.
Enregistrez-le globalement (avant le middleware de détection, c’est sans problème — les helpers lisent la configuration et le cache, ils ne dépendent pas de l’ordre des middlewares) :```php // bootstrap/app.php (Laravel 11+) ->withMiddleware(function ($middleware) { $middleware->prepend(\App\Http\Middleware\EnforceThreatDecisions::class); })
The helpers:
| Helper | Retourne | Basé sur |
|---|---|---|
| `ThreatDetection::isBlocklisted($ip)` | `bool` | config `blocklisted_ips` (CIDR via `IpUtils` ; la liste blanche gagne) |
| `ThreatDetection::isWhitelisted($ip)` | `bool` | config `whitelisted_ips` |
| `ThreatDetection::ddosRequestCount($ip)` | `int` | le compteur de flood que maintient le middleware de détection |
| `ThreatDetection::isDdosThresholdExceeded($ip)` | `bool` | ce compteur vs `ddos.threshold` |
Notes:
- **La denylist est statique et maintenue par l'opérateur.** Rien dans le package ne vient jamais l'enrichir — il exécute la même décision qu'une prison fail2ban (« J'ai lu le tableau de bord ; ce /24 est hostile »), juste dans l'application.
- Le compteur DDoS ne compte que les requêtes ayant atteint la détection (`skip_paths`, les adresses IP en liste blanche et les environnements désactivés ne sont jamais comptés), et reste à 0 sur les pilotes de cache où la détection DDoS est désactivée (`file`, `database`, `null`).
- Lorsqu'un client dépasse le seuil, un événement [`DdosThresholdExceeded`](#ddosthresholdexceeded-event) est également déclenché — utile pour alerter ou alimenter une liste de bannissement externe. N'appelez toutefois pas `abort()` depuis le listener : les listeners s'exécutent dans le `try/catch` fail-open du middleware de détection, le refus relève donc de votre propre middleware comme ci-dessus.
---
## Suivi des sondes 404
Le package détecte les sondes de reconnaissance - des bots qui frappent des chemins vulnérables connus comme `/wp-admin`, `/.env`, ou `/phpmyadmin` sur votre site non-WordPress, non-phpMyAdmin. Elles ne contiennent aucune charge utile malveillante ; le chemin lui-même est le signal.
Journalisées avec une étiquette de type `[probe]`, distinctes de la détection basée sur la charge utile. Si une requête de sonde contient également une charge utile malveillante, les deux sont journalisées indépendamment.
Activé par défaut avec plus de 50 chemins de sondes. Personnalisez dans `config/threat-detection.php` :```php
'probe_tracking' => [
'enabled' => true,
'default_level' => 'medium',
'paths' => [
'/wp-admin' => 'WordPress Admin',
'/wp-admin/*' => 'WordPress Admin',
'/.env' => 'Environment File',
'/phpmyadmin' => 'phpMyAdmin',
'/actuator/*' => 'Spring Actuator',
// Add your own probe paths...
],
],
Désactiver avec THREAT_DETECTION_PROBE_TRACKING=false.
Si des champs de formulaire spécifiques contiennent légitimement du HTML, des mots-clés SQL ou du code (par exemple, les éditeurs CMS, les champs de saisie d'extraits de code), vous pouvez les exclure de l'analyse :```php // config/threat-detection.php 'safe_fields' => ['content', 'body', 'html', 'description', 'code'],
Les champs listés ici sont retirés des paramètres de requête et du corps de la requête - à la fois codés en formulaire et JSON (`application/json`) - avant que la détection ne s'exécute. Les autres champs de la même requête sont toujours entièrement analysés.
### Chemins sûrs (conscients du chemin, pour les API JSON imbriquées)
`safe_fields` correspond à un nom de clé **n'importe où** il apparaît. Pour les API JSON imbriquées, c'est souvent trop large — vous pouvez souhaiter exempter la valeur d'un champ spécifique sans exempter cette clé partout. Utilisez `safe_paths`, qui correspond par **chemin** en notation par points et prend en charge les wildcards `fnmatch` :```php
// config/threat-detection.php
'safe_paths' => ['search.query', 'filters.*.value'],
Par exemple, search.query exempte la valeur de {"search": {"query": "..."}} (une boîte de recherche dont le texte contient légitimement des mots comme SELECT), tandis qu'un champ query ailleurs dans la requête est toujours analysé. Tout ce qui n'est pas listé est analysé exactement comme avant.
Une regex seule ne peut pas exprimer toutes les contraintes : toute suite de 12 chiffres correspond au motif Aadhaar, mais un vrai numéro Aadhaar passe également la somme de contrôle de Verhoeff. Associez une étiquette de motif (par défaut ou personnalisée) à un validateur nommé, et une correspondance regex ne compte comme détection que si au moins une valeur correspondante le valide :```php // config/threat-detection.php 'pattern_validators' => [ 'Aadhaar Number Detected' => 'verhoeff', // shipped default ],
Validateurs disponibles:
| Validateur | Somme de contrôle | Usage typique |
|------------|----------|-------------|
| `verhoeff` | Verhoeff | numéros Aadhaar |
| `luhn` | Luhn | numéros de cartes de crédit/débit |
Avec le mappage fourni, les horodatages, les identifiants de commande et les codes-barres ayant 12 chiffres ne sont plus journalisés comme données personnelles — alors que les véritables numéros Aadhaar le sont toujours. Si plusieurs valeurs correspondent et qu'une seule passe la somme de contrôle, la détection se déclenche quand même : un numéro réel parmi le bruit reste une fuite.
Associez un validateur à votre propre motif pour la détection de cartes basée sur la somme de contrôle:```php
'custom_patterns' => ['/\b(?:\d[ -]?){13,19}\b/' => 'Card Number Detected'],
'pattern_validators' => ['Card Number Detected' => 'luhn'],
Un nom de validateur inconnu échoue en mode ouvert — la correspondance est comptée comme non validée et un avertissement est journalisé une fois — de sorte qu'une faute de frappe ne peut jamais désactiver silencieusement un motif de détection. Les configurations publiées avant cette fonctionnalité ne possèdent tout simplement pas la clé et conservent exactement leur comportement actuel.
Détecter des données sensibles signifiait autrefois les stocker. Un formulaire de profil contenant un numéro de mobile, un PAN et un compte bancaire déclencherait trois motifs de PII, et chacune des trois lignes écrites conservait l'intégralité du corps de la requête tel quel - conservée pendant toute la période de rétention, lisible par quiconque ayant accès au tableau de bord ou à la base de données. Une valeur dans une chaîne de requête atterrissait également dans la colonne url. Le détecteur devenait une seconde copie concentrée de ce dont il vous avertit exactement.
Activé par défaut depuis la v1.7.0. Lorsqu'un motif dont le libellé est listé se déclenche, la valeur correspondante est masquée dans la charge utile stockée et l'URL :``` BODY: {"name":"Jane Doe","mobile":"[REDACTED]","pan":"[REDACTED]","bank_account":"[REDACTED]"}
L'alerte, l'endpoint, les noms des champs et l'IP attaquante survivent tous - seule la valeur disparaît. Le masquage s'exécute *après* la détection, donc rien n'est manqué.```php
// config/threat-detection.php
'redact' => [
'enabled' => env('THREAT_DETECTION_REDACT', true),
'mask' => '[REDACTED]',
'labels' => ['Aadhaar Number Detected', 'PAN Number Detected', /* ... */],
],
Les payloads d'attaque sont délibérément laissés intacts - une chaîne d'injection est une preuve, pas un secret, et la masquer détruirait l'investigation. Seules les étiquettes que vous listez sont touchées.
Cela ne remplace pas Safe Fields. Ceux-ci empêchent un champ d'être analysé; le masquage vous permet de continuer à analyser et d'arrêter le stockage. Définissez
THREAT_DETECTION_REDACT=falsesi vous avez besoin des payloads complets pour la forensique.
Le tableau de bord et l'API prennent en charge des gardes d'authentification configurables via .env:```env
THREAT_DETECTION_DASHBOARD_GUARD=auth
THREAT_DETECTION_DASHBOARD_GUARD=role THREAT_DETECTION_DASHBOARD_ROLE=admin
THREAT_DETECTION_DASHBOARD_GUARD=ip THREAT_DETECTION_DASHBOARD_IPS=127.0.0.1,10.0.0.0/8
Les mêmes options sont disponibles pour les routes API avec `THREAT_DETECTION_API_GUARD`.
Lorsque `guard=none` (par défaut), le package enregistre un avertissement une fois par jour pour vous rappeler de configurer l'authentification.
Le guard **échoue en position fermée** : une valeur de guard non reconnue (par ex. une faute de frappe) est refusée avec une erreur 403 et un avertissement journalisé, plutôt que d'accorder silencieusement l'accès, et `guard=role` refuse (avec un avertissement) lorsque le modèle d'utilisateur authentifié ne possède pas de méthode `hasRole()`.
### Désactiver une détection nécessite plus qu'un accès en lecture
Marquer une menace comme faux positif et supprimer une règle d'exclusion désactivent tous deux un type de détection pour tout le monde, ce qui est un privilège différent de celui de lire le journal. Ces deux endpoints sont vérifiés par un guard distinct :```env
# Options: none, auth, role, ip. Default: role
THREAT_DETECTION_API_WRITE_GUARD=role
Il s'applique uniquement à ces routes, donc la lecture et le tableau de bord se comportent exactement comme le dit THREAT_DETECTION_API_GUARD. Sans cela, tout utilisateur authentifié de votre application pourrait désactiver une détection.
Si votre modèle utilisateur n'a pas de hasRole(), utilisez =auth. Pour restaurer le comportement d'avant la 1.7.0 où tout utilisateur authentifié pouvait désactiver des détections, utilisez =none - threat-detection:doctor émettra un avertissement tant que cela est défini.
Remarque Dashboard ↔ API : le tableau de bord intégré récupère ses données depuis les routes API en utilisant le cookie de session du navigateur. Si vos routes API sont protégées par
auth:sanctum, configurez l'authentification Sanctum stateful/SPA (ou orientez le tableau de bord vers un garde authentifié par cookie) afin que ces appels AJAX soient autorisés - sinon le tableau de bord se vide.
Ajoutez vos propres expressions régulières de détection dans config/threat-detection.php :```php
'custom_patterns' => [
'/your-regex-here/i' => 'Your Threat Label',
],
**Exemple - détecter une sonde d'endpoint admin personnalisée :**```php
'/\/my-admin-panel/i' => 'Custom Admin Panel Probe',
En plus de la forme chaîne classique, la valeur d'un motif peut être un tableau pour un contrôle complet :```php 'custom_patterns' => [ '/\b(?:\d[ -]?){13,19}\b/' => [ 'label' => 'Card Number Detected', // required 'level' => 'high', // low|medium|high — overrides keyword derivation 'contexts' => ['query', 'body'], // query|body|headers — default: all segments 'validator' => 'luhn', // post-match checksum, wins over pattern_validators ], ],
- **`level`** définit directement le niveau de menace au lieu de le dériver des mots-clés `threat_levels` du libellé.
- **`contexts`** limite l'analyse à des segments de requête spécifiques — par exemple, un motif de carte qui n'a de sens que dans le corps cesse de correspondre aux suites de chiffres dans les en-têtes.
- **`validator`** nomme une vérification post-correspondance en ligne (voir [Validateurs post-correspondance](#post-match-validators-checksum-aware-false-positive-reduction)) ; il prime sur la table `pattern_validators` du libellé.
Les entrées de chaînes et de tableaux se mélangent librement dans la même configuration. Les options mal formées **échouent en mode ouvert** — le motif continue d'analyser, sans restriction, et un avertissement est journalisé — de sorte qu'une erreur de configuration ne peut jamais désactiver ni restreindre silencieusement une détection.
> **Remarque :** Les chemins de sonde courants comme `/wp-login.php`, `/.env`, `/phpmyadmin` sont désormais gérés automatiquement par la fonctionnalité [404 Probe Tracking](#404-probe-tracking). Vous n'avez pas besoin de motifs personnalisés pour ceux-ci.
Le niveau de menace de chaque motif est déterminé automatiquement en confrontant les mots-clés du libellé à la configuration `threat_levels` :```php
'threat_levels' => [
'high' => ['XSS', 'SQL Injection', 'SQL DDL', 'SQL DML', 'SQL File', 'SQL Hex', 'RCE', ..., 'Shellshock', 'Spring4Shell', 'PowerShell', 'CRLF', 'Null Byte', 'SSTI', 'Java', 'LDAP', 'XPath', 'PHP assert', ...],
'medium' => ['Directory Traversal', 'LFI', 'SSRF', 'Sensitive', 'Config', ..., 'Open Redirect', 'LF Injection', 'GraphQL', 'Spring Boot Actuator', ...],
'low' => ['User-Agent', 'JS Redirect', 'SEO Bot', 'Empty', 'Rate', 'Command-line Downloader', 'DNS Rebinding'],
],
Si l'étiquette ne correspond à aucun mot-clé, la menace utilise par défaut la sévérité low.
Les motifs regex invalides sont automatiquement ignorés et journalisés comme avertissements - ils ne feront pas planter votre application.
Pour un accès programmatique aux données de menace en dehors du middleware :```php use JayAnta\ThreatDetection\Facades\ThreatDetection;
// Get attack statistics for a specific IP $stats = ThreatDetection::getIpStatistics('192.168.1.1');
// Detect coordinated attacks (multiple IPs targeting same URL within 15 minutes) $attacks = ThreatDetection::detectCoordinatedAttacks(15, 3);
// Detect attack campaigns (same threat type from 5+ IPs in last 24 hours) $campaigns = ThreatDetection::detectAttackCampaigns(24);
// Get a summary of all correlation data $summary = ThreatDetection::getCorrelationSummary();
// Operator-side decision helpers (see "Acting on the Data") $blocked = ThreatDetection::isBlocklisted('203.0.113.7'); // static denylist, CIDR, whitelist wins $trusted = ThreatDetection::isWhitelisted('10.0.0.5'); $count = ThreatDetection::ddosRequestCount('203.0.113.7'); // requests in the current DDoS window $flooded = ThreatDetection::isDdosThresholdExceeded('203.0.113.7');
---
## Passage en production
Le paquet est passif par conception : il ne bloque, ne rejette ni ne modifie jamais une requête, et le middleware de détection enveloppe l'intégralité de son corps dans `try/catch`, de sorte qu'un échec de détection ne peut jamais casser votre application. Il est livré avec des valeurs par défaut raisonnables et ne nécessite aucun service externe pour fonctionner. Avant de passer en production, cette petite liste de contrôle mérite un coup d'œil :
1. **Protégez le tableau de bord et l'API.** Les deux utilisent par défaut `guard = none` pour un premier lancement sans configuration, et enregistrent un avertissement quotidien tant qu'ils ne sont pas protégés. Avant la production, définissez un garde - `THREAT_DETECTION_DASHBOARD_GUARD` et `THREAT_DETECTION_API_GUARD` (`auth`, `role` ou `ip`). Une valeur non reconnue ou un garde `role` sur un modèle utilisateur sans `hasRole()` **échoue en mode fermé** (403), de sorte qu'une faute de frappe ne peut pas exposer silencieusement les données. La désactivation d'une détection est contrôlée séparément par `THREAT_DETECTION_API_WRITE_GUARD`, qui est défini par défaut sur `role`. Voir [Authentification du tableau de bord et de l'API](#dashboard-and-api-authentication).
2. **Exécutez les migrations** (`vendor:publish --tag=threat-detection-migrations && migrate`). La republication est sûre - les migrations déjà publiées sont ignorées.
3. **Choisissez un mode de détection.** `balanced` (par défaut) convient à la plupart des applications ; utilisez `relaxed` pour les sites à forte teneur en contenu, `strict` pour les surfaces à haute sécurité. Ajustez avec `content_paths`, `safe_fields` et `min_confidence` - voir [Réduction des faux positifs](#reducing-false-positives).
4. **Révisez les motifs régionaux de PII / motifs personnalisés.** Les valeurs par défaut sont centrées sur l'Inde (Aadhaar, PAN, IFSC) et les motifs numériques larges (par ex. compte bancaire) peuvent correspondre à de longs identifiants numériques en dehors des routes d'authentification. Remplacez ou réduisez `custom_patterns` selon votre région et votre application, et ajoutez les routes à contenu dense à `auth_paths` / `content_paths`.
5. **Activez la rétention** si vous attendez du volume : `THREAT_DETECTION_RETENTION=true` (purge automatique via le planificateur). Nécessite que le planificateur de Laravel (`schedule:run`) soit piloté par cron.
6. **Extras optionnels, tous désactivés par défaut :** alertes Slack (`THREAT_DETECTION_NOTIFICATIONS`), géo-enrichissement (`php artisan threat-detection:enrich` - la seule fonctionnalité qui effectue un appel sortant, vers le service gratuit ip-api.com), et écritures en file d'attente (`THREAT_DETECTION_QUEUE` - à activer uniquement si vous exécutez déjà un worker de file d'attente ; sinon les écritures sont synchrones et ne nécessitent pas Redis).
Aucun Redis, aucun worker de file d'attente et aucun appel réseau sortant ne sont requis pour la détection et la journalisation de base.
---
## Réduction des faux positifs
Le paquet fournit plusieurs outils pour réduire les faux positifs. Utilisez celui qui correspond à votre situation :
### Champs sûrs et chemins sûrs
Excluez entièrement un champ de l'analyse, soit par nom partout (`safe_fields`), soit par chemin en notation pointée pour le JSON imbriqué (`safe_paths`). L'approche la plus simple, et la plus radicale - le champ est ignoré, donc aucune détection ne s'y exécute.
Tous les détails et exemples : [Champs sûrs](#safe-fields-false-positive-reduction).
### Suppression par chemin de contenu
Si vous avez des éditeurs CMS, des formulaires d'articles de blog ou des sections de commentaires où les utilisateurs soumettent du contenu riche, ces chemins déclenchent souvent des faux positifs (par ex., un article de blog contenant des extraits de code `<script>`). Ajoutez ces chemins pour supprimer les alertes faibles/moyennes :```php
// config/threat-detection.php
'content_paths' => [
'admin/posts/*',
'admin/pages/*',
'blog/*/edit',
'comments',
],
Sur ces chemins, seules les menaces à haute sévérité sont journalisées.
Cliquez sur le bouton FP de n'importe quelle menace dans le tableau de bord pour la marquer comme faux positif. Cela :
is_false_positive = trueGérez les règles d'exclusion via l'API :```bash GET /api/threat-detection/exclusion-rules DELETE /api/threat-detection/exclusion-rules/{id}
### Score de confiance
Chaque menace reçoit un score de confiance (0-100) basé sur :
- Nombre de correspondances de motifs dans la même requête
- Gravité du motif correspondant
- Où le motif a été trouvé (chaîne de requête > en-têtes > corps)
- Si le user-agent correspond à un outil d'attaque connu
- Mode de détection actuel
Les menaces en dessous du seuil de confiance pour votre mode de détection ne sont pas journalisées (voir [Modes de détection](#detection-modes)).
---
## Types d'attaques détectés
| Catégorie | Exemples |
|----------|---------|
| **Injection SQL** | UNION, booléen, basé sur le temps, encodage CHAR, DDL (DROP/ALTER/CREATE), DML (INSERT/UPDATE/DELETE), opérations de fichiers (INTO OUTFILE, LOAD_FILE), énumération ORDER BY, chaînes hexadécimales, UNHEX |
| **Injection NoSQL** | opérateurs MongoDB $ne, $gt, $regex, $where |
| **XSS** | balises script, gestionnaires d'événements SVG (`<svg onload=`), gestionnaires d'événements HTML (`<body onload=`, `<img onerror=`), expressions CSS, URI JavaScript, manipulation DOM |
| **Exécution de code** | fonctions shell RCE, désérialisation PHP, désérialisation Java (base64 + octets magiques hexadécimaux), injection de modèles (Blade, JSP, ASP, Jinja2, Velocity), eval(), décodage base64, assert() PHP, create_function(), preg_replace /e |
| **SSTI** | sondes mathématiques (`{{7*7}}`), import/config Jinja2, modèles Velocity, Expression Language |
| **Injection de commandes** | Linux (fonctions shell, chaînes de commandes, curl, wget, nc), Windows (cmd.exe, PowerShell, wscript, cscript, net user) |
| **Accès aux fichiers** | traversée de répertoire, protocoles LFI/RFI, sondes de fichiers sensibles (.env, .git, composer.json) |
| **SSRF** | Localhost (127.0.0.1, 0.0.0.0, ::1), métadonnées AWS/GCP, adresses IP privées, localhost encodé en hexadécimal/décimal, rebinding DNS (xip.io, nip.io, sslip.io) |
| **Injection LDAP** | manipulation de filtres LDAP, injection OR |
| **Injection XPath** | sélecteurs d'attributs, fonctions XPath (contains, substring) |
| **Injection CRLF / en-têtes** | CRLF encodé en URL (`%0d%0a`), injection LF, injection d'octet nul |
| **Attaques de protocole** | contrebande de requêtes HTTP (CL+TE), injection SSI |
| **Exploits CVE** | Shellshock (CVE-2014-6271), Spring4Shell (CVE-2022-22965), PHPUnit RCE (CVE-2017-9841), Drupalgeddon, Log4Shell |
| **Suivi des sondes** | WordPress (`/wp-admin`, `/wp-login.php`), fichiers de configuration (`/.env`, `/.git`), outils de base de données (`/phpmyadmin`), sondes de technologie (`.asp`, `.jsp`), Spring actuator, documentation Swagger/API - 50+ chemins |
| **Scanners** | SQLMap, Nikto, Nmap, Burp Suite, FeroxBuster, FFUF, XSStrike, Dalfox, Netsparker, Qualys, Nuclei, et 20+ autres (53 au total) |
| **Scrapers IA** | GPTBot, ClaudeBot, ChatGPT, ByteSpider, Cohere, Common Crawl |
| **Navigateurs headless** | HeadlessChrome, PhantomJS, Selenium, Puppeteer, Playwright |
| **Bots** | scripts Python, clients HTTP Go, cURL, wget, AhrefsBot, SEMRushBot, user agents vides |
| **Authentification** | détection de force brute, fuites de jetons, exposition de mots de passe, exposition d'ID de session |
| **DDoS** | détection de requêtes excessives basée sur le débit |
| **Évasion** | insertion de commentaires SQL, double encodage URL, encodage d'entités HTML, échappements Unicode, Unicode IIS, échappements hexadécimaux |
| **Autre** | introspection GraphQL, pollution de prototypes, redirection ouverte, XXE, web shells, minage de crypto, détection de PII |
---
## Exécution de la suite de tests```bash
composer test
Le paquet inclut 335 tests (856 assertions) couvrant les modèles de détection, le comportement du middleware, les points de terminaison de l'API, le score de confiance, les règles d'exclusion, la détection DDoS, la résistance à l'évasion, les modèles CVE, l'injection LDAP/XPath/SSTI, la détection de bots/scanners, le suivi des sondes, les commandes d'exportation, l'authentification du tableau de bord, les champs sûrs, les optimisations des performances et la vérification complète du cycle HTTP vers base de données.
Licence MIT. Voir LICENSE pour plus de détails.
Les contributions sont les bienvenues ! Veuillez soumettre une Pull Request.
| Ce paquet (IDS applicatif) | WAF (mod_security, Cloudflare WAF) | Edge / CDN (Cloudflare) |
|---|
| Bloque les requêtes malveillantes | ❌ journaux uniquement | ✅ | ✅ |
| Contexte applicatif complet (route exacte, charge utile décodée, utilisateur authentifié) | ✅ | ⚠️ partiel | ❌ |
| Tableau de bord intégré + journal des menaces dans votre base de données | ✅ | ⚠️ variable | ⚠️ edge uniquement |
| Détections spécifiques à l'application (p. ex. Aadhaar / PAN / IFSC PII) | ✅ motifs personnalisés | ❌ | ❌ |
| Fonctionne hors ligne / aucun service externe | ✅ | ⚠️ dépend | ❌ |
| Arrête le trafic avant qu'il n'atteigne votre application | ❌ | ✅ edge | ✅ |
| Installation | une commande composer require | moyen–élevé | faible–moyen |
| Coût | gratuit, MIT | variable | offre gratuite + payant |
| Vous voulez | Utilisez | Effort |
|---|
| Voir ce qui vous frappe | Le tableau de bord ou threat-detection:stats | aucun, c'est déjà en cours |
| Bannir les récidivistes au niveau du pare-feu | threat-detection:export-fail2ban — à mettre dans une cron | une ligne |
| Refuser au niveau du serveur web | threat-detection:export-blocklist → directives nginx/apache | une ligne |
| Refuser les requêtes dans l'application | Helpers côté opérateur — isBlocklisted(), isDdosThresholdExceeded() | ~10 lignes de votre propre middleware |
| Réagir en temps réel | L'événement ThreatDetected — Telegram, SIEM, PagerDuty | un listener |
strict, balanced (par défaut) et relaxed - sensibilité réglabledashboard.guardapi.guard