Audit complet du dépôt suite à un signalement de résultats vides
intermittents qui se résolvaient tout seuls après plusieurs minutes.
Deux causes racines identifiées et corrigées :
1. run_search() attendait TOUS les trackers (asyncio.gather sans budget)
avant de renvoyer quoi que ce soit. Chaque tracker a son propre timeout
(10-20s), mais un seul tracker capricieux qui va au bout du sien retarde
toute la réponse — combiné à la récupération des métadonnées (jusqu'à
20s) et la vérification de disponibilité débrideur (jusqu'à 20s), la
latence totale pouvait dépasser ce que Stremio tolère côté client,
donnant un "0 résultat" trompeur qui se résout dès que le tracker
redevient rapide. Ajout d'un budget global de 15s (asyncio.wait) : les
trackers pas encore revenus sont abandonnés pour cette requête sans
bloquer les autres, même principe que le budget déjà en place sur la
vérification débrideur.
2. Un résultat de recherche VIDE (tracker qui répond 200 avec 0 item lors
d'un accroc passager, pas une exception) était mis en cache pour le
TTL_SEARCH complet (30 min par défaut) — verrouillant "aucun résultat"
pour TOUT LE MONDE sur cette instance partagée jusqu'à expiration, même
une fois le tracker redevenu normal. Nouveau TTL_SEARCH_EMPTY (3 min par
défaut, RANGER_TTL_SEARCH_EMPTY) appliqué uniquement aux résultats vides.
Durcissement additionnel (trouvé en auditant les autres appels réseau) :
- Aucune instance aiohttp.ClientSession n'avait de timeout par défaut au
niveau session ; plusieurs appels dans alldebrid.py/torbox.py/
stremthru.py/realdebrid.py/debridlink.py (chemin /resolve, au clic sur
lecture) n'avaient pas de timeout par appel non plus, héritant du défaut
aiohttp de 300s. Ajout d'un timeout de session par défaut (20s) partout,
qui n'écrase pas les timeouts par-appel déjà présents.
- Suppression de services/tmdb.py (code mort, jamais importé — remplacé
depuis le début par core/metadata.py).
Le ping /health toutes les 30s dans les logs est le HEALTHCHECK Docker
normal (urllib.request, cf. Dockerfile), pas un souci.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Suite à un audit externe du dépôt, correction des vulnérabilités confirmées
après vérification manuelle du code :
P0 (critiques) :
- RealDebrid : suppression d'un serveur StremThru tiers codé en dur
(stremthru.13377001.xyz) auquel la clé API RD était envoyée à l'insu de
l'utilisateur dès lors qu'aucune StremThru propre n'était configurée.
Remplacé par un vrai client API Real-Debrid officiel (api.real-debrid.com
exclusivement).
- XSS réfléchie sur /configure : la config décodée était injectée brute
(json.dumps) dans un <script> inline, une valeur contenant "</script>"
cassait hors du tag. Échappement des caractères dangereux en séquences
unicode avant injection.
- XSS stockée dans le dashboard admin : un ID Stremio non validé entrait
dans les clés de cache puis était rendu via innerHTML sans échappement
(dont un point d'injection via onclick jamais échappé du tout). Validation
stricte de l'IMDB ID côté serveur (^tt\d{5,9}$) + reconstruction du
dashboard sans innerHTML/onclick interpolés (DOM API + addEventListener).
- Fuite de cache inter-comptes : la clé de cache recherche et la clé de
cache des liens résolus ne dépendaient pas des identifiants utilisés,
seulement de l'IMDB/hash. Deux profils différents (ex: plusieurs membres
d'une famille) partageant la même instance pouvaient recevoir les
résultats ou liens streaming de l'autre. Namespacing par empreinte des
credentials (core.cache.fingerprint) pour les trackers/débrideurs à clé ;
cache resté partagé pour les trackers publics (pas de credential, pas de
fuite).
- SSRF via Torznab/UNIT3D : les URLs d'indexeurs viennent de la config
utilisateur, or Ranger est exposé publiquement (config auto-suffisante,
pas besoin de connaître de clé). Ajout de core/netsafety.py qui résout et
bloque les cibles loopback/link-local/réservées (dont le 169.254.169.254
des métadonnées cloud) avant chaque requête ; RFC1918 reste autorisé
(usage prévu : Jackett/Prowlarr auto-hébergé sur le LAN de l'opérateur).
Corrections additionnelles :
- Token admin : comparaison à temps constant (hmac.compare_digest) et
suppression du fallback ?token= en query string (loggable par les CDN/proxy).
- Conteneur Docker : utilisateur non-root (UID/GID 1000 fixes).
- RANGER_PUBLIC_URL (optionnel) : URL publique fixe prioritaire sur les
en-têtes X-Forwarded-Proto/Host, potentiellement usurpables si le port du
conteneur est aussi joignable directement.
- Détection anime : le genre Animation (16) est désormais requis dans tous
les cas — un "or orig_lang == 'ja'" classait n'importe quel drama
live-action japonais comme anime.
- Numérotation absolue transmise jusqu'à /resolve (elle ne servait qu'au
filtrage) : la sélection du bon fichier dans un pack anime fansub peut
désormais matcher l'épisode absolu en plus de SxxExx.
- Budget de 20s sur la vérification de disponibilité multi-débrideurs : un
backend lent/hors service ne bloque plus toute la réponse /stream.
- UNIT3D : suppression de download_torrent (code mort, jamais appelé) et
réduction du logging de réponse brute (risque de fuite de tokens signés).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Score qualité global (résolution/source/HDR/codec/langue/seeders) +
critère de tri "quality" (⭐), sélectionnable et par défaut en 2e position.
Tri par défaut élargi à tous les critères (dont priorité tracker).
- Cache court des liens débrideur résolus (TTL 15 min, clé service+hash+SE+ip)
-> seek instantané, ~5x moins d'appels débrideur/StremThru. Table dédiée,
visible et purgeable dans l'admin.
- Mode d'affichage : "detailed" (actuel) ou "simple" (famille) = 1 lien par
résolution, meilleur choix auto (cache puis score qualité).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Protégé par RANGER_ADMIN_TOKEN (panel désactivé si non défini)
- Dashboard : uptime, taille DB, requêtes/streams servis, résolutions,
hit-rate cache (recherche + dispo), répartition par débrideur/source
- Explorateur de cache : recherches + métadonnées, suppression unitaire
- Force-refresh par IMDB ID (purge recherches+méta -> re-fetch Stremio frais)
- Maintenance : cleanup expirés, flush sélectif ou total
- Métriques hit/miss instrumentées dans le cache
Fix: web.Response étant falsy, `if err:` ne bloquait pas l'auth admin
-> remplacé par `if err is not None:`.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>