Пошук уроків, статей та іншого контенту
Навчитеся перевіряти вразливості npm-пакетів, оновлювати залежності та контролювати ризики ланцюга постачання.
NestJS-застосунок складається не лише з коду проєкту. У ньому можуть бути сотні пакетів:
прямі залежності, які вказані у package.json;
транзитивні залежності, встановлені іншими пакетами;
пакети для тестування, збирання та лінтингу;
npm-скрипти, які виконуються під час встановлення;
бінарні модулі, що постачаються окремими пакетами.
Вразливість у транзитивній залежності також може вплинути на production-застосунок. Наприклад, уразливий HTTP-парсер, валідатор або пакет для обробки файлів може бути доступним через функціональність NestJS, навіть якщо ви не імпортували його безпосередньо.
Безпека залежностей має охоплювати три завдання:
виявлення відомих вразливостей;
контроль і оновлення версій;
зменшення ризиків підміни або компрометації пакетів.
Прямі залежності оголошуються у package.json:
{
"dependencies": {
"@nestjs/common": "^10.4.0",
"@nestjs/core": "^10.4.0",
"class-validator": "^0.14.1"
},
"devDependencies": {
"@nestjs/cli": "^10.4.0",
"typescript": "^5.4.0"
}
}Префікс ^ дозволяє npm встановити новішу сумісну версію в межах major-релізу. Наприклад, для ^10.4.0 допустимими є версії 10.x.x, але не 11.0.0.
Фактично встановлені версії зберігаються в package-lock.json. Саме lock-файл визначає точне дерево пакетів, яке буде встановлено в CI та production.
package-lock.json потрібно комітитиLock-файл забезпечує відтворювані встановлення:
npm ciКоманда npm ci:
використовує версії з package-lock.json;
не змінює lock-файл;
видаляє наявний node_modules;
завершується з помилкою, якщо package.json і lock-файл не узгоджені.
Для застосунку NestJS lock-файл потрібно зберігати в системі контролю версій. Його видалення або ігнорування призводить до того, що різні середовища можуть отримати різні версії транзитивних пакетів.
Запустити аудит можна командою:
npm auditnpm порівнює дерево залежностей із базою відомих вразливостей і показує:
назву уразливого пакета;
діапазон уразливих версій;
рівень серйозності;
ланцюжок залежностей;
рекомендацію щодо виправлення.
Для машинної обробки результату використовується JSON:
npm audit --json > audit-report.jsonЦей файл можна передати системі CI або зберігати як артефакт перевірки.
Зазвичай npm використовує такі рівні:
low — низький ризик;
moderate — помірний;
high — високий;
critical — критичний.
У CI можна заборонити успішне завершення команди, якщо знайдено вразливість не нижче певного рівня:
npm audit --audit-level=highЦе означає: збірка завершиться помилкою лише за наявності high або critical. Вразливості нижчого рівня будуть показані, але не зламають команду.
Такий поріг не повинен бути єдиною політикою. Ризик залежить також від:
того, чи використовується уразливий код;
доступності функціональності з мережі;
типу застосунку;
наявності автентифікації;
середовища виконання;
доступу процесу до секретів та інших систем.
Щоб зрозуміти, чому пакет встановлений, використовуйте:
npm explain <назва-пакета>Наприклад:
npm explain cookieКоманда покаже, який прямий або транзитивний пакет привів до встановлення cookie.
Для загального перегляду дерева:
npm ls --allДля перевірки конкретної версії:
npm ls <назва-пакета>Це важливо, коли аудит повідомляє про вразливість у пакеті, якого немає у вашому package.json. Такий пакет може бути транзитивною залежністю NestJS-модуля, адаптера або інструмента тестування.
Команда npm outdated показує:
current — встановлену версію;
wanted — найновішу версію, дозволену діапазоном у package.json;
latest — останню опубліковану версію.
npm outdatedОновлення до версії, дозволеної поточним діапазоном:
npm updateЦе не завжди оновлює пакет до нового major-релізу. Major-оновлення потребує явної зміни версії та перевірки сумісності.
Оновлення залежності не повинно зводитися до виконання однієї команди. Рекомендований процес:
Переглянути результат npm audit.
Виконати npm explain для уразливого пакета.
Перевірити, чи є новіша версія батьківської залежності.
Оновити один логічний набір пакетів.
Перевірити lock-файл у diff.
Запустити тести, type checking і збірку.
Повторити аудит.
Перевірити основні сценарії застосунку.
Для пакетів NestJS зазвичай важливо оновлювати пов'язані пакети узгоджено. Наприклад, @nestjs/common, @nestjs/core, @nestjs/platform-express та інші @nestjs/* можуть мати вимоги до сумісності версій.
npm audit fixКоманда:
npm audit fixнамагається встановити сумісні виправлені версії та змінити lock-файл.
Команда з --force може встановити несумісні major-версії:
npm audit fix --forceВикористовувати її автоматично в CI або production не слід. Вона може:
змінити публічні API;
оновити NestJS до іншого major-релізу;
спричинити помилки під час запуску;
змінити поведінку middleware або адаптерів;
оновити кілька пакетів одночасно.
Краще розглядати npm audit fix --force як спосіб отримати пропозицію для окремої гілки, після чого вручну перевірити всі зміни.
overridesnpm підтримує поле overrides, яке дозволяє примусово задати версію транзитивної залежності:
{
"overrides": {
"some-transitive-package": "2.4.1"
}
}Це може бути корисно, якщо:
виправлена версія вже опублікована;
батьківський пакет ще не випустив оновлення;
кілька пакетів використовують уразливу версію;
нова версія сумісна з вашим застосунком.
Після додавання overrides потрібно перевірити дерево:
npm install
npm ls some-transitive-package
npm auditoverrides не є універсальним виправленням. Примусова версія може порушити API або peer dependency батьківського пакета. Тому необхідні тести й перевірка runtime-сценаріїв.
Якщо уразлива залежність приходить через @nestjs/some-module, найкраще спочатку оновити сам NestJS-модуль.
overrides — це контрольований тимчасовий виняток, а не заміна нормального оновлення. Для кожного override варто мати:
причину;
посилання на внутрішній issue або запис ризику;
відповідального;
умову видалення override;
дату повторної перевірки.
У навчальному прикладі це може виглядати так:
{
"overrides": {
"some-transitive-package": "2.4.1"
}
}Назву та версію потрібно брати з фактичного звіту npm audit, а не вигадувати наперед.
npm ciУ CI та під час розгортання застосунку використовуйте:
npm ciНе використовуйте npm install для стандартного production-розгортання, якщо lock-файл уже існує. npm install може перерахувати дерево і змінити lock-файл.
Приклад базового процесу:
npm ci
npm audit --audit-level=high
npm run build
npm testПорядок команд можна адаптувати до проєкту, але аудит має виконуватися на тому самому дереві залежностей, яке буде зібране.
Якщо dev-залежності не потрібні в production-контейнері:
npm ci --omit=devЦе зменшує кількість пакетів у production та площу атаки. Однак застосунок має бути зібраний до цього кроку, оскільки TypeScript-компілятор і Nest CLI часто знаходяться в devDependencies.
Типовий підхід:
встановити всі залежності;
виконати npm run build;
у фінальному образі встановити лише production-залежності;
скопіювати зібраний каталог dist.
Пакети можуть виконувати npm-скрипти під час встановлення: preinstall, install, postinstall. Такі скрипти є частиною ланцюга постачання і потенційно можуть виконати довільні команди з правами процесу, який запускає npm.
У контрольованому середовищі можна спочатку встановити залежності без lifecycle-скриптів:
npm ci --ignore-scriptsАле це не універсальна заміна звичайному встановленню. Деякі пакети потребують таких скриптів для:
компіляції native-модулів;
завантаження бінарних файлів;
генерації файлів;
підготовки runtime.
Якщо застосування --ignore-scripts ламає збірку, слід оцінити конкретні скрипти та дозволити лише необхідний процес у контрольованому середовищі.
Нижче наведено мінімальний workflow для GitHub Actions. Він встановлює точні версії з lock-файла, запускає аудит, перевіряє типи, тести та збірку NestJS.
name: Dependency security
on:
pull_request:
push:
branches:
- main
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Отримати код
uses: actions/checkout@v4
- name: Встановити Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Встановити залежності з lock-файла
run: npm ci
- name: Перевірити відомі вразливості
run: npm audit --audit-level=high
- name: Перевірити типи
run: npm run typecheck --if-present
- name: Запустити тести
run: npm test -- --runInBand
- name: Зібрати NestJS-застосунок
run: npm run buildПараметр --if-present дозволяє не завершувати workflow помилкою, якщо в package.json немає скрипта typecheck. Для production-проєкту краще мати такий скрипт явно:
{
"scripts": {
"build": "nest build",
"typecheck": "tsc --noEmit",
"test": "jest"
}
}Тести не замінюють аудит, а аудит не замінює тести. Перший перевіряє відомі проблеми в пакетах, другі — сумісність оновленого дерева із вашим кодом.
Для однакової перевірки перед комітом можна додати npm-скрипти:
{
"scripts": {
"security:dependencies": "npm audit --audit-level=high",
"verify": "npm run security:dependencies && npm run typecheck && npm test -- --runInBand && npm run build"
}
}Тепер перевірка запускається однією командою:
npm run verifyЦе не замінює CI, але дає змогу виявити проблему до створення pull request.
Перед додаванням пакета потрібно оцінити:
чи справді він необхідний;
чи підтримується проєкт;
коли було останнє оновлення;
скільки транзитивних залежностей він додає;
чи має пакет lifecycle-скрипти;
яку ліцензію він використовує;
чи є пакет критичним для runtime або лише для розробки.
Малий пакет не обов'язково безпечніший, але велике дерево залежностей збільшує кількість компонентів, які потрібно контролювати.
Не слід без потреби використовувати залежності з незафіксованими версіями на кшталт:
{
"dependencies": {
"example-package": "*"
}
}Також небажано встановлювати залежності напряму з неперевірених git-репозиторіїв або URL-архівів. Такі джерела складніше відтворити, оновити й перевірити.
Залежності повинні запускатися з мінімально необхідними правами:
не запускати npm від імені root без потреби;
обмежувати доступ production-процесу до файлової системи;
не передавати зайві секрети під час встановлення;
розділяти середовища збірки та runtime;
не встановлювати dev-залежності у фінальний production-образ.
Це не усуває вразливість, але зменшує наслідки компрометації пакета.
Іноді npm audit показує проблему, для якої ще немає доступного виправлення. Не слід бездумно приховувати її або видаляти lock-файл.
Потрібно зафіксувати оцінку:
Який пакет уразливий?
Чи є він production-залежністю?
Який код використовує цей пакет?
Чи доступний уразливий шлях через зовнішній HTTP-запит?
Які дані може прочитати або змінити процес?
Чи можна оновити батьківський пакет?
Чи можна тимчасово вимкнути функціональність?
Які компенсувальні заходи застосовано?
Для тимчасового прийняття ризику можна зберігати запис у внутрішньому security issue, а в CI використовувати вузьке, документоване виключення. Не варто глобально ігнорувати весь аудит.
Перевірка лише пакетів із dependencies не показує повної картини. Вразливий пакет може бути на кілька рівнів нижче в дереві.
Використовуйте:
npm audit
npm explain <назва-пакета>npm audit fix --forceПримусове major-оновлення може створити нові проблеми й навіть порушити запуск NestJS-застосунку. Спочатку потрібно перевірити сумісність і створити окрему зміну.
package-lock.jsonЦе не виправляє вразливість. Воно лише змушує npm повторно розв'язувати залежності, потенційно додаючи непередбачувані версії.
npm install у productionБез узгодженого lock-файла різні розгортання можуть отримати різні дерева залежностей. Для CI та deployment використовуйте npm ci.
Локальний аудит легко пропустити. Перевірка має виконуватися в CI для кожного pull request і для змін у lock-файлі.
Вразливість low може бути непридатною до експлуатації у вашій архітектурі, але це потрібно оцінити, а не приховувати. Поріг CI має бути частиною формальної політики.
Навіть патч-оновлення може змінити поведінку. Після оновлення залежностей потрібно запускати:
npm ci
npm audit
npm run typecheck
npm test
npm run buildДля регулярного контролю залежностей NestJS використовуйте такий цикл:
Комітити package.json і package-lock.json.
Перевіряти залежності командою npm audit.
Для кожної знахідки виконувати npm explain.
Спочатку оновлювати прямий пакет, який приносить уразливість.
За необхідності використовувати overrides із документованою причиною.
Не застосовувати --force без перевірки major-змін.
Використовувати npm ci у CI та deployment.
Запускати аудит, тести, перевірку типів і збірку в CI.
Обмежувати production-набір через npm ci --omit=dev.
Регулярно переглядати старі залежності та тимчасові винятки.
Безпека залежностей NestJS — це постійний процес, а не одноразовий запуск npm audit.
Ключові принципи:
lock-файл забезпечує відтворюваність встановлення;
npm audit виявляє відомі вразливості;
npm explain допомагає знайти джерело транзитивного пакета;
npm ci є базовою командою для CI та deployment;
overrides дає контроль над транзитивними версіями, але потребує тестування;
npm audit fix --force не можна застосовувати без оцінки сумісності;
перевірка залежностей має поєднуватися з тестами та збіркою;
ризики ланцюга постачання зменшуються через мінімальну кількість пакетів, контроль скриптів і найменші привілеї.