«Я только поставил зависимости, приложение не запускал». Для JavaScript-проекта это важное уточнение, но ещё не ответ на вопрос об исполнении кода.
npm поддерживает скрипты жизненного цикла. В зависимости от команды, версии и типа пакета установка может вызвать preinstall, install, postinstall, prepare и связанные этапы. Такие скрипты встречаются в обычной разработке: например, для подготовки сборки. Их наличие требует проверки назначения, а не автоматического обвинения в распространении вредоносного кода. [1]
Где заканчивается «скачивание файлов»
Рассмотрим учебный фрагмент корневого package.json. Для этой цепочки и задачи в конце статьи используем npm 11 с разрешёнными скриптами жизненного цикла:
| |
Здесь postinstall вызывает команду запуска приложения. Чтобы понять риск, нужно прочитать server/app.js, затем проследить его импорты. Название tools/build.js тоже не гарантирует, что файл только собирает проект.
Важен весь путь:
| |
Любое звено может изменить результат: пакет не установился, команда завершилась с ошибкой, импорт не достигнут, сетевая ветвь не сработала. Статический разбор показывает возможную цепочку; журнал конкретного запуска помогает установить состоявшуюся.
Почему недостаточно корневого package.json
Проверка начинается с корневого файла, но продолжается в рабочих пакетах монорепозитория и зависимостях. Отдельного внимания требуют Git-зависимости и операции подготовки пакета: условия вызова prepare отличаются от простого запуска произвольного скрипта. Точную последовательность нужно сверять с документацией используемой версии npm. [1]
Также стоит прочитать .npmrc. Это конфигурация менеджера пакетов, а значит часть условий выполнения: откуда берутся зависимости и какие параметры применяются. Не копируйте в чужую среду свой пользовательский конфиг вместе с токенами доступа. [2]
Само получение проекта из репозитория ещё не доказывает компрометацию публичного реестра пакетов. В нашем учебном сценарии достаточно вредоносной логики в корневом проекте. Поэтому точнее говорить о запуске недоверенного проекта через установочные хуки, а не объявлять любой такой случай атакой на npm registry.
Что даёт --ignore-scripts
При установке этот параметр отключает выполнение скриптов из package.json. Но явная команда вроде npm start, npm test или npm run build запускает выбранный скрипт даже при действующем ignore-scripts; подавляются только сопутствующие pre/post-этапы. Это прямо описано в документации npm. [2]
Флаг одного вызова не становится постоянной настройкой: после npm ci --ignore-scripts отдельный npm start не наследует его. Если запрет не задан в конфигурации или окружении, вместе со start могут сработать и prestart/poststart.
Следовательно, сочетание «установил с --ignore-scripts, затем сразу запустил npm start» не сохраняет запрет на выполнение кода. Оно только переносит момент исполнения.
Для анализа известного снимка с согласованным lockfile в отдельной подготовленной среде возможна команда:
| |
Это не первый шаг проверки неизвестного архива. Сначала читают конфигурацию и оценивают происхождение зависимостей. Даже такая установка получает и обрабатывает внешние данные, поэтому её не следует представлять как безопасное действие на машине с рабочими секретами.
OWASP рекомендует ограничивать установочные скрипты и проверять чужой проект до их разрешения. [3]
Почему lockfile не выдаёт сертификат безопасности
npm ci требует lockfile, проверяет согласованность с package.json и не обновляет его по ходу установки. Но команда не запрещает lifecycle scripts сама по себе. [4]
Фиксация версии и контроль целостности помогают установить, какие именно байты получены. Они не доказывают, что эти байты доброкачественны. Зафиксированная вредоносная версия воспроизводится не хуже обычной.
То же относится к результату проверки известных уязвимостей. npm audit запрашивает сведения об известных проблемах зависимостей; отсутствие отчёта не является экспертизой произвольного JavaScript, скрытого в файлах проекта. [5]
Разрешение скриптов как отдельное решение
После первичного анализа может выясниться, что конкретной зависимости действительно требуется подготовительный этап. Тогда разрешение привязывают к известному пакету, версии и понятной команде, а не ко всему содержимому репозитория.
При редакции в ноябре 2026 года документация npm 12 уже описывает политику allowScripts: установочные скрипты зависимостей без соответствующего разрешения пропускаются. Это важное отличие от прежних выпусков, но оно не отменяет проверку корневого проекта. Его собственные хуки при обычном локальном npm install обрабатываются отдельно и не становятся безопасными из-за запрета скриптов зависимостей. [6][7]
В этой статье мы намеренно ограничиваемся npm. У Yarn и pnpm есть собственные настройки и меняющееся между версиями поведение. Переносить один флаг и считать результат одинаковым без проверки документации не стоит.
Задача: что изменится в трёх запусках?
Файлы для самостоятельного разбора доступны в синтетическом практикуме.
Для фрагмента выше рассматриваются действия:
| |
В упражнении предполагаются npm 11, согласованный lockfile, разрешённые политикой окружения lifecycle scripts и отсутствие дополнительных ограничений.
Показать разбор
Разбор. В А разрешена цепочка lifecycle scripts, поэтому postinstall может запустить приложение. В Б эта автоматическая цепочка подавлена. В В отдельная команда npm start всё же вызывает приложение. Ни один ответ не устанавливает, что именно приложение сделало: для этого нужно продолжить чтение и сопоставить его с журналами.
Что стоит запомнить
Установка, сборка и тестирование — разные команды, но каждая может стать точкой исполнения. Проверять нужно не название действия, а программу, до которой оно доводит управление.
Источники
[1] npm: Scripts — lifecycle events и условия prepare.
[2] npm: Config — ignore-scripts и другие параметры.
[3] OWASP: NPM Security Cheat Sheet.
[4] npm: npm ci.
[5] npm: npm audit.
[6] npm 12: управление разрешениями установочных скриптов зависимостей.
[7] npm CLI: реализация install и отдельный вызов root lifecycle scripts.
Установочное поведение следует проверять для фактической версии npm. Статья не переносит возможности нынешних выпусков на событие января 2026 года.