---
title: "Postmortem — CrowdSec AppSec : faux positif heuristique sur Sonarr/Radarr"
description: "Sonarr et Radarr inaccessibles (403) après une session expirée — CrowdSec AppSec heuristique a détecté une rafale de requêtes échouées comme du http-probing sur l'IP maison."
url: "https://www.arleo.eu/posts/postmortem-crowdsec-appsec-false-positive-sonarr/"
language: "fr"
datePublished: "2026-05-25T20:51:17+02:00"
dateModified: "2026-05-25T21:03:14+02:00"
tags: ["crowdsec","appsec","openresty","Debug","postmortem","nginx","sonarr"]
categories: ["Debug","Post-mortems"]
contentSignal: "ai-train=no, search=yes, ai-input=yes"
---

# Postmortem — CrowdSec AppSec : faux positif heuristique sur Sonarr/Radarr
Sonarr et Radarr inaccessibles (403) après une session expirée — CrowdSec AppSec heuristique a détecté une rafale de requêtes échouées comme du http-probing sur l'IP maison.



## Résumé

Le **25 mai 2026 vers 22h02 (heure locale)**, Sonarr et Radarr sont devenus totalement inaccessibles depuis l'IP maison (`82.XX.XX.XX`), retournant **403** sur toutes les URLs y compris `/login`. Le service était pourtant opérationnel. Le diagnostic initial suspectait les récents déploiements du refactor `crowdsec-cf-sync` — la cause réelle est un **faux positif heuristique CrowdSec AppSec**.

---

## Timeline

| Heure (locale) | Événement |
|---|---|
| ~22h00 | Cookie de session Sonarr expiré côté navigateur |
| 22h02:31 | Le navigateur charge la bibliothèque Sonarr → tente de charger 20+ `/MediaCover/*.jpg` simultanément |
| 22h02:31 | Sonarr retourne 302 → `/login` pour chaque image (session invalide) |
| 22h02:34 | La connexion SignalR WebSocket s'établit (101) via `access_token` dans l'URL |
| ~22h05 | CrowdSec AppSec déclenche la règle heuristique `http-probing` : rafale de requêtes échouées depuis la même IP |
| 22h11:37 | Toutes les requêtes de `82.XX.XX.XX` retournent 403 — `cs_reason=heuristic` dans les logs nginx |
| 22h13:34 | Même `/login` bloqué — l'IP ne peut plus s'authentifier |

---

## Cause racine

CrowdSec AppSec maintient un **état heuristique en mémoire**, distinct des décisions LAPI. Lorsque le navigateur tente de charger simultanément de nombreuses ressources et reçoit des 302/403 de l'application upstream (Sonarr), AppSec interprète cette rafale d'échecs comme du sondage agressif (`http-probing`) et bloque l'IP source.

Le problème : l'IP maison est dans l'allowlist LAPI (`my_allowlist`) et dans le bloc `geo $lan` nginx, mais **l'heuristique AppSec ne consulte pas l'allowlist LAPI** — elle opère indépendamment sur des signaux comportementaux.

```
cscli decisions list --ip 82.XX.XX.XX
→ No active decisions   ← l'allowlist LAPI est respectée

nginx access log :
82.XX.XX.XX ... "GET /" 403 ... cs_reason=heuristic   ← AppSec en mémoire bloque
```

---

## Ce qui n'était PAS la cause

Plusieurs pistes ont été écartées dans l'ordre :

| Piste | Résultat |
|---|---|
| Refactor `crowdsec-cf-sync` (redéploiement du jour) | ❌ Ne touche pas nginx, AppSec, ni Sonarr |
| Ban LAPI CrowdSec (`cscli decisions list`) | ❌ Aucune décision active pour l'IP |
| `bans.json` Lua IPC | ❌ 3 IPs tierces uniquement, pas l'IP maison |
| Bloc nginx `geo $lan` | ❌ IP présente dans le bloc (`$lan = 1`) |
| API key Sonarr invalide | ❌ `/api/v3/system/status` retourne 200 avec la clé |
| Problème réseau / VM Sonarr | ❌ VM répond (302 attendu pour session expirée) |

---

## Impact

- **Durée** : ~15–20 minutes (détection manuelle)
- **Périmètre** : Sonarr et Radarr sur `vm-media` uniquement
- **Utilisateurs** : 1 (IP maison)
- **Données** : aucune perte

---

## Fix immédiat

Redémarrage de CrowdSec pour vider l'état heuristique en mémoire :

```bash
sudo systemctl restart crowdsec
```

---

## Fix permanent

Ajout d'un bypass Lua dans `/etc/openresty/snippets/crowdsec_access.conf` pour exempter les IPs LAN de tout check AppSec avant même la vérification LAPI :

```nginx
access_by_lua_block {
    if ngx.var.lan == "1" then return end
    require("crowdsec.access").check()
}
```

La variable `$lan` est définie par le bloc `geo` dans `conf.d/lan.conf` et couvre les plages RFC 1918 (`192.168.0.0/16`, `10.0.0.0/8`, `172.16.0.0/12`) ainsi que l'IP maison fixe.

Les IPs externes continuent à passer par le check complet (LAPI + AppSec).

---

## Leçons

1. **L'allowlist LAPI ne protège pas contre l'heuristique AppSec.** Les deux systèmes sont indépendants — une IP peut être dans `my_allowlist` et quand même être bloquée en mémoire par AppSec.

2. **Les rafales légitimes déclenchent les règles heuristiques.** Sonarr et Radarr chargent des dizaines d'images couverture au premier affichage — pattern indiscernable d'un scan d'assets.

3. **`cs_nginx_ban: false` ne signifie pas "non bloqué".** Ce champ reflète uniquement le bouncer LAPI. Un bloc AppSec heuristique retourne 403 avec `cs_reason=heuristic` dans le log nginx — champ absent du pipeline Vector/BetterStack.

4. **Le diagnostic via BetterStack a ses limites.** La source `nginx_status` n'inclut pas `cs_reason` — la cause réelle n'était visible que dans les logs nginx bruts (`/var/log/nginx/vm-media.access.log`).


## Tags

- crowdsec
- appsec
- openresty
- Debug
- postmortem
- nginx
- sonarr

## Categories

- Debug
- Post-mortems
