nginx 1.31.5 и njs 1.0.1: Control API, JSON в конфиге и три CVE

2 сентября 2026 года на nginx.org вышли сразу два релиза: основная ветка nginx 1.31.5 и модуль JavaScript njs 1.0.1. В 1.31.5 четыре заметных возможности: Control API, location по переменной (predicate locations), встроенный разбор JSON и директива client_body_early_read. В njs закрыты три уязвимости, одна из них с оценкой major.

Стабильная ветка по-прежнему 1.30.x. Последний стабильный пакет на момент этой новости: 1.30.4 от 15 июля 2026. В неё, как обычно, попадают только серьёзные ошибки и дыры. Следующая стабильная линейка 1.32 будет собрана уже из 1.31.x.

Кому это вообще касается

Типичный WordPress за nginx с PHP-FPM эти новинки сами по себе не включают. Location по JSON-телу, Control API и раннее чтение POST нужны там, где nginx режет API, GraphQL, MCP или свой шлюз. Для обычного сайта с парой location и fastcgi_pass это скорее обзор, чем обязательный апгрейд.

Есть два исключения. Первое: если nginx проксирует HTTP/2 с буферизацией ответа, в 1.31.5 закрыт use-after-free в worker. В официальном блоге это прямо названо основной причиной обновиться, даже без новых директив. Второе: если в конфиге есть js_import и njs (особенно js_access, ngx.fetch() или XML Canonicalization), смотреть нужно на njs 1.0.1, а не только на номер nginx.

Что нового в nginx 1.31.5

Полный список в CHANGES. Разбор с примерами: запись в блоге nginx от 2 сентября 2026.

Location по переменной

Раньше location выбирался по URI: префикс, regexp, именованный блок. Теперь в определении location можно поставить переменную. Если она не пустая и не равна 0, блок считается совпавшим. Это тот же предикат, что у early_hints и access_log if=, только теперь им выбирают сам location.

Пример из разбора OpenNET (переменная из map по аргументу запроса):

map $arg_foo $pred {
    bar  1;
    qux  1;
}

location $pred {
    root html;
}

На практике это убирает пачку if в одном location. Условие можно собрать из заголовка, подсети, результата map или поля JSON, если тело уже прочитано. Скорость та же C-логика, без njs и Lua.

Раннее чтение тела: client_body_early_read

Обычный порядок: заголовки, выбор location, потом тело. Директива client_body_early_read (контекст http и server) читает тело сразу после заголовков, настройками из server. Если хотя бы один параметр не пустой и не 0, чтение начинается до выбора location.

map $http_content_type $is_json {
    application/json  1;
}

server {
    listen 8000;

    client_body_early_read $is_json;

    client_max_body_size    256;
    client_body_buffer_size 256;

    return 200 $request_body;
}

Ограничения из документации: несовместимо с модулями без буферизации тела (в том числе gRPC) и с теми, кто пишет тело в файл (DAV). client_body_in_file_only при раннем чтении игнорируется. Имеет смысл только если дальше по конфигу реально смотрят в $request_body или в поля JSON. Иначе вы просто раньше жрёте память и диск на каждый POST.

JSON в ядре: ngx_http_json_module

Отдельный модуль, без Lua и без njs. В блоге nginx приведён такой разбор поля method из тела:

json_set $request_body $json_method "method";

Вместе с client_body_early_read переменная заполняется до выбора location, и её уже можно поставить в map или в location $json_method. Это как раз сценарий «один URI /mcp или /graphql, а бэкенд разный в зависимости от метода внутри JSON».

Отдельной страницы модуля в дереве docs на момент материала ещё нет (запрос к /en/docs/http/ngx_http_json_module.html отдаёт 404). Синтаксис и смысл лучше сверять с блогом и исходниками PR #1642, а не с чужими шпаргалками под сторонний ngx_http_json_module из 2010-х.

Control API

REST-интерфейс в master-процессе: список воркеров, конфиг из памяти, перечитывание конфигурации с ответом в JSON, а не через «послал сигнал и пошёл в error.log». Возможность пришла из NGINX Plus (там она появилась в R37.0) и теперь есть в открытой сборке.

Запуск, как в официальном блоге: unix-сокет, не TCP-порт.

sudo nginx -l unix:/run/nginx-control.sock

Перечитать конфиг и сразу получить статус:

curl --unix-socket /run/nginx-control.sock -X PATCH http://localhost/1/control/config

В блоге прямо написано: API без аутентификации, это доступ к внутренностям процесса. Сокет только на файловой системе с правами для доверенных пользователей. Слушать порт в сети не стоит. В разборе OpenNET для сборки из исходников указан флаг --with-control-api. На готовом пакете это проверяется так:

nginx -V 2>&1 | tr ' ' '\n' | grep -E 'control|configure'

Баги, из-за которых mainline имеет смысл даже без новых директив

Самый неприятный: при проксировании с буферизацией и ошибке на отдаче ответа HTTP/2-клиенту worker мог отдать клиенту уже освобождённую память или упасть. По HTTP/1.1 это маскировалось, по HTTP/2 флаг ошибки ставился на fake-соединение, и write-handler продолжал слать DATA-кадры в освобождённый heap. Фикс: PR #1664. Если у вас proxy_buffering on и HTTP/2 к клиенту, это не «косметика changelog».

