npm staged publishing і allow-* controls: як зробити publish безпечнішим

npmNode.jsJavaScriptCI/CDSupply Chain

npm пакет проходить через staged release queue, CI, ручне підтвердження і allowlist для джерел залежностей

Зачіпка

npm одночасно підсилив дві болючі ділянки JavaScript supply chain: фінальний publish пакета і джерела, з яких проєкт дозволяє встановлювати залежності.

Staged publishing став загальнодоступним, а npm CLI 11.15.0+ отримав нові allow-* controls для file, remote і directory sources. Разом із allow-git це вже не просто новина про npm, а нормальний привід переглянути release pipeline.

Проблема / Контекст

Багато команд роками живуть із простим flow: CI збирає пакет, запускає тести і робить npm publish. Це зручно, поки все працює. Але якщо CI token занадто широкий, workflow скомпрометований або release job запускається не там, де треба, пакет одразу вилітає в registry.

Staged publishing додає паузу між автоматичною підготовкою пакета і фінальною публікацією. CI може виконати npm stage publish, але останній крок робить людина через CLI або npmjs.com. Для сильнішої моделі npm прямо поєднує це з trusted publishing, restricted token access і stage-only permissions.

Друга частина змін про install. Залежності не завжди приходять із registry. У package.json можуть бути git URL, локальні файли, директорії або remote tarballs. Іноді це легальний workflow. Іноді це випадковий обхід нормального dependency review.

Нові allow-file, allow-remote, allow-directory доповнюють allow-git. Кожне правило може мати значення all, none або root, тож політика не зводиться до “зламати все” або “дозволити все”.

Чому це важливо

Publish і install - це дві сторони одного ризику. Publish визначає, що команда випускає назовні. Install визначає, що команда тягне всередину.

Якщо CI може напряму publish-ити пакет, один зламаний workflow може стати production-інцидентом для всіх користувачів пакета. Якщо install безконтрольно дозволяє non-registry sources, у проєкт може потрапити залежність, яку складніше перевірити, відтворити й оновити.

Для бібліотек це ризик репутації. Для application repo це ризик непомітної залежності, яка обходить звичний registry review. Для platform-команди це ще й проблема повторюваності: build має бути зрозумілим через місяць, а не залежати від випадкового URL або локальної директорії.

Як це зробити

1. Знайдіть прямий publish

Почніть із пошуку release jobs:

rg "npm publish|npm stage publish|NPM_TOKEN|NODE_AUTH_TOKEN" .github .gitlab-ci.yml package.json .npmrc

Якщо бачите npm publish у CI, поставте просте питання: цей job має право одразу змінити public registry чи лише підготувати release?

2. Оновіть runtime для stage flow

Staged publishing потребує npm CLI 11.15.0+ і Node.js 22.14.0+. Зафіксуйте це в CI, а не покладайтеся на image “latest”:

- uses: actions/setup-node@v4
  with:
    node-version: 22.14.0
- run: npm -v

Після цього замініть фінальний publish у CI на:

npm stage publish --provenance

Фінальний approve лишіть людині з 2FA. Ідея не в тому, щоб зробити release повільним. Ідея в тому, щоб CI не був останньою інстанцією перед registry.

3. Звузьте права CI

Найкращий варіант - trusted publishing без довгоживучого npm token. Якщо token ще потрібен, він не має бути універсальним publish-ключем на все.

Для trust relationship перевірте, що CI має stage-only permission. Тоді навіть скомпрометований job не зможе одразу опублікувати пакет, а лише створить staged version, яку треба окремо підтвердити.

4. Перевірте non-registry dependencies

Знайдіть залежності не з registry:

node -e "const p=require('./package.json'); for (const [k,v] of Object.entries({...p.dependencies,...p.devDependencies})) if (/^(git\\+|https?:|file:|\\.\\.?\\/)/.test(v)) console.log(k, v)"

Потім почніть з помірної політики:

npm install --allow-git=root --allow-remote=none --allow-file=root --allow-directory=root

root означає: дозволити тільки те, що явно описано у root package. Це хороший проміжний режим для команд, які мають свідомі локальні workspace-патерни, але не хочуть неочікуваних джерел у transitive dependency tree.

5. Додайте CI smoke test

Окремий job має перевіряти install policy:

npm ci --allow-git=root --allow-remote=none --allow-file=root --allow-directory=root

Якщо він падає, не вимикайте політику одразу. Спершу з’ясуйте, яке джерело блокується і чи воно справді потрібне.

Антипатерни

  • лишити npm publish у CI з broad token і назвати це automation;
  • увімкнути staged publishing, але дати CI повний publish access;
  • approve-ити staged release без перегляду package contents;
  • дозволити allow-remote=all, бо один старий пакет так працює;
  • не фіксувати Node.js і npm CLI у release job;
  • змішати publish hardening з великим refactor release pipeline.

Висновок / План дій

Мінімальний здоровий rollout такий:

  1. знайти всі місця з npm publish;
  2. оновити release job до Node.js 22.14.0+ і npm CLI 11.15.0+;
  3. перевести CI на npm stage publish;
  4. залишити фінальний approve за людиною з 2FA;
  5. звузити CI до trusted publishing або stage-only permission;
  6. перевірити package.json на non-registry sources;
  7. додати npm ci з allow-* policy;
  8. задокументувати винятки й rollback.

Офіційні джерела:

Короткий чеклист

  • Перевірити, чи CI має пряме право npm publish.
  • Оновити Node.js і npm CLI до версій, які підтримують staged publishing.
  • Замінити прямий publish на npm stage publish.
  • Залишити фінальний approve за людиною з 2FA.
  • Обмежити non-registry sources через allow-* controls.
  • Задокументувати rollback для release pipeline.

Prompt Pack: перевірити npm publish та install policy

Допоможи перевірити npm supply-chain hardening у нашому Node.js проєкті. Вхідні дані: - package.json і .npmrc; - як зараз запускається npm publish у CI/CD; - чи використовується trusted publishing; - які npm tokens або trust relationships мають publish-права; - фрагмент package-lock.json або список non-registry dependencies; - поточна версія Node.js і npm CLI; - чи є ручний release approval. Поверни: 1. verdict: publish-flow безпечний, ризикований або критичний; 2. де є прямий publish із CI; 3. план переходу на npm stage publish і manual approve; 4. рекомендовані allow-git, allow-remote, allow-file, allow-directory; 5. CI smoke test для install policy; 6. rollback-план, якщо release або install зламається. Формат відповіді: verdict, ризики, diff команд, rollout на 30 хвилин, rollback.