Один декодер на всех: почему Brotli — общая точка риска для Node, Bun и Python

24.10.2026 · Авторская колонка

Багрепорт на переполнение буфера обычно приходит в один проект. С Brotli всё хитрее: одна и та же ошибка декодера — уже в силу архитектуры — потенциально накрывает разом Node.js, Bun и Python-окружения. Совпадение? Нет. Три экосистемы таскают внутри один и тот же скомпилированный код. Сейчас объясню, как уязвимость в сжатии превращается в проблему целого стека — и почему переиспользование нативных библиотек способно стать точкой отказа для всех сразу.

Слой, которого не видно

Brotli — алгоритм сжатия от Google, эталонная реализация написана на C. Сидит он в вебе глубоко: браузеры жмут им HTTP-ответы, шрифты WOFF2 изнутри тоже упакованы им же, его поддерживают CDN и прокси. На типичном HTML и CSS формат ужимает плотнее gzip — за этим его и притащили все современные рантаймы. В Node.js декодер зашит в модуль zlib: исходники компилируются прямо в бинарник платформы. Bun, построенный по принципу «всё в одном», по имеющимся данным, тоже несёт декодер в едином бинарнике. А в Python-мире тот же декодер, судя по всему, приносят сторонние пакеты — их подтягивает любой HTTP-клиент со включённым сжатием.

Итог? Процессы по всей индустрии ежедневно разбирают недоверенные байты одним и тем же машинным кодом. А декодер по определению ест данные из сети — то есть потенциально враждебный ввод. Ошибка разбора потока — классическая дорога от «распаковали криво» к «записали за границей буфера», а дальше, при удачном для атакующего стечении обстоятельств, — до выполнения кода. Фантазией это не назовёшь: несколько лет назад в декодере уже закрыли переполнение буфера с потенциалом удалённого исполнения (CVE-2020-8927).

Копия — в каждом бинарнике

Дело не в том, что Brotli «плохо написан». Код популярный, покрыт фаззингом, чинится оперативно. Проблема — в модели распространения. Системная библиотека подгружается в рантайме; здесь же каждая платформа вендорит исходники: копирует их в своё дерево, подстраивает под себя, собирает и раздаёт со своими релизами. Обновление апстрима само по себе ничего не чинит.

Уязвимость опубликовали — и латать приходится не один репозиторий, а многие: у каждого рантайма своё дерево, у Python-пакетов свои, плюс nginx-модули и все, кто собрал декодер внутрь. Получился Heartbleed наоборот. Там одна общая OpenSSL требовала одного патча, но тысячи устройств его так и не получили. Здесь патч один, зато путей доставки — множество, и у каждого свой лаг. Между публикацией багрепорта и моментом, когда конкретный прод-кластер получит пересобранный рантайм, могут пройти недели. И всё это время известная уязвимость размножена по инфраструктуре — и эксплуатируема.

Сколько стоит латать всех сразу

Нативный слой — слепая зона DevSecOps. Команды знают свои JavaScript- и Python-зависимости по lock-файлам, но не видят C-код внутри бинарников. Первый ответ — SBOM: точная опись всех компонентов с версиями, включая вендоренные библиотеки. Без неё нельзя даже честно ответить на вопрос «затронуты ли мы». Второй — дисциплина: боты обновлений, регулярная пересборка контейнеров от свежих базовых образов, а не раз в год.

Есть и вопрос на будущее: почему разбор недоверенного ввода до сих пор пишут на C? По оценкам Microsoft и Google, порядка двух третей критических уязвимостей — ошибки работы с памятью. Отсюда отраслевой тренд: парсеры и декодеры переписывают на Rust, где переполнение просто не пройдёт компиляцию; существуют и memory-safe реализации Brotli. Фаззинг вроде OSS-Fuzz снижает вероятность, но не отменяет её — рано или поздно баг в C-коде найдут снова. Экономия на аудите зависимостей — это кредит, который инфраструктура возвращает каскадом панических релизов.

Рунет: nginx, Angie и всё тот же нативный слой

В России расклад тот же, с локальной спецификой. Базовые веб-серверы рунета — nginx и его российский форк Angie — работают с Brotli через сторонний модуль: ещё один вендоренный слой поверх ещё одного. Крупные экосистемы с большими Node.js- и Python-парками получают тот же патч-дедлайн, что и весь мир. Своих аналогов низкоуровневых кодеков нет и вряд ли появится: ниша слишком узкая, а ценность формата — только в совместимости.

Ответ здесь скорее организационный. Компании строят внутренние зеркала npm и PyPI с фильтрацией уязвимых версий, службы AppSec проверяют состав зависимостей до допуска в прод. Рынок ИБ — Positive Technologies, BI.ZONE, Solar, «Лаборатория Касперского» — отслеживает supply-chain-риски, публикует разборы и рекомендации. Для объектов КИИ требования к составу и обновлению ПО закреплены регулированием, а тема прозрачности поставок софта и SBOM звучит в отраслевых разговорах всё громче. Ограничение прежнее: держать собственный форк нативной библиотеки дорого. Поэтому реальная стратегия — максимально быстрая доставка апстримных патчей и автоматизация пересборки.

Вывод

История с Brotli — не про сжатие. Она про то, что наш стек сшит из лоскутов чужого нативного кода, и шов на нём один на всех. Вывод прагматичный: считать вендоренные C-библиотеки частью своей поверхности атаки — вести учёт, фаззировать, обновлять быстрее, чем это сделает атакующий. Стратегический: выводить разбор недоверенных данных из memory-опасных языков. Пока этого не произошло, любой декодер на C остаётся потенциальным общим знаменателем следующего каскадного инцидента.