npm 12: install-скрипты теперь по согласию

8 июля 2026 года npm 12 вышел в общий доступ, и вместе с ним — самое серьёзное изменение поведения npm install за годы: скрипты жизненного цикла preinstall, install и postinstall больше не выполняются автоматически. Им нужно явное разрешение, а само разрешение записывается в package.json и проходит ревью в pull request наравне с обычным кодом.

Проблема: npm install означал «выполни их код у себя»

С первых дней реестра любой пакет мог объявить install-скрипт, выполняющий произвольные команды с правами того, кто набрал npm install. Импорт не нужен, точка входа не нужна — достаточно самой установки. Именно этот путь неоднократно использовался в течение 2026 года в серии компрометаций пакетов в реестре, включая компрометацию пакетов генератора AsyncAPI, разобранную Microsoft в июле 2026 года.

Поверхность атаки не ограничивается прямыми зависимостями. Дерево из 1 200 пакетов — это 1 200 возможностей выполнить код, и большинство из них никто не читал.

Что изменилось

Три новых значения по умолчанию в npm 12:

  • allowScripts теперь выключен. Скрипты жизненного цикла и неявная сборка node-gyp не запускаются без разрешения.
  • --allow-git по умолчанию none. Git-зависимости — прямые и транзитивные — больше не резолвятся без явного разрешения.
  • --allow-remote по умолчанию none. Зависимости по удалённым URL (например, https-тарболы) блокируются.

--allow-file и --allow-directory сохранили прежние значения.

Практический путь миграции

Разумнее выяснить радиус поражения до того, как CI станет красным. Начните с npm 11.16.0 или новее — там предупреждение появляется, но скрипты всё ещё выполняются:

bash
npm i -g npm@latest
npm approve-scripts --allow-scripts-pending   # покажет ожидающие пакеты, ничего не меняя

Затем разрешайте только то, что действительно необходимо, пакет за пакетом:

bash
npm approve-scripts sharp esbuild

Разрешение привязывается к установленной версии (pkg@1.2.3), если не передан --no-allow-scripts-pin. Явный запрет:

bash
npm deny-scripts some-package

Чтобы включить поведение v12 заранее в CI — со сбоем сборки вместо тихого пропуска скриптов:

ini
# .npmrc
strict-allow-scripts=true

После этого npm ci превращается в настоящий шлюз: любая новая зависимость, пытающаяся выполнить код при установке, останавливает пайплайн и требует ревью человеком.

Оговорки из практики

Бинарные пакеты страдают первыми. Всё, что скачивает готовый исполняемый файл или собирает нативный аддон, опирается на postinstall. Блокировка без разрешения даёт сборку, которая проходит, а падает уже в рантайме с вводящей в заблуждение ошибкой. Разрешайте такие пакеты осознанно и фиксируйте причину в описании PR.

Привязка к версии создаёт шум. Каждое обновление зависимости аннулирует привязанное разрешение, поэтому автоматические PR будут нести дополнительный шаг ревью. Это не дефект, а смысл механизма: ревью при каждом изменении кода, который будет выполнен.

Git-зависимости ломают внутренние форки. Командам, указывающим на пропатченный форк по Git-URL, теперь нужен явный --allow-git либо переход на приватный реестр.

В календаре есть вторая дата. Ужесточение на этом не заканчивается: с начала августа 2026 года гранулярные токены доступа (GAT), настроенные на обход 2FA, теряют право на чувствительные операции, а с января 2027 года — возможность публиковать напрямую. Рекомендуемая замена — trusted publishing через OIDC или поэтапная публикация с подтверждением человеком.

Вывод

npm 12 переводит выполнение кода во время установки из режима «по умолчанию и молча» в режим «явно и с ревью». Это не косметическое ужесточение — это закрытие самого широкого пути эксплуатации в экосистеме JavaScript и перенос решения туда, где ему место: в код-ревью.

Команда, которая проходит переход сегодня на npm 11.16 с strict-allow-scripts=true, входит в v12 с чистым и понятным списком разрешений. Команда, которая отложит, познакомится со списком в день остановки пайплайна.

Подробнее о волне обновлений безопасности в экосистеме — в материале о июльском релизе безопасности Next.js 2026.

Первоисточник: npm install-time security and GAT bypass2fa deprecation — GitHub Changelog