Ещё из CHANGES и блога:

  • worker мог не завершиться при исчерпании файловых дескрипторов, в логе появлялось accept4() failed (9: Bad file descriptor);
  • запросы к FastCGI и uwsgi ломались, если имя параметра слишком длинное (128+ байт для FastCGI, 256+ для uwsgi): бэкенд закрывал соединение, nginx отвечал 502;
  • отдельные правки HTTP/3 (CRYPTO-кадры в 1-RTT), ngx_http_slice_module и memcached.

В самом nginx 1.31.5 новых CVE нет. Дыры этого дня относятся к njs.

njs 1.0.1: три уязвимости

njs: JavaScript внутри http и stream. С 1.0.0 (23 июня 2026) встроенный движок njs помечен как deprecated, рекомендуют QuickJS (js_engine qjs). 1.0.1 от 2 сентября: security и стабильность, без смены этой линии.

По странице advisories njs:

  • CVE-2026-18329, medium. Обход контроля доступа в js_access: если асинхронное продолжение чтения тела бросало исключение или unhandled rejection, nginx мог обработать запрос так, будто проверка прошла. Уязвимы 0.9.9–1.0.0, исправлено в 1.0.1. js_access появился как раз в 0.9.9.
  • CVE-2026-78222, medium. Падение worker при чтении Response.statusText после ответа апстрима со статус-строкой без reason phrase. Уязвимы 0.5.1–1.0.0 (то есть почти все, кто пользуется ngx.fetch()).
  • CVE-2026-78689, major. Переполнение кучи при разборе списка namespace prefix в xml.exclusiveC14n(). Уязвимы 0.7.10–1.0.0.

Важная оговорка с той же страницы: если в nginx.conf нет js_import, nginx к JavaScript-уязвимостям njs не привязан. Код скриптов считается доверенным наравне с конфигом. Нет js_import, нет этих трёх CVE. Есть js_access на 0.9.9 или 1.0.0, обновление njs уже не «когда будет окно».

Кроме CVE в 1.0.1 починены reuse контекстов QuickJS, SharedDict.pop(), валидация Fetch Headers и r.headersOut, циклические ссылки Fetch/HTTP/Stream, плюс btoa()/atob() в QuickJS. Полный список: Changes with njs 1.0.1.

Что делать на рабочем сервере

Сначала версия, не «apt upgrade наугад».

nginx -v
nginx -V 2>&1 | head -n 5

Если njs загружен динамически, номер модуля смотрите в пакете или в строке configure. В образах Docker официального nginx 1.31.5 переменные сборки уже NGINX_VERSION=1.31.5 и NJS_VERSION=1.0.1.

Рабочий сайт на пакете дистрибутива (Ubuntu, Debian) обычно отстаёт от mainline nginx.org. Прыгать с 1.18/1.24 из репозитория ОС сразу на 1.31.5 без стенда не стоит: меняется не только номер, но и набор модулей, пути и дефолты ветки 1.29–1.31 (keepalive к апстриму, HTTP/1.1 по умолчанию в proxy и так далее, это копилось не в одном релизе).

Если сознательно сидите на пакетах nginx.org mainline:

sudo apt update
apt-cache policy nginx nginx-module-njs

Перед установкой: копия /etc/nginx, понимание, как откатиться. После замены бинарника:

sudo nginx -t

Только если тест проходит:

sudo systemctl reload nginx

Если в конфиге появился Control API, не выставляйте сокет в мир и не вешайте его на 80/443. Отдельный путь вроде /run/nginx-control.sock, владелец root, права 0600, доступ только с той же машины или через SSH.

Где обычно ломается

  • client_body_early_read плюс gRPC или DAV: документация прямо говорит «несовместимо».
  • Раннее чтение большого POST без лимита: client_max_body_size и client_body_buffer_size лучше задать явно, как в примере документации (там 256 байт, это учебный потолок, не норма для продакшена).
  • Control API на TCP: открытый неуполномоченный интерфейс к reload и содержимому конфига.
  • Пакет njs из дистрибутива и nginx с nginx.org в разных эпохах: ABI модуля не обязан совпасть.
  • Ожидание, что 1.31.5 «уже стабильный». Нет. Стабильный сейчас 1.30.4. 1.32 ещё впереди.

Как проверить, что вы на нужной версии

nginx -v
systemctl is-active nginx
sudo nginx -t

Для njs, если модуль в конфиге:

nginx -T 2>/dev/null | grep -E 'js_import|js_engine|load_module.*njs'

Если js_import есть, а пакет njs старше 1.0.1, для js_access и XML C14n это уже предмет обновления, а не «прочитаем changelog на выходных».

Коротко

nginx 1.31.5: mainline с нормальными новыми ручками (предикаты, JSON, раннее тело, Control API) и одним неприятным фиксом HTTP/2. njs 1.0.1: три CVE, из них major в XML. На обычном WordPress-VPS без njs и без HTTP/2-проксирования тела можно спокойно сидеть на 1.30.4. Если njs или HTTP/2 с буферизацией уже в бою, смотреть пакеты стоит сейчас, а новые директивы сначала на стенде.

Источники


Комментарии загружаются…