Next.js 3 листопада випустив патчі v16.4.5 (Active LTS) і v15.5.29 (Maintenance LTS), що закривають сім вразливостей – одну High severity і шість Medium. Усі вони так чи інакше пов’язані з Cache Components, Edge Route Handlers і Image Optimization. Паралельно Node.js анонсував окремий security release на 11 листопада для ліній 26.x, 24.x і 22.x, де найвища severity теж позначена як High – але конкретних CVE поки не розкрили.
Про сам факт анонсу Next.js-релізу ми вже писали в попередньому матеріалі, коли деталі ще не були опубліковані. Тепер вони є, і варто розібрати, що саме змінилось і кого це реально стосується.
Чому це не просто "ще один патч у changelog"
Якщо твій застосунок на Next.js використовує Cache Components, use cache або unstable_cache – ти в зоні ризику за замовчуванням, просто тому що ці механізми активно просуваються як рекомендований підхід до кешування (про це ми писали в розборі Cache Components як дефолту і в ментальній моделі кешу Next.js). Найсерйозніша з вразливостей цього релізу – якраз про те, що один орендар (tenant) у мультитенантному застосунку міг отримати закешовані дані іншого орендаря.
Для продуктів, де tenant isolation – це частина контракту з клієнтом (SaaS, B2B-платформи, white-label рішення), це не теоретична дірка, а пряме порушення обіцянки "дані одного клієнта ніколи не бачить інший". Навіть якщо в тебе немає explicit multi-tenancy, подібні патерни трапляються там, де кеш розрізняє контент за локаллю, сайтом чи акаунтом користувача.
Що саме полагодили
Головна вразливість (High) стосується ситуації, коли та сама публічна use cache функція викликається і напряму, і обгорнута в unstable_cache. Якщо функція читає root-параметр на кшталт tenant ID, під час холодного заповнення кешу конкурентний виклик через unstable_cache може не перевірити root-параметр і отримати результат чужого запиту. Цей результат потім зберігається і віддається наступним викликам з тими самими аргументами, поки кеш не оновиться чи не витече.
Шість medium-вразливостей охоплюють суміжні сценарії:
- Вкладені
use cacheфункції можуть передати контент одного root-параметра (tenant, site, locale) запитам для іншого, якщо зовнішня функція завершується раніше, ніж внутрішня встигає записати залежності від root-параметра. - Бінарні аргументи в
use cacheможуть колізувати за cache identity: різні за змістом binary-аргументи дають однаковий ключ кешу, і якщо вони використовуються для розрізнення користувачів – один користувач може отримати чужий кешований результат. - Shared shell poisoning у self-hosted застосунках з
next start, увімкненими Cache Components і вимкненим Partial Prefetching: неавтентифікований запит з підготовленим URL може зіпсувати спільну page shell для інших URL, спричиняючи неправильний рендер або постійні 404. - Response injection через stale compressed framing – якщо застосунок пересилає attacker-controlled fetched Response без змін, за HTTP/1.1 реверс-проксі з pipelined-запитами на спільному backend-з’єднанні можна підмінити, кому дістанеться яка відповідь.
- Витік cookie через внутрішній заголовок
x-middleware-set-cookieв App Router Edge Route Handlers і Pages Router Edge API routes: той самий origin JavaScript може прочитати значення cookie, навіть якщо вона позначена HttpOnly. Node.js Route Handlers іcookies().set()цю проблему не зачіпають. - SVG rejection bypass в Image Optimization – зображення з дозволеного джерела можна помилково класифікувати як AVIF, і бекенд оптимізації в підсумку декодує оригінальні байти як SVG, обходячи
images.dangerouslyAllowSVG: false.
Як це виглядає в коді
Джерело прямо описує проблемний патерн: та сама use cache-функція викликається і напряму, і через unstable_cache. Умовно це виглядає так:
// lib/tenant-settings.ts
import { unstable_cache } from "next/cache";
async function getTenantSettings(tenantId: string) {
"use cache";
// функція читає tenantId як root-параметр кешу
return fetchSettingsFromDb(tenantId);
}
// Виклик напряму, десь у Server Component:
const settings = await getTenantSettings(tenantId);
// Той самий getTenantSettings, обгорнутий в unstable_cache десь-інде:
const cachedSettings = await unstable_cache(
getTenantSettings,
["tenant-settings"]
)(tenantId);Саме комбінація "та сама функція викликається двома шляхами" і створює вікно для гонки під час cold cache fill. Практичний крок після оновлення версій – пройтися кодовою базою і перевірити: чи є у тебе use cache функції, які одночасно викликаються напряму і обгортаються в unstable_cache. Якщо так – варто або прибрати дублювання викликів, або явно розділити ці шляхи так, щоб root-параметр завжди валідувався перед поверненням результату.
Оновлення залежностей виглядає стандартно:
npm install next@15.5.29 # для гілки 15.5
npm install next@16.4.5 # для гілки 16.5Для перевірки конкретних вразливостей зручно шукати застосунок за ознаками: чи увімкнені Cache Components, чи є Edge Route Handlers, що ставлять cookie через NextResponse.cookies.set(), чи дозволено images.dangerouslyAllowSVG.
Чесні обмеження патчу
Не всі описані issues зачіпають кожен застосунок однаково. SVG-баг вимагає, щоб бекенд оптимізації зображень взагалі вмів декодувати SVG – це not universal. Response injection через stale framing спрацьовує тільки за конкретної поведінки HTTP/1.1 реверс-проксі з pipelined-запитами на спільному з’єднанні – це не стосується кожної конфігурації проксі. Shared shell poisoning вимагає специфічної форми route: prerenderable динамічний параметр без build-time значення, щонайменше два глибші параметри, які ніколи не prerender-яться, і blocking fallback shell з читанням параметра всередині Suspense boundary. Якщо твій застосунок не підпадає під ці умови – ризик нижчий, але сам факт патчингу не завадить.
Trade-off тут простий: оновлення мінорної версії виглядає дешево, але варто прогнати регресійні тести саме на multi-tenant і auth-сценаріях, бо зміни в поведінці кешу можуть вплинути на те, що саме повертається з use cache і unstable_cache навіть у коректних викликах.
Коли патчити негайно, а коли можна в наступному спринті
Патч потрібен прямо зараз, якщо:
- у застосунку увімкнені Cache Components і є multi-tenant, multi-site чи multi-locale логіка;
- є Edge Route Handlers або Pages Router Edge API routes, що ставлять cookie через
NextResponse.cookies.set(); - застосунок self-hosted через
next startз Cache Components і без Partial Prefetching; - Image Optimization працює з користувацькими джерелами зображень і native image backend.
Якщо нічого з переліченого не про твій проєкт – оновлення все одно варте того, щоб потрапити в найближчий реліз, просто без режиму "зупинити все й патчити зараз".
Щодо Node.js: деталі з’являться ближче до 11 листопада або одразу після релізу, зараз відомо лише, що найвища severity позначена як High для ліній 26.x, 24.x і 22.x. Якщо хочеш отримати сповіщення одразу, як з’являться подробиці – підпишись на nodejs-sec mailing list, посилання є в офіційному блозі Node.js.
Фінальний практичний крок простий: онови Next.js до патченої версії цього тижня, окремо запланюй рев’ю коду на предмет подвійних викликів use cache-функцій (напряму і через unstable_cache), і постав собі нагадування перевірити деталі Node.js-релізу після 11 листопада – там може знайтися щось, що вплине на твій deployment pipeline ще до кінця місяця.