# Безпека ланцюга постачання

> Модель загроз та обґрунтування заходів контролю npm-залежностей сайту

---

LLMS index: [llms.txt](/llms.txt)

---

Щодо самих заходів контролю та щоденних процедур див. [Керування залежностями](../../build/dependencies/). Суміжні теми безпеки мають власні розділи: привілеї тригерів робочих процесів і токенів — у [CI-робочих процесах](../../build/ci-workflows/#security-model), а повідомлення про вразливості — у [політиці безпеки][security policy].

## Модель загроз {#threat-model}

Серпневий хробак npm 2026 року ([повідомлення про безпеку][security notice]) визначив поточну позицію: зламані облікові записи підтримувачів публікували шкідливі версії популярних npm-пакунків. [Скрипти життєвого циклу][lifecycle scripts] пакунків, які виконуються під час встановлення, запускали корисне навантаження та поширювали його за допомогою викрадених облікових даних. До стримування було уражено кілька PR-гілок цього репозиторію; жодна не досягла `main` або виробничого середовища.

Шляхи атак, важливі для цього репозиторію:

- **Визначення версій**: будь-яке встановлення, яке визначає діапазони версій, може завантажити щойно опублікований шкідливий випуск.
- **Скрипти життєвого циклу**: виконання скриптів під час встановлення дозволяє шкідливому пакунку скомпрометувати хости учасників, виконавців CI та образи збірки.
- **Встановлення без нагляду**: завдання CI та [образ збірки Netlify][Netlify] встановлюються без нагляду людини, як і сеанси агентів.
- **Визначення назв**: виклик інструмента, який може звернутися до реєстру за назвою (`npx`), виконує той пакунок, якому належить ця назва, коли локальне встановлення застаріле або відсутнє.

## Рішення щодо дизайну {#design-decisions}

Наскрізна тема — **закриватися при збої** (fail closed): коли [захід контролю][control] неможливо забезпечити, встановлення завершується збоєм, а не продовжується без нього.

Кожне рішення відповідає **шляху атаки**, упорядковані приблизно за моментом дії. Підсумкова таблиця зіставляє рішення з їхнім забезпеченням.

- _Кожна залежність, пряма чи транзитивна, — це поверхня, якої може досягти нападник._
  - **Мінімізація залежностей**: <a id="minimize"></a> невикористані та зручні залежності видаляються, а не зберігаються.
- _Встановлення, яке визначає діапазони версій, може завантажити щойно опублікований шкідливий випуск._
  - **Встановлення з блокування**: <a id="lock"></a> встановлення є [точними щодо блокування][install contracts], відтворюючи зафіксований і перевірений [`package-lock.json`][]. Єдиний виняток: локальний `npm install` може перезаписати незгодне блокування; такі перезаписи виявляє [перевірка](#verify).
  - **Явне визначення версій**: <a id="deliberate"></a> визначення версій відбувається лише під час [навмисних оновлень залежностей][deliberate dependency updates], ніколи як побічний ефект встановлення.
  - **Визначення версій лише для випусків, що відстоялися**: <a id="cooldown-releases"></a> навіть явне визначення версій ігнорує випуски, молодші за [період охолодження][cooldown]; відкликання шкідливих випусків на стороні реєстру потребує кількох днів.
- _Скрипти пакунка, що виконуються під час встановлення, запускають код нападника на хостах учасників та машинах збірки: шлях корисного навантаження хробака._
  - **Виконуються лише перевірені скрипти життєвого циклу**: <a id="scripts"></a> [скрипти життєвого циклу][lifecycle scripts] є [стандартно забороненими][allowlist].
    - Схвалення є точними щодо версії, тому скомпрометований патч-випуск не може успадкувати схвалення свого попередника.
    - Перевірки фіксують і заборони, тож відсутність відповіді завжди означає «не перевірено».
    - Винятки називаються та повторно вмикаються безпосередньо в місці використання, ніколи не послаблюючи стандартну позицію.
- _[Власне встановлення npm][netlify-deps] Netlify виконується без нагляду, поза скриптами, які контролює цей репозиторій, і його неможливо вимкнути._
  - **Нейтралізація автоматичного встановлення**: <a id="auto-install"></a> конфігурація [нейтралізує його][inert auto-install]; команда збірки виконує [справжнє встановлення][install contracts].
- _Бінарний файл, викликаний за назвою, яку можна визначити через реєстр, виконує те, що претендує на цю назву, коли локальне встановлення застаріле; сквотінг незареєстрованої назви бінарного файлу довів це у червні._
  - **Викликайте бінарні файли, а не назви**: <a id="no-bare-npx"></a> обвʼязка репозиторію [ніколи не використовує голий `npx`][no bare npx]; бінарні файли беруться зі встановленого дерева залежностей або завершуються гучним збоєм.
- _Захід контролю, який мовчки перестає застосовуватися, гірший за його відсутність._
  - **Блокування при збої на старому npm**: <a id="old-npm"></a> встановлення завершується збоєм, а не продовжується, коли активний npm занадто старий, щоб застосовувати налаштування `.npmrc`.
  - **Перевірка замість довіри**: <a id="verify"></a> інертність і точність щодо блокування — це перевірені твердження, а не припущення.

Забезпечення з першого погляду:

| Рішення                                                                                    | Забезпечується                                                                                                                                  |
| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| [Мінімізація залежностей][Minimize dependencies]                                           | Рішення підтримувача під час перегляду залежностей; механічного контролю немає                                                                  |
| [Встановлення з блокування][Install from the lock]                                         | `npm ci` у кожному [контракті встановлення][install contracts]                                                                                  |
| [Явне визначення версій][Resolve deliberately]                                             | Конвенція, підкріплена блокуванням: неочікуване визначення перезаписує його, що позначає перевірка                                              |
| [Визначення версій лише для випусків, що відстоялися][Resolve only cooled-down releases]   | Захід [охолодження][cooldown], однаково для npm та Renovate                                                                                     |
| [Виконання лише перевірених скриптів життєвого циклу][Run only reviewed lifecycle scripts] | [Дозволений список][allowlist] у строгому режимі; неперевірене завершує встановлення збоєм                                                      |
| [Нейтралізація автоматичного встановлення][Neutralize the auto-install]                    | Захід [інертного автоматичного встановлення][inert auto-install]                                                                                |
| [Викликайте бінарні файли, а не назви][Invoke bins, not names]                             | Правило [жодного голого npx][no bare npx]; дисципліна рецензування, механічного контролю немає                                                  |
| [Блокування при збої на старому npm][Fail closed on old npm]                               | [Мінімальна версія npm][npm engines floor] зі строгою перевіркою рушія                                                                          |
| [Перевірка замість довіри][Verify, don't trust]                                            | [Перевірки чистого робочого дерева][install contracts] завершують збірку збоєм; попередження `postinstall` при локальних перезаписах блокування |

## Попередні спроби {#prior-art}

- Стандартна заборона запуску скриптів життєвого циклу — це напрямок екосистеми:
  - [pnpm][] і [Yarn][] стандартно блокують скрипти залежностей.
  - Прийнятий npm [RFC #54][] впроваджує ту саму модель у npm через `allowScripts`, включно із записами, точними щодо версій.
- Періоди охолодження випусків — усталена практика:
  - [pnpm стандартно відкладає][pnpm defers] випуски, молодші на день.
  - Значення 3 дні відповідає давній домовленості [Renovate `minimumReleaseAge`][renovate].
- Набір заходів відповідає усталеним рекомендаціям фреймворків:
  - [Таксономія атак TUF][tuf]: атаки довільного встановлення програмного забезпечення, «mix-and-match» та зайвих залежностей.
  - [Посібник OpenSSF з npm][openssf]: точні щодо блокування CI-встановлення.

<!-- prettier-ignore-start -->
[`package-lock.json`]: https://docs.npmjs.com/cli/configuring-npm/package-lock-json
[allowlist]: ../../build/dependencies/#lifecycle-script-allowlist
[control]: ../../build/dependencies/#controls
[cooldown]: ../../build/dependencies/#release-cooldown
[deliberate dependency updates]: ../../build/dependencies/#updating
[Fail closed on old npm]: #old-npm
[inert auto-install]: ../../build/dependencies/#inert-netlify-auto-install
[install contracts]: ../../build/dependencies/#install-contracts
[Install from the lock]: #lock
[Invoke bins, not names]: #no-bare-npx
[lifecycle scripts]: https://docs.npmjs.com/cli/using-npm/scripts
[Minimize dependencies]: #minimize
[netlify-deps]: https://docs.netlify.com/build/configure-builds/manage-dependencies/#npm
[Netlify]: https://www.netlify.com/
[Neutralize the auto-install]: #auto-install
[no bare npx]: ../../build/dependencies/#no-bare-npx
[npm engines floor]: ../../build/dependencies/#npm-version-floor
[openssf]: https://github.com/ossf/package-manager-best-practices/blob/main/published/npm.md
[pnpm defers]: https://pnpm.io/settings/dependency-resolution
[pnpm]: https://pnpm.io/settings/build
[renovate]: https://docs.renovatebot.com/configuration-options/#minimumreleaseage
[Resolve deliberately]: #deliberate
[Resolve only cooled-down releases]: #cooldown-releases
[RFC #54]: https://github.com/npm/rfcs/blob/main/accepted/0054-make-scripts-install-opt-in.md
[Run only reviewed lifecycle scripts]: #scripts
[security notice]: https://github.com/open-telemetry/opentelemetry.io/issues/11210
[security policy]: https://github.com/open-telemetry/opentelemetry.io/security/policy
[tuf]: https://theupdateframework.io/docs/security/
[Verify, don't trust]: #verify
[Yarn]: https://yarnpkg.com/advanced/lifecycle-scripts
<!-- prettier-ignore-end -->
