Brotli CVE-2026-111497 : corriger Node.js, Bun et Python
CVE-2026-111497 est une corruption de mémoire dans le décodeur Brotli de Google, le code C qui décompresse les données Content-Encoding: br. Des données compressées forgées écrivent au-delà de la fin d'un tampon, ce qui peut faire planter le programme, divulguer de la mémoire ou exécuter le code de l'attaquant. Node.js, Bun, les paquets Python Brotli et brotlicffi ainsi qu'Envoy embarquent chacun leur propre copie de ce décodeur : chacun doit donc être mis à jour séparément. Ce guide montre quand une application est réellement exposée, comment vérifier un serveur en une commande et comment déployer le correctif sans oublier un seul conteneur.
Quelle gravité
- Google la note 9.2 (critical) en CVSS 4.0, Node.js la classe high, Red Hat lui donne 8.1.
- Les chercheurs d'Imperva ont exécuté leur propre code sur un Node.js 24.22.0 non corrigé sous Linux ARM64, à la fois via une réponse récupérée par fetch et via le corps d'une requête Express. L'exploit n'est pas public.
- L'évaluation de la CISA du 23 octobre ne relevait aucune exploitation active, et il n'existe pas de preuve de concept publique fiable.
- La faille fonctionne dans les deux sens : un serveur qui décompresse des entrées compressées peut être attaqué par un client, et un client qui décompresse des réponses compressées peut être attaqué par un serveur.
Le correctif a été intégré au code source de Brotli le 27 février, mais aucune version de Brotli ne l'a contenu avant le 21 octobre, d'où la sortie de tous les runtimes corrigés la même semaine.
Êtes-vous exposé ?
Seul le code qui décode des données Brotli contrôlées par quelqu'un d'autre est concerné :
- Node.js et Bun - le
fetch()intégré lorsqu'il lit une réponse envoyée avecContent-Encoding: br, les appels Brotli denode:zlibsur des données non fiables et les parseurs de corps qui décompressent des envois Brotli (Express 5, body-parser 2). Un simple serveur HTTP Node.js ne décode pas les corps de requête de lui-même. - Python - il suffit que le paquet Brotli ou brotlicffi soit installé : requests, urllib3, httpx et aiohttp décodent alors eux-mêmes les réponses Brotli, même si votre code n'importe jamais Brotli.
- Envoy et Istio - uniquement si un filtre de décompression Brotli est configuré.
Tout service qui appelle des API tierces, des webhooks, des prestataires de paiement ou qui scrape des pages exécute fetch() vers des serveurs que vous ne contrôlez pas. Retirer br de Accept-Encoding n'est pas un correctif : un serveur peut quand même envoyer du Brotli.
| Logiciel | Versions touchées | Corrigé en |
|---|---|---|
| Node.js | 22.x, 24.x, 26.x avant le correctif ; 20.x et antérieures (fin de vie) | 22.23.4, 24.22.1, 26.12.1 |
| Bun | 1.4.3 et antérieures | 1.4.4 |
| Python Brotli | 1.2.0 et antérieures | 1.2.1 |
| Python brotlicffi | 1.2.0.x et antérieures | 1.2.1.0 |
| Google Brotli (bibliothèque C) | 1.0.3 - 1.2.0 | 1.0.10, 1.1.1, 1.2.1 |
| Envoy | 1.39.2, 1.38.5, 1.37.7, 1.36.11 | 1.40.0, 1.39.3, 1.38.6, 1.37.8, 1.36.12 |
| Istio | 1.31.0 - 1.31.1, 1.30.0 - 1.30.5, 1.29.0 - 1.29.8 | 1.31.2, 1.30.6, 1.29.9 |
Vérifier un serveur en une commande
Ce script shell POSIX examine Bun, Node.js et Python sur la machine (ou dans un conteneur) et indique lesquels sont antérieurs au correctif. Enregistrez-le sous brotli-check.sh :
#!/bin/sh
# CVE-2026-111497 (Brotli decoder): which runtimes on this host are older than the fixes?
ver_lt() { [ "$1" = "$2" ] && return 1; [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | head -n1)" = "$1" ]; }
if command -v bun >/dev/null 2>&1; then
v=$(bun --version)
if ver_lt "$v" 1.4.4; then echo "bun $v: VULNERABLE, upgrade to 1.4.4+"; else echo "bun $v: fixed"; fi
fi
if command -v node >/dev/null 2>&1; then
v=$(node -p "process.versions.bun ? '' : process.versions.node")
if [ -z "$v" ]; then echo "node: this is Bun's node shim, see the bun line"; else major=${v%%.*}
case $major in
22) fix=22.23.4 ;; 24) fix=24.22.1 ;; 26) fix=26.12.1 ;;
*) fix= ;;
esac
if [ -z "$fix" ]; then echo "node $v: no fixed release for this line, move to 22, 24 or 26"
elif ver_lt "$v" "$fix"; then echo "node $v: VULNERABLE, upgrade to $fix+"
else echo "node $v: fixed"; fi
fi
fi
for py in python3 python; do
command -v $py >/dev/null 2>&1 || continue
$py - <<'PY'
from importlib import metadata
for pkg, fix in (("Brotli", "1.2.1"), ("brotlicffi", "1.2.1.0")):
try:
v = metadata.version(pkg)
except metadata.PackageNotFoundError:
print(f"python {pkg}: not installed")
continue
a = [int(x) for x in v.split(".") if x.isdigit()]
b = [int(x) for x in fix.split(".")]
print(f"python {pkg} {v}: {'VULNERABLE, upgrade to ' + fix + '+' if a < b else 'fixed'}")
PY
break
done
sh brotli-check.sh
docker exec my-app sh -s < brotli-check.sh # the same check inside a running container
Sortie vérifiée le 2026-10-24 (un portable de développeur, puis les images Docker officielles) :
bun 1.4.1: VULNERABLE, upgrade to 1.4.4+
node 26.6.0: VULNERABLE, upgrade to 26.12.1+
python Brotli: not installed
python brotlicffi: not installed
bun 1.4.4: fixed
node: this is Bun's node shim, see the bun line
node 22.23.4: fixed
node 16.20.2: no fixed release for this line, move to 22, 24 or 26
python Brotli 1.1.0: VULNERABLE, upgrade to 1.2.1+
python brotlicffi: not installed
Lancez-le dans chaque conteneur, pas seulement sur l'hôte : le Node.js de l'hôte ne dit rien de celui qui est intégré à une image.
Déployer le correctif
- Trouvez chaque copie. Les images sont l'oubli le plus courant. Listez ce qui tourne :
docker ps --format '{{.Image}}', puis les lignesFROMde vos Dockerfiles. Un tag commenode:22ouoven/bun:alpinen'avance que lorsque vous faites un pull ; un tag figé commeoven/bun:1.4.1-slimn'avance jamais. - Mettez à jour le runtime. Montez les tags figés (
oven/bun:1.4.4-slim,node:22.23.4-alpine), lancezbun upgradeet mettez à jour vos paquets Node.js sur les hôtes, etpip install -U "Brotli>=1.2.1"(oubrotlicffi>=1.2.1.0) dans les environnements Python. Si une application Python n'a pas besoin de Brotli,pip uninstall Brotli brotlicffiferme aussi la voie : les clients HTTP se rabattent sur gzip et deflate. - Reconstruisez et remplacez.
docker compose build --pull && docker compose up -d. Un processus en cours garde l'ancien décodeur jusqu'à son redémarrage, donc redémarrez aussi les services sur les hôtes. - Vérifiez. Relancez le script de vérification dans chaque conteneur (
docker exec <name> bun --versionen est la version rapide). Les lockfiles avec--frozen-lockfilepeuvent faire échouer la reconstruction après une mise à jour des dépendances : commitez le lockfile mis à jour avecpackage.json. - Impossible de mettre à jour tout de suite ? Rejetez au proxy les requêtes avec
Content-Encoding: bravant que quoi que ce soit ne décode le corps, et évitez de récupérer des données depuis des hôtes non fiables. Vercel rappelle que les règles de pare-feu ne couvrent pas les réponses que votre propre code récupère d'autres serveurs - seule la mise à jour le fait.
Pour une stack Node.js sur une VM, voir Serveur cloud pour Node.js, pour Python Serveur cloud pour Python et Flask, et pour des images qui se reconstruisent proprement Serveur cloud pour Docker Swarm.
Ce que nous avons fait
Dans les jours qui ont suivi les correctifs, nous avons passé en 1.4.4 chaque service DCXV qui tourne sous Bun : les images de base figées sont passées à des tags corrigés, et toutes les images ont été reconstruites avec un pull frais. Nos composants Node.js ne décodent pas de corps Brotli, et nos proxys n'ont pas de décompression Brotli. Les serveurs clients sont gérés par leurs propriétaires : si vous faites tourner Node.js, Bun ou Python sur un serveur cloud DCXV, lancez la vérification ci-dessus dès aujourd'hui, ou demandez-nous notre offre d'administration de serveur infogérée.
Sources (vérifiées le 2026-10-24) : les versions de sécurité Node.js d'octobre 2026 (22.23.4, 24.22.1, 26.12.1), la version Bun 1.4.4, l'évaluation de la CISA du 23 octobre 2026 et les recherches d'Imperva.
