Brotli CVE-2026-111497: łatka dla Node.js, Bun i Pythona
CVE-2026-111497 to błąd uszkodzenia pamięci w dekoderze Brotli od Google, czyli kodzie w C, który rozpakowuje dane Content-Encoding: br. Spreparowane skompresowane dane zapisują poza końcem bufora, co może wywrócić program, ujawnić zawartość pamięci albo uruchomić kod atakującego. Node.js, Bun, pakiety Pythona Brotli i brotlicffi oraz Envoy mają każdy własną kopię tego dekodera, więc każdy trzeba zaktualizować osobno. Ten poradnik pokazuje, kiedy aplikacja jest naprawdę narażona, jak sprawdzić serwer jednym poleceniem i jak wdrożyć poprawkę, nie pomijając żadnego kontenera.
Jak poważna jest luka
- Google ocenia ją na 9.2 (critical) w CVSS 4.0, Node.js jako high, Red Hat na 8.1.
- Badacze z Imperva uruchomili własny kod na niezałatanym Node.js 24.22.0 na Linux ARM64, zarówno przez odpowiedź pobraną przez fetch, jak i przez treść żądania w Express. Exploit nie jest publiczny.
- Ocena CISA z 23 października nie odnotowała wykorzystania luki w realnych atakach i nie ma wiarygodnego publicznego proof of concept.
- Luka działa w obie strony: serwer, który rozpakowuje skompresowane dane wejściowe, może zostać zaatakowany przez klienta, a klient, który rozpakowuje skompresowane odpowiedzi, przez serwer.
Poprawka trafiła do kodu źródłowego Brotli 27 lutego, ale żadne wydanie Brotli nie zawierało jej aż do 21 października, dlatego wszystkie załatane runtime'y pojawiły się w tym samym tygodniu.
Czy jesteś narażony?
Zagrożony jest tylko kod, który dekoduje dane Brotli kontrolowane przez kogoś innego:
- Node.js i Bun - wbudowany
fetch()czytający odpowiedź wysłaną zContent-Encoding: br, wywołania Brotli znode:zlibna niezaufanych danych oraz parsery treści żądań, które rozpakowują przesyłane pliki w Brotli (Express 5, body-parser 2). Zwykły serwer HTTP w Node.js sam nie dekoduje treści żądań. - Python - wystarczy, że zainstalowany jest pakiet Brotli lub brotlicffi: requests, urllib3, httpx i aiohttp same dekodują wtedy odpowiedzi Brotli, nawet jeśli Twój kod nigdy nie importuje Brotli.
- Envoy i Istio - tylko jeśli skonfigurowano filtr dekompresji Brotli.
Każda usługa, która wywołuje zewnętrzne API, webhooki, operatorów płatności albo scrapuje strony, wykonuje fetch() do serwerów, których nie kontrolujesz. Usunięcie br z Accept-Encoding nie jest poprawką: serwer i tak może wysłać Brotli.
| Oprogramowanie | Podatne | Poprawione w |
|---|---|---|
| Node.js | 22.x, 24.x, 26.x przed poprawką; 20.x i starsze (koniec wsparcia) | 22.23.4, 24.22.1, 26.12.1 |
| Bun | 1.4.3 i starsze | 1.4.4 |
| Python Brotli | 1.2.0 i starsze | 1.2.1 |
| Python brotlicffi | 1.2.0.x i starsze | 1.2.1.0 |
| Google Brotli (biblioteka 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 |
Sprawdź serwer jednym poleceniem
Ten skrypt powłoki POSIX sprawdza Bun, Node.js i Pythona na maszynie (lub w kontenerze) i podaje, które są starsze niż poprawka. Zapisz go jako 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
Wynik sprawdzony 2026-10-24 (laptop programisty, potem oficjalne obrazy Dockera):
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
Uruchom go w każdym kontenerze, nie tylko na hoście: Node.js na hoście nic nie mówi o tym, który jest wbudowany w obraz.
Jak wdrożyć poprawkę
- Znajdź każdą kopię. Najczęściej pomijane są obrazy. Wypisz, co działa:
docker ps --format '{{.Image}}', potem linieFROMw Twoich Dockerfile. Tag taki jaknode:22czyoven/bun:alpineprzesuwa się do przodu tylko wtedy, gdy zrobisz pull; przypięty tag, np.oven/bun:1.4.1-slim, nie przesuwa się nigdy. - Zaktualizuj runtime. Podbij przypięte tagi (
oven/bun:1.4.4-slim,node:22.23.4-alpine), na hostach wykonajbun upgradei zaktualizuj pakiety Node.js, a w środowiskach Pythonapip install -U "Brotli>=1.2.1"(lubbrotlicffi>=1.2.1.0). Jeśli aplikacja w Pythonie nie potrzebuje Brotli,pip uninstall Brotli brotlicffirównież zamyka tę drogę: klienty HTTP przechodzą wtedy na gzip i deflate. - Przebuduj i podmień.
docker compose build --pull && docker compose up -d. Działający proces trzyma stary dekoder aż do restartu, więc zrestartuj też usługi na hostach. - Zweryfikuj. Uruchom skrypt ponownie w każdym kontenerze (
docker exec <name> bun --versionto szybka wersja). Lockfile z--frozen-lockfilemogą wywrócić przebudowę po podbiciu zależności: commituj zaktualizowany lockfile razem zpackage.json. - Nie możesz jeszcze zaktualizować? Odrzucaj na proxy żądania z
Content-Encoding: br, zanim cokolwiek zdekoduje treść, i unikaj pobierania danych z niezaufanych hostów. Vercel zaznacza, że reguły firewalla nie obejmują odpowiedzi, które Twój własny kod pobiera z innych serwerów - pomaga tylko aktualizacja.
Dla stosu Node.js na VM zobacz Serwer cloud dla Node.js, dla Pythona Serwer cloud dla Pythona i Flaska, a dla obrazów, które czysto się przebudowują, Serwer cloud dla Docker Swarm.
Co zrobiliśmy
W ciągu kilku dni od wydania poprawek przenieśliśmy każdą usługę DCXV działającą na Bun na 1.4.4: przypięte obrazy bazowe dostały poprawione tagi, a wszystkie obrazy zostały przebudowane ze świeżym pull. Nasze komponenty Node.js nie dekodują treści Brotli, a nasze proxy nie mają dekompresji Brotli. Serwerami klientów zarządzają ich właściciele: jeśli uruchamiasz Node.js, Bun lub Pythona na serwerze cloud DCXV, wykonaj powyższe sprawdzenie jeszcze dziś albo zapytaj nas o zarządzaną administrację serwerem.
Źródła (sprawdzone 2026-10-24): październikowe wydania bezpieczeństwa Node.js 2026 (22.23.4, 24.22.1, 26.12.1), wydanie Bun 1.4.4, ocena CISA z 23 października 2026 i badania Imperva.
