GitHub готовит к выходу npm версии 12, и у многих разработчиков уже начались вопросы после первого знакомства с changelog. Самое заметное изменение касается жизненного цикла установки пакетов: скрипты preinstall, install и postinstall больше не будут запускаться автоматически для зависимостей. По умолчанию они заблокированы, и чтобы разрешить их выполнение, нужно явно это подтвердить.
Причина такого решения прямая. GitHub называет установочные скрипты «крупнейшей поверхностью для выполнения произвольного кода во всей экосистеме npm». Когда вы запускаете npm install, выполняются скрипты не только из ваших прямых зависимостей, но и из всех транзитивных: зависимостей зависимостей, которые могут уходить на несколько уровней вглубь. Один скомпрометированный пакет где-то в этом дереве способен запустить произвольный код прямо на машине разработчика или на CI-сервере. Именно это и называют атаками на цепочку поставок программного обеспечения, и npm 12 целенаправленно сужает эту точку уязвимости.
Модель безопасности меняется с «доверять по умолчанию» на «явное одобрение». Раньше пакет мог делать почти что угодно в момент установки, если только разработчик специально не передавал флаг --ignore-scripts. Теперь логика перевёрнута: ничего не выполняется без вашего ведома.
Помимо скриптов жизненного цикла, npm 12 вводит аналогичные ограничения для нескольких других категорий зависимостей. Git-зависимости теперь заблокированы по умолчанию, и флаг --allow-git по умолчанию выставлен в none. Зависимости, подключаемые через удалённые URL (например, HTTPS-архивы), тоже блокируются без явного --allow-remote. Нативные сборки через node-gyp для пакетов с файлом binding.gyp раньше запускались автоматически даже без явного скрипта установки; теперь это тоже требует разрешения. Скрипты prepare для Git-, файловых и символических зависимостей обрабатываются по той же логике.
Отдельного внимания заслуживает закрытая брешь с --ignore-scripts. До npm 12 этот флаг считался достаточной защитой: он запрещал выполнение скриптов из package.json. Но через Git-зависимость можно было подсунуть файл .npmrc, который переопределял сам исполняемый файл git. Код выполнялся, даже когда --ignore-scripts был активен. Перевод --allow-git в none по умолчанию именно это и закрывает.
Для тех, кто собирается мигрировать на npm 12, GitHub предлагает конкретную последовательность действий. Сначала нужно обновиться до npm 11.16.0 или выше: именно с этой версии появились предупреждения о скриптах, которые будут заблокированы в следующей мажорной версии. Запустите обычный npm install и посмотрите на предупреждения в выводе: они покажут, какие пакеты используют скрипты. Затем запустите:
npm approve-scripts --allow-scripts-pending
Эта команда выведет список пакетов со скриптами, ожидающими одобрения. Вы проверяете каждый, разрешаете те, которым доверяете, и коммитите обновлённый package.json. После перехода на npm 12 одобренные скрипты продолжат работать, а всё, что не получило явного разрешения, запускаться перестанет.
Раньше в этом году GitHub уже добавил в npm похожую по духу функцию min-release-age: настройку, позволяющую отклонять любые версии пакетов, опубликованные менее чем за указанное количество дней до установки. Смысл тот же: дать разработчику время на обнаружение вредоносного пакета до того, как он попадёт в продакшн. npm 12 строится на той же логике временного карантина и явного контроля.
Практически это означает, что проекты с нативными зависимостями (типичный случай: пакеты вроде bcrypt или canvas, использующие node-gyp и binding.gyp) потребуют ручной проверки перед миграцией. CI-пайплайны, где npm install запускается автоматически, тоже нужно будет привести в порядок заранее. Если этого не сделать, сборки просто сломаются после обновления. Выход npm 12 запланирован на следующий месяц, так что время на подготовку ещё есть, но его уже немного.
Причина такого решения прямая. GitHub называет установочные скрипты «крупнейшей поверхностью для выполнения произвольного кода во всей экосистеме npm». Когда вы запускаете npm install, выполняются скрипты не только из ваших прямых зависимостей, но и из всех транзитивных: зависимостей зависимостей, которые могут уходить на несколько уровней вглубь. Один скомпрометированный пакет где-то в этом дереве способен запустить произвольный код прямо на машине разработчика или на CI-сервере. Именно это и называют атаками на цепочку поставок программного обеспечения, и npm 12 целенаправленно сужает эту точку уязвимости.
Модель безопасности меняется с «доверять по умолчанию» на «явное одобрение». Раньше пакет мог делать почти что угодно в момент установки, если только разработчик специально не передавал флаг --ignore-scripts. Теперь логика перевёрнута: ничего не выполняется без вашего ведома.
Помимо скриптов жизненного цикла, npm 12 вводит аналогичные ограничения для нескольких других категорий зависимостей. Git-зависимости теперь заблокированы по умолчанию, и флаг --allow-git по умолчанию выставлен в none. Зависимости, подключаемые через удалённые URL (например, HTTPS-архивы), тоже блокируются без явного --allow-remote. Нативные сборки через node-gyp для пакетов с файлом binding.gyp раньше запускались автоматически даже без явного скрипта установки; теперь это тоже требует разрешения. Скрипты prepare для Git-, файловых и символических зависимостей обрабатываются по той же логике.
Отдельного внимания заслуживает закрытая брешь с --ignore-scripts. До npm 12 этот флаг считался достаточной защитой: он запрещал выполнение скриптов из package.json. Но через Git-зависимость можно было подсунуть файл .npmrc, который переопределял сам исполняемый файл git. Код выполнялся, даже когда --ignore-scripts был активен. Перевод --allow-git в none по умолчанию именно это и закрывает.
Для тех, кто собирается мигрировать на npm 12, GitHub предлагает конкретную последовательность действий. Сначала нужно обновиться до npm 11.16.0 или выше: именно с этой версии появились предупреждения о скриптах, которые будут заблокированы в следующей мажорной версии. Запустите обычный npm install и посмотрите на предупреждения в выводе: они покажут, какие пакеты используют скрипты. Затем запустите:
npm approve-scripts --allow-scripts-pending
Эта команда выведет список пакетов со скриптами, ожидающими одобрения. Вы проверяете каждый, разрешаете те, которым доверяете, и коммитите обновлённый package.json. После перехода на npm 12 одобренные скрипты продолжат работать, а всё, что не получило явного разрешения, запускаться перестанет.
Раньше в этом году GitHub уже добавил в npm похожую по духу функцию min-release-age: настройку, позволяющую отклонять любые версии пакетов, опубликованные менее чем за указанное количество дней до установки. Смысл тот же: дать разработчику время на обнаружение вредоносного пакета до того, как он попадёт в продакшн. npm 12 строится на той же логике временного карантина и явного контроля.
Практически это означает, что проекты с нативными зависимостями (типичный случай: пакеты вроде bcrypt или canvas, использующие node-gyp и binding.gyp) потребуют ручной проверки перед миграцией. CI-пайплайны, где npm install запускается автоматически, тоже нужно будет привести в порядок заранее. Если этого не сделать, сборки просто сломаются после обновления. Выход npm 12 запланирован на следующий месяц, так что время на подготовку ещё есть, но его уже немного.