Введение
В последнее время экосистема npm столкнулась с серией атак на цепочки поставок, наиболее известной стала атака с использованием червя Shai-Hulud 2.0. В ответ на это GitHub и npm предприняли ряд мер для повышения безопасности публикации пакетов. Ключевое изменение — отказ от долгоживущих npm-токенов в пользу короткоживущих токенов и механизма доверенной публикации (trusted publishing).
Для многих разработчиков, использующих автоматическую публикацию через GitHub Actions, это означает необходимость обновления своих рабочих процессов. Именно с такой ситуацией столкнулся и я: давно настроенный workflow, публикующий новые версии пакетов при создании тега, перестал работать из-за отзыва устаревших токенов.
Документация npm по доверенной публикации даёт общее понимание механизма, но, как выяснилось на практике, некоторые важные детали либо опущены, либо изложены недостаточно явно. Изучая вопрос, я обнаружил на GitHub обсуждение, где другие разработчики описывали те же трудности.
Цель статьи — собрать в одном месте все необходимые действия для успешного перехода на доверенную публикацию. Ниже — пошаговая инструкция, основанная на реальном опыте, с пояснением моментов, которые обычно вызывают вопросы.
Настройка доверенного издателя на npmjs.com
Первый шаг — указать npm, какому именно GitHub-репозиторию и какому workflow разрешено публиковать ваш пакет. Это делается в интерфейсе npm для каждого пакета отдельно.
Где искать настройки
Документация npm лаконично сообщает: «Перейдите в настройки пакета на npmjs.com и найдите раздел "Trusted Publisher"». Однако найти этот раздел с первой попытки удаётся не всем.
Ключевой нюанс: настройки доверенной публикации находятся не в общем списке ваших пакетов (по адресу https://www.npmjs.com/settings/ваш-username/packages), а на странице конкретного пакета в разделе управления доступом:
https://www.npmjs.com/package/название-вашего-пакета/accessЧто нужно указать
На этой странице потребуется заполнить три поля:
- Provider — выбираем GitHub (или GitLab, если используете его).
- Repository — указывается в формате пользователь/репозиторий.
- Workflow name — имя файла вашего workflow (например,
publish.yml). Без этого npm не поймёт, какой именно процесс имеет право публиковать пакет.
Важное замечание о масштабировании
Процедуру необходимо повторить для каждого пакета, который вы планируете публиковать через доверенную публикацию. Если у вас один-два пакета — это не проблема. Для мейнтейнеров с десятками проектов процесс может оказаться утомительным, поскольку автоматизации на уровне списка пакетов на данный момент не предусмотрено.
После того как доверенный издатель настроен на стороне npm, можно переходить к обновлению самого проекта.
Подготовка проекта: что должно быть в package.json
Прежде чем переходить к настройке GitHub Actions, важно убедиться, что файл package.json вашего пакета содержит корректные данные. Некоторые проблемы с доверенной публикацией возникают именно из-за несоответствий в этом файле.
Указываем ссылку на репозиторий
Система доверенной публикации сверяет данные, которые вы указали на сайте npm, с тем, что написано в package.json. Если они разойдутся, публикация может завершиться ошибкой.
Поле repository должно содержать прямую ссылку на GitHub-репозиторий. Рекомендуемый формат:
{
"repository": {
"type": "git",
"url": "git+https://github.com/ваш-username/название-пакета.git"
}
}Обратите внимание: git+https — это стандартный префикс для GitHub-ссылок в npm. Именно такую ссылку система ожидает увидеть при проверке.
Включаем provenance (подтверждение происхождения)
Одно из ключевых преимуществ доверенной публикации — автоматическое создание подтверждений происхождения кода. Они позволяют тем, кто устанавливает ваш пакет, убедиться, что он действительно собран и опубликован из указанного репозитория.
Включить provenance можно тремя способами. Первый — через настройки в package.json:
{
"publishConfig": {
"provenance": true
}
}Преимущество этого способа в том, что настройка хранится прямо в коде пакета и не требует дополнительных флагов при каждом запуске публикации. Если вы предпочитаете управлять этим через переменные окружения или флаги командной строки, соответствующие варианты будут показаны в следующей части, при разборе GitHub Actions.
Модернизация GitHub Actions: пишем правильный workflow
Теперь, когда пакет настроен, а доверенный издатель зарегистрирован на npmjs.com, необходимо обновить сам скрипт публикации в GitHub Actions. Именно здесь возникает большинство сложностей, поскольку требования изменились, а документация описывает их не всегда полно.
Права доступа: OIDC-токен
Для работы механизма доверенной публикации GitHub Actions должен иметь возможность сгенерировать OIDC-токен (OpenID Connect). Этот токен npm использует для проверки подлинности вашего workflow.
Достаточно добавить одну строку в начало вашего workflow-файла или в описание конкретной job:
permissions:
id-token: write # Разрешение на создание OIDC-токенаБез этого разрешения GitHub не предоставит токен, и npm отклонит попытку публикации.
Обновление npm до актуальной версии
В документации npm указано важное требование: доверенная публикация работает только с npm версии 11.5.1 и выше. В виртуальном окружении GitHub Actions по умолчанию может стоять более старая версия.
Чтобы гарантировать совместимость, рекомендуется добавить отдельный шаг для обновления npm перед публикацией:
- name: Update npm to latest version
run: npm install -g npm@latestЭто простая мера предосторожности избавляет от ошибок, связанных с устаревшим клиентом.
Команда публикации: важность флага --provenance
Самый неочевидный момент, с которым столкнулся автор и другие разработчики в GitHub-обсуждениях, касается флага --provenance.
Документация npm утверждает, что при использовании доверенной публикации подтверждения (provenance attestations) добавляются автоматически, и указывать флаг явно не требуется. Однако на практике, по крайней мере при первой публикации, это может не сработать.
Надёжный вариант — указывать флаг явно:
- name: Publish package
run: npm publish --provenanceВозможно, флаг требуется только при первом запуске, после чего система запоминает настройки. Однако его постоянное присутствие в скрипте гарантирует работоспособность и не создаёт проблем.
Альтернативный способ: переменная окружения
Если по каким-то причинам вы не хотите добавлять флаг в команду npm publish, можно воспользоваться переменной окружения. Этот способ даёт тот же эффект, но управляется через конфигурацию окружения, а не через аргументы командной строки:
env:
NPM_CONFIG_PROVENANCE: true
run: npm publishВыбор между флагом и переменной окружения — вопрос личных предпочтений или принятых в проекте стандартов оформления.
Полный пример рабочего workflow
Для наглядности соберём все описанные элементы в единый пример:
name: Publish package
on:
push:
tags:
- 'v*' # Запуск при создании тега с префиксом v
permissions:
id-token: write # Ключевое разрешение для trusted publishing
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Update npm
run: npm install -g npm@latest
- name: Install dependencies
run: npm ci
- name: Publish package
run: npm publish --provenance
env:
NODE_AUTH_TOKEN: $Обратите внимание: переменная NODE_AUTH_TOKEN всё ещё может использоваться для аутентификации, но теперь она работает в паре с доверенной публикацией, а не заменяет её.
Часто задаваемые вопросы
Нужно ли удалять старый NPM_TOKEN из секретов GitHub после настройки trusted publishing?
Удалять токен не обязательно, но это может быть полезно для минимизации используемых секретов. Trusted publishing не требует токена для аутентификации — его заменяет OIDC-токен, который генерирует GitHub.
Однако если ваш workflow использует переменную NODE_AUTH_TOKEN для других целей (например, для установки приватных пакетов из npm-реестра), токен может оставаться необходимым. В таком случае он просто перестаёт отвечать за публикацию, но продолжает использоваться в других операциях.
Почему я получаю ошибку "403 Forbidden" при попытке публикации, хотя всё настроил по инструкции?
Ошибка 403 чаще всего указывает на проблемы с аутентификацией или несовпадение данных. Проверьте последовательно:
- Совпадает ли имя репозитория? Убедитесь, что в настройках доверенного издателя на npmjs.com указан именно тот репозиторий, который используется в workflow.
- Правильно ли указано имя workflow? В настройках требуется указать точное имя файла (например,
publish.yml). Ошибка даже в одну букву приведёт к отказу. - Есть ли разрешение
id-token: write? Без этого разрешения GitHub не сгенерирует OIDC-токен. - Корректен ли
repositoryвpackage.json? Адрес репозитория должен точно совпадать с тем, что указан на npmjs.com.
Нужно ли добавлять флаг --provenance при каждой публикации или достаточно только первой?
Из опыта автора и других разработчиков, упоминавших эту проблему в GitHub-обсуждениях, надёжнее оставить флаг в скрипте на постоянной основе.
Даже если технически после первой успешной публикации флаг становится необязательным (как утверждает документация), его наличие не создаёт проблем. При этом оно гарантирует, что при копировании workflow в другой проект или при смене окружения публикация продолжит работать без дополнительных настроек.
У меня несколько десятков пакетов. Неужели нужно настраивать каждый вручную через веб-интерфейс?
К сожалению, на данный момент интерфейс npmjs.com не предоставляет инструментов для массовой настройки доверенных издателей. Однако процесс можно автоматизировать через npm API.
Пример запроса для добавления доверенного издателя через командную строку:
curl -X POST https://registry.npmjs.org/-/npm/v1/access/название-пакета/trusted-publishers \
-H "Authorization: Bearer ваш-токен-доступа" \
-H "Content-Type: application/json" \
-d '{
"type": "github",
"owner": "ваш-username",
"repo": "название-репозитория",
"workflow": "publish.yml"
}'Для использования этого метода потребуется сгенерировать токен доступа к npm с соответствующими правами. Такой подход требует изучения документации API, но для поддержки большого количества пакетов он может быть оправдан.
Работает ли trusted publishing только с GitHub или есть другие провайдеры?
На момент написания статьи доверенная публикация поддерживается для двух провайдеров:
- GitHub
- GitLab
При настройке на npmjs.com нужно будет выбрать соответствующего провайдера в выпадающем списке. Механика настройки для GitLab аналогична описанной в статье, за исключением формата указания репозитория.
Что делать, если пакет уже опубликован, и я просто перевожу его на trusted publishing?
Процесс перехода не отличается от первичной настройки:
- Зарегистрируйте доверенного издателя на странице пакета.
- Обновите workflow в соответствии с рекомендациями из Части 3.
- Убедитесь, что
package.jsonсодержит корректную ссылку на репозиторий.
При следующей публикации пакет будет выпущен с использованием нового механизма. Существующие версии пакета остаются доступными, их обновлять не требуется.
Заключение
Переход на короткоживущие токены, доверенную публикацию и подтверждение происхождения (provenance) — это не просто ужесточение правил со стороны npm и GitHub. Это важный шаг к повышению безопасности всей экосистемы. Чем больше пакетов будет публиковаться с использованием trusted publishing, тем сложнее злоумышленникам будет внедрять вредоносный код в цепочки поставок.
Предложенная в статье последовательность действий основана на реальном опыте обновления рабочего процесса. Возможно, у вас есть десятки пакетов, и настройка каждого вручную покажется утомительной. Или, как в моём случае, вы обновляете пакет, которым, скорее всего, пользуетесь только вы. В любом случае, понимание механизма доверенной публикации и умение его настраивать — полезный навык для любого поддерживающего npm-пакеты разработчика.
На момент написания статьи trusted publishing поддерживается для GitHub и GitLab. Хочется надеяться, что со временем список провайдеров будет расширяться, а доля пакетов, публикующихся через этот механизм, — расти. И, конечно, хочется верить, что атак вроде Shai-Hulud 2.0 в будущем станет меньше.
Если у вас остались вопросы или вы столкнулись с нюансами, не описанными в статье, — пишите в комментариях. Опыт каждого помогает сделать экосистему безопаснее.