Слогер Создать блог
Разработка

npm v12 больше не запускает скрипты установки без вашего ведома — разбор реального аудита

Как инструмент npm-script-lens помогает понять, какие пакеты можно доверять, а какие требуют внимания.

С выходом npm v12 8 июля скрипты жизненного цикла зависимостей (preinstall, install, postinstall) и неявные сборки node-gyp больше не выполняются автоматически. Теперь их нужно явно разрешать в блоке allowScripts в package.json.

Это серьёзное улучшение безопасности цепочки поставок. Но оно ставит перед каждой командой задачу: просмотреть список пакетов и решить, какие из них заслуживают доверия.

npm сообщает, что пакет sharp хочет запустить скрипт установки, но не говорит, что именно этот скрипт делает. Строка "postinstall": "node install.js" абсолютно непрозрачна — ответ лежит внутри install.js, внутри тарбола, плюс все файлы, которые он подключает.

Мне надоело читать тарболы вручную, поэтому я создал npm-script-lens — open-source инструмент, который анализирует их за вас и выдаёт отчёт. Эта статья — реальный аудит с реальными результатами, без макетов.

Аудит

Демо-проект, зависящий от sharp, prisma, chalk и core-js — всего 39 заблокированных пакетов:

npx npm-script-lens audit --fail-on-high

Примерно через пять секунд (инструмент скачивает тарболы только для пакетов, у которых есть скрипты установки — у большинства их нет):

Проверено 39 заблокированных пакетов: 2 ВЫСОКОГО, 0 СРЕДНЕГО, 2 НИЗКОГО риска; 35 без опасного поведения.

ПакетСкриптРискСигналы (сокращённо)@prisma/engines@5.22.0
через prisma
1.7 года · 15.4 млн загрузок/нед · 4 мейнтейнера · provenance ✓postinstall🟥 ВЫСОКИЙexec: require('execa') · net: require('@prisma/fetch-engine') · fs: writeFileSync · …sharp@0.33.5
1.9 года · 74.1 млн загрузок/нед · 1 мейнтейнер · без provenanceinstall🟥 ВЫСОКИЙexec: node-gyp rebuild --directory=src · exec: pkg-config --modversion vips-cpp · obf: require(<string-built specifier>) · …core-js@3.38.1postinstall🟡 НИЗКИЙfs: fs.writeFileSync · env: process.envprisma@5.22.0preinstall🟡 НИЗКИЙenv: process.env

Читая это как человек:

  • @prisma/engines — ВЫСОКИЙ, и это реальное поведение: его postinstall скачивает бинарники движка по сети и запускает процессы. Строка «через prisma» показывает, как пакет попал в дерево; строка доверия (15 млн загрузок/нед, sigstore provenance) говорит, что это известный Prisma делает известные вещи — а не трёхдневный клон.
  • sharp — ВЫСОКИЙ, потому что нативные сборки — это ВЫСОКИЙ риск: node-gyp rebuild, pkg-config, brew probe. Каждый сигнал цитируется из реальной цепочки скриптов — анализатор прошёл по install/check.js и его относительным require, чтобы их найти.
  • core-js пишет файл и читает env — это его знаменитый баннер postinstall. НИЗКИЙ.
  • 35 пакетов, включая chalk, вообще не имеют скриптов установки. Ничего не нужно одобрять.

И часть, которую вы вставляете в package.json — рискованные записи по умолчанию false, с привязкой к версии, как требует формат npm v12:

{ "allowScripts": { "@prisma/engines@5.22.0": false, "core-js@3.38.1": true, "prisma@5.22.0": true, "sharp@0.33.5": false } }

Вы проверяете два ВЫСОКИХ, решаете, что они легитимны для вашего проекта, меняете на true — и теперь каждое решение в этом блоке принято на основе фактов.

Что действительно пугает: обновления

Одобрение sharp@0.33.5 сегодня ничего не говорит о sharp@0.34.0 в следующем месяце. Реальные атаки на цепочку поставок (event-stream, волна Shai-Hulud в этом году) — это в основном взломанные релизы: новая версия доверенного пакета тихо получает возможности, которых не было у старой.

Поэтому в режиме --diff (для PR — сравнение с lockfile базовой ветки) обновлённые пакеты сравниваются с анализом версии, которая у вас была:

git show origin/main:package-lock.json > /tmp/base-lock.json npx npm-script-lens audit --diff /tmp/base-lock.json --fail-on-high

Реальный вывод для обновления sharp с 0.32.6 до 0.33.5:

⚠️ добавлено по сравнению с 0.32.6: exec: node-gyp rebuild --directory=src · obf: require(<string-built specifier>)

Это легитимное изменение (sharp перестроил нативную сборку в 0.33). Но именно так выглядит взломанный релиз — и именно эту строку вы хотите видеть в комментарии к PR, прежде чем кто-то одобрит обновление. Скучное обновление показывает «нет новых возможностей по сравнению с 0.32.6».

Ещё две вещи выполняются при каждом аудите:

  • Проверка OSV: каждый пакет проверяется через OSV.dev; уведомление MAL-* помечается как ⛔ ИЗВЕСТНО ВРЕДОНОСНЫЙ, принудительно устанавливается false и ломает CI независимо от результатов анализа скриптов.
  • Обработка обфускации: eval, new Function, строковые require() получают оценку ВЫСОКИЙ — а base64/char-code литералы декодируются и анализируются повторно, так что отчёт показывает, что на самом деле делает скрытый код.

Поддержание блока в актуальном состоянии

Записи в npm v12 привязаны к версии, поэтому каждое обновление зависимости молча аннулирует ваши одобрения. Это превращается в рутину, которая автоматизируется:

npx npm-script-lens sync --check # CI: exit 1 при расхождении allowScripts npx npm-script-lens sync --write # перепривязка: решения СОХРАНЯЮТСЯ, если новая версия ничего не добавила, и отмечаются, если добавила npx npm-script-lens approve # интерактивный режим: доказательства по каждому пакету, y/n

Также есть GitHub Action (отчёт в виде комментария к PR + SARIF для code scanning), режим --offline для аудита node_modules на диске и MCP-сервер (npx npm-script-lens mcp), чтобы AI-агенты могли проверить пакет до добавления его как зависимости. Поддерживаются lockfile: npm, yarn (classic + berry), pnpm, bun.

Честные ограничения

Это статическое обнаружение возможностей, а не доказательство злонамеренности. ВЫСОКИЙ означает «этот скрипт может запускать процессы» — это правильный вопрос перед предоставлением прав на установку, но многие ВЫСОКИЕ пакеты — легитимные нативные сборки. Полезные нагрузки, собираемые только во время выполнения, остаются непрозрачными (отмечаются, но не декодируются). Инструмент даёт доказательства; решение принимаете вы.

Попробуйте

npx npm-script-lens audit --path ./your-project --fail-on-high
  • GitHub: https://github.com/Booyaka101/npm-script-lens (MIT)
  • npm: https://www.npmjs.com/package/npm-script-lens
  • RRFC с предложением встроить такой отчёт в npm: https://github.com/npm/rfcs/issues/897

Если вы в процессе миграции на npm v12, мне было бы интересно узнать, что в отчёте правильно, а что нет в вашем дереве зависимостей.

По материалам: dev.to. Текст переработан редакцией Слогера.

← На главную

Рекламное место — Конец поста
Реклама · Слогер

Комментарии (0)

Войдите, чтобы комментировать.

Пока нет комментариев. Будьте первым.