Керування залежностями
npm-залежності закріплені зафіксованим package-lock.json, а встановлення виконують лише перевірені скрипти життєвого циклу. Щодо моделі загроз та обґрунтування цих заходів див. Безпека ланцюга постачання.
Контракти встановлення
CI, devcontainer та Netlify встановлюють із точним блокуванням версій та без скриптів, а потім явно повторно вмикають єдиний перевірений хук: повторну збірку hugo-extended, яка завантажує зафіксований бінарний файл Hugo. Збірка виконується через scripts/rebuild-hugo-extended.mjs, який повторює завантаження з обмеженим backoff і відмовляється виконуватися, якщо встановлено будь-який HUGO_* installer override. Встановлення зберігають опціональні залежності: npm доставляє бінарні файли для потрібної платформи (наприклад, Dart Sass компілятор у sass-embedded) як опціональні залежності, обрані за os/cpu, тому їх вилучення ламає збірку. За середовищами:
- CI:
npm run ci:min; завдання, які збирають сайт, після цього виконуютьnpm run ci:prepare. - Devcontainer:
npm run install:safe, той самий контракт. - Netlify:
npm run install:safe, який виконує команда збірки після inert auto-install, між перевірками чистоти робочого дерева:- Відхилення блокування або будь-які інші зміни, видимі в Git, призводять до збою збірки.
- Для збоїв на шляхах, яких встановлення ніколи не торкалося, див. Застарілий кеш збірки Netlify нижче.
- Локально:
npm run install:safeабо стандартнеnpm install, яке слідує за блокуванням, поки воно узгоджується зpackage.json, і обмежує скрипти життєвого циклу дозволеним списком, а не вимикає їх; див. локальне налаштування.
Вкладене налаштування теми Docsy дотримується того самого контракту: крок prepare викликає власне точне, без скриптів, встановлення залежностей теми.
Застарілий кеш збірки Netlify
Netlify зберігає кеш збірки для кожного контексту розгортання:
- Один для робочої версії
- По одному для кожної назви головної гілки у разі попереднього розгортання, що завантажується з кешу робочої версії під час першої збірки за цією назвою. Історія кешу зникає лише в разі явного очищення: видалення гілки не враховується Netlify, тому гілка, відтворена під тією самою назвою (включно з повторно використаними назвами гілок-ботів), знову привʼязується до старого кешу.
Кожен кеш містить клон репозиторію, а перехід на коміт, який прибирає git-субмодуль, залишає робоче дерево субмодуля на місці, тож видалений субмодуль може через кеш повернутися у наступні збірки як невідстежуваний залишок і призвести до збою перевірок чистоти робочого дерева: журнал розгортання показує шлях у статусному рядку з префіксом ??.
Очистіть відповідний кеш збірки, а не додавайте шлях до .gitignore:
- Робоча версія:
- Очистіть кеш і розгорніть сайт: Deploys > Trigger deploy.
- Deploy Previews: кожна вже зібрана гілка має власну копію кешу, на яку пізніше очищення кешу робочої версії не впливає.
- Очистіть його зі сторінки останнього розгортання PR: Retry > Clear cache and retry with latest branch commit. Масового очищення по всіх гілках немає.
Після видалення git-субмодуля очистіть кеш збірки робочої версії як частину видалення, до того як залишки поширюються у кешах гілок. Також очистіть ланцюжок будь-якого переробленого імені бот-гілки; очищення робочої версії ніколи не досягає його.
Оновлення залежностей
Планові підвищення версій надходять як PR від Renovate, обмежені періодом охолодження, а виправлення відомих вразливостей надходять за сповіщеннями (Оновлення безпеки). Решта випадків робиться вручну; у кожному з них зафіксуйте перегенероване блокування разом із будь-якою зміною package.json.
Зміни маніфесту
Чи ви редагували package.json вручну, чи підвищили кожну версію в межах діапазону за допомогою npm run update:packages (до запропонованих версій застосовується період охолодження), узгодьте блокування із зміненим маніфестом:
npm install --package-lock-only --ignore-scripts
На відміну від npm update (нижче), це переписує лише те, чого вимагає зміна маніфесту, залишаючи інші записи закріпленими. Конфлікт злиття у файлі блокування вирішується тим самим рецептом: залиште версію з main і повторно виконайте команду.
Пакунки зі скриптами
Коли додаєте або оновлюєте пакунок, який має чи потребує запис allowScripts, учасник, що вносить зміну:
- Переглядає скрипти життєвого циклу нової версії.
- Записує результат, зафіксований разом зі зміною залежності та перевірений під час рецензування PR: потрібний скрипт — як схвалення з точною версією, непотрібний — як заборона на рівні назви (
false, яка не потребує оновлення при наступних підвищеннях версій). - Для нового схвалення також додає пакунок до винятку автоматичного злиття Renovate у
.github/renovate.json5: кожне підвищення схваленого пакунка потребує наведених вище кроків, тож його PR з оновленням мають чекати на учасника.
Транзитивне оновлення
Жоден розклад не перевизначає блокування в цілому (визначення версій навмисне). Щоб оновити транзитивні залежності, виконайте наступне за потреби в корені репозиторію (блокування також покриває робочий простір scripts/generate-community-data):
npm update --package-lock-only --ignore-scripts
Період охолодження застосовується, з гострим краєм: залежність, чиї єдині задовільні версії молодші за період охолодження (точне закріплення — поширений випадок), звалює всю резолюцію (ETARGET), доки якась версія не постаріє. Коли молодий випуск — той, який ви переглянули та за який ручаєтесь, звільніть саме цю назву; охолодження залишається увімкненим для решти дерева:
npm_config_min_release_age_exclude=PACKAGE_NAME \
npm update --package-lock-only --ignore-scripts
Замініть PACKAGE_NAME на пакунок, за який ви ручаєтесь. Зберігайте виняток лише для конкретного виклику; постійний запис у .npmrc назавжди скасував би охолодження для цієї назви. Також перегляньте перегенероване блокування на стрибки major-версій: npm update дотримується задекларованих діапазонів у маніфестах, а батьківський пакунок, який розширює діапазон, може потягнути новий транзитивний major.
Неочікувані зміни блокування
Якщо блокування змінилося, але ви не змінювали залежності (перевірка postinstall попереджає, коли встановлення робить це), це ознака відхилення: відновіть блокування та дослідіть причину, а не фіксуйте перезапис.
Оновлення безпеки
Виправлення відомих вразливостей не чекають на щотижневі PR з оновленнями; вони надходять за сповіщеннями:
- GitHub Dependabot security updates: налаштування на рівні репозиторію (без
dependabot.yml), здатне виправляти як прямі, так і транзитивні залежності; для npm це може означати перезапис записів батьківських маніфестів, а не лише блокування. - PR Renovate про вразливості: відкриваються негайно, для прямих залежностей.
Перетин навмисний; випадковий дублікат PR приймається. Оскільки заплановані перевизначення блокування вимкнено за дизайном, ці шляхи за сповіщеннями є єдиним автоматичним способом виправлення транзитивних залежностей, тож налаштування на рівні репозиторію залишається увімкненим. Два шляхи по-різному ставляться до періоду охолодження:
- Безпекові оновлення Dependabot навмисно обходять будь-який поріг віку випуску (включно з
.npmrc): версія з виправленням, молодша за період охолодження, може зійтися, і її перевірка — обов’язок супровідника, який рецензує. - PR Renovate підпорядковується порогу
.npmrc, коли він перегенеровує блокування, тож виправлення, молодше за період охолодження, приходить як невдале оновлення артефакту; раннє його прийняття потребує закріпленого винятку, виконаного супровідником.
Заходи безпеки ланцюга постачання
Аудит ланцюга постачання
Тест аудиту ланцюга постачання, scripts/supply-chain-audit.test.mjs, перевіряє заходи нижче лише з зафіксованих файлів при кожному запуску test:local-tools, щоб регресія заходу виявлялася тестом, а не чекала на інцидент. Щодо принципів перевірки, що лежать в основі самого аудиту, див. його сторінку дизайну.
Коли аудит не проходить у вашому PR, повідомлення стверджування вказує очікувану умову; поширені випадки:
- Ви підвищили залежність, що має запис
allowScripts: дивіться в пакунках зі скриптами; повідомлення про помилку називає версію, до якої запис має бути перенесений. - Ви змінили скрипт шляху встановлення,
.npmrcабоnetlify.toml: це помилка і є метою аудиту. Аудит закріплює поверхню встановлення так, що кожна зміна її проходить свідомий перегляд. Оновіть відповідне твердження разом із вашою зміною, і поясніть чому в PR.
Ніколи не послаблюйте твердження просто щоб отримати зелений статус: кожне з них забезпечує захід на цій сторінці, тож спочатку визначте, який захід ваша зміна послаблює.
Поза межами аудиту:
- GitHub workflow файли
- Конфігурація Renovate (
.github/renovate.json5): переглядається як код, не закріплена аудитом - Власне встановлення залежностей теми Docsy (відстежується upstream)
- npm скрипти збірки після межі встановлення
Період охолодження випусків
Визначення версій ігнорує випуски, молодші за налаштований мінімальний вік.
- Вимога:
min-release-ageу.npmrc. Субпроєктscripts/generate-community-dataє npm-простором, а не окремим домом блокування, тож кореневі.npmrcта блокування керують і його визначенням версій. - Сфера дії:
- Зачіпаються лише операції визначення версій; точні встановлення з блокуванням (
npm ci) не визначають версії. - npm надає перевагу конфігурації проєкту над конфігурацією користувача, тож суворіший період охолодження у вашому власному
.npmrcтут послаблюється до значення проєкту; щоб зберегти своє для одного виклику, встановіть змінну середовищаnpm_config_min_release_age, яка має вищий пріоритет за обидва.
- Зачіпаються лише операції визначення версій; точні встановлення з блокуванням (
- Renovate: застосовує власний період охолодження до PR з оновленнями, які він відкриває, заданий
minimumReleaseAgeу.github/renovate.json5; довший для оновлень, які зливаються без рецензування людиною. Триденне охолодження npm із пресета (security:minimumReleaseAgeNpm) виключено, щоб воно не могло перевизначити ці віки, включно з його винятками за віком; увага: перейменування цього пресета upstream мовчки повернуло б його. Типи оновлень, які Renovate не може датувати (наприклад,pin,replacement,rollback), випадають з-під його охолодження: їхні PR відкриваються зазвичай, у крайньому випадку показуючи постійно очікуваний статус стабільності (не обов’язкову перевірку), тож звичайне рецензування і є барʼєром.
Дозволений список скриптів життєвого циклу
Встановлення виконує скрипти життєвого циклу пакунка лише тоді, коли його точна назва та версія внесені до списку дозволів allowScripts:
- Вимога: мапа
allowScriptsуpackage.json, яка стає стандартно закритою черезstrict-allow-scriptsу.npmrc. - Заборони:
- Запис зі значенням
falseфіксує переглянуту заборону: пакунок встановлюється, його скрипт пропускається. - Заборони нічого не надають, тож вони покривають пакунок за назвою, на всіх версіях.
- Запис зі значенням
- Взаємодія з
--ignore-scripts:- Дозволений список лише фільтрує: він ніколи не вмикає повторно скрипти, які вимикає
ignore-scripts, тож встановлення без скриптів не виконують жодних, дозволених чи ні. - Переглянутий виняток вимагає явного
--ignore-scripts=falseу місці виклику.
- Дозволений список лише фільтрує: він ніколи не вмикає повторно скрипти, які вимикає
Мінімальна версія npm
Встановлення завершується збоєм, коли активний npm старший за мінімальну версію engines: найстарішу версію, яка підтримує наведені вище заходи.
- Забезпечення:
enginesуpackage.jsonзадає мінімальну версію.engine-strictу.npmrcробить це закритим.
- Політика мінімальної версії:
- Мінімальна версія зростає, коли npm виправляє прогалини у забезпеченні цих заходів.
- Закріплений
.nvmrcфіксує випуск Node.js, чий вбудований npm задовольняє мінімальну версію, тож CI, Netlify і локальні налаштування під керуваннямnvmпроходять перевірку за побудовою; Renovate оновлює це закріплення. (Плаваючий.nvmrc, як-отlts/*, не може цього гарантувати: виконавці CI визначають його з можливо застарілих кешів.)
- Netlify:
- Стандартний npm, вбудований у Node від Netlify, може бути старшим за мінімальну версію;
NPM_VERSIONуnetlify.tomlзакріплює версію, яка її задовольняє. - Оновлюйте закріплення щонайменше тоді, коли зростає мінімальна версія.
- Стандартний npm, вбудований у Node від Netlify, може бути старшим за мінімальну версію;
Автоматичне встановлення Inert Netlify
Автоматичне встановлення Netlify на початку збірки нейтралізується NPM_FLAGS у netlify.toml:
--dry-run: npm вирішує та логує, що змінило б встановлення, але нічого не записує.--ignore-scripts: скрипти життєвого циклу залишаються вимкненими за явною вказівкою, а не як побічний ефект сухого запуску.
Сфера дії: NPM_FLAGS — це налаштування збірки Netlify, а не конфігурація npm; він застосовується лише до автоматичного встановлення, ніколи до запусків npm у команді збірки.
Захист у глибину: справжнє встановлення — це npm ci, яке замінює node_modules повністю, тож залишки автоматичного встановлення чи кешу збірки там не переживають збірку, навіть якщо node_modules невидиме для перевірок чистоти робочого дерева (вони бачать лише зміни, видимі в Git).
Жодного голого npx
Обвʼязка репозиторію (скрипти пакунка, CI, допоміжні скрипти, документація для учасників) ніколи не викликає бінарний файл як npx BIN: на застарілому чи відсутньому node_modules npx звертається до публічного реєстру та виконує будь-який пакунок, якому належить ця назва. Його запит на встановлення не є захистом: він пропускається в неінтерактивних контекстах і провокує бездумне «так» в інших. Локалізовані копії документації для учасників наздоганяють це правило через відстеження відхилень.
- Натомість:
- Скрипти пакунка викликають бінарні файли, надані залежностями, безпосередньо; npm додає
node_modules/.binдо їхньогоPATH, а відсутній бінарний файл завершується гучним збоєм без жодного звернення до реєстру. - Контексти без запису в
PATH(документація, окремі скрипти) використовуютьnpm exec --no -- BIN, який ніколи нічого не встановлює.
- Скрипти пакунка викликають бінарні файли, надані залежностями, безпосередньо; npm додає
- Забезпечення: дисципліна рецензування; автоматичної перевірки немає.