Пошук уроків, статей та іншого контенту
Навчимося виявляти вразливості залежностей Node.js, фіксувати версії пакетів і безпечно оновлювати їх.
Залежності Node.js часто містять не лише власний код застосунку, а й десятки транзитивних пакетів. Вразливість може бути не у вашому безпосередньому пакеті, а в пакеті, який він використовує.
Наприклад, застосунок може напряму залежати від express, а express — від інших пакетів. Якщо в одному з них знайдено вразливість, проблема потенційно стосується і вашого застосунку.
Безпека залежностей складається з кількох практик:
регулярне сканування залежностей;
фіксація точного дерева пакетів у lock-файлі;
контрольоване оновлення;
перевірка змін перед встановленням у production;
мінімізація кількості непотрібних залежностей.
npm має вбудовану команду npm audit, яка перевіряє залежності проєкту за відомими базами вразливостей.
Запустіть її в каталозі, де розташований package.json:
npm auditУ результаті npm може показати:
назву вразливого пакета;
рівень серйозності (low, moderate, high, critical);
ланцюжок залежностей, через який пакет потрапив у проєкт;
доступне виправлення;
рекомендацію щодо команди для виправлення.
Наприклад, залежність може бути транзитивною:
your-app
└─┬ some-framework
└── vulnerable-packageУ цьому випадку vulnerable-package не записаний безпосередньо у вашому package.json. Щоб виправити проблему, зазвичай потрібно оновити some-framework.
JSON-формат зручний для CI або власних інструментів аналізу:
npm audit --jsonДля перевірки тільки production-залежностей використовуйте:
npm audit --omit=devЦе корисно перед розгортанням, оскільки dev-залежності зазвичай не встановлюються в production. Проте їх також не варто ігнорувати: вони можуть впливати на середовище розробки, процес збірки або CI.
npm класифікує вразливості за рівнем ризику:
low — низький ризик;
moderate — помірний ризик;
high — високий ризик;
critical — критичний ризик.
Рівень сам по собі не визначає, чи можна безпечно залишити проблему без виправлення. Потрібно врахувати:
чи використовується вразливий код у вашому сценарії;
чи доступний відповідний функціонал ззовні;
чи стосується вразливість production-залежностей;
чи існує виправлена версія;
чи призведе оновлення до несумісних змін.
Не слід автоматично вважати, що кожна знайдена вразливість дає зловмиснику доступ до вашого застосунку. Але кожне повідомлення потрібно перевірити, а рішення — зафіксувати.
Файл package.json описує дозволені версії залежностей. Якщо вказано діапазон:
{
"dependencies": {
"express": "^4.18.0"
}
}символ ^ дозволяє npm встановити сумісніші мінорні та patch-версії в межах major-версії.
Файл package-lock.json зберігає конкретні версії пакетів, URL завантаження та контрольні суми. Він фіксує фактичне дерево залежностей.
Для застосунку lock-файл потрібно:
зберігати в системі контролю версій;
переглядати під час code review;
оновлювати разом із відповідними змінами в package.json;
використовувати в CI та production.
Не додавайте package-lock.json до .gitignore для застосунку. Без нього різні середовища можуть встановити різні версії транзитивних залежностей.
Команда npm install може оновити lock-файл, якщо він не відповідає package.json:
npm installКоманда npm ci призначена для чистого, відтворюваного встановлення:
npm cinpm ci:
очікує наявність lock-файлу;
перевіряє відповідність package.json і package-lock.json;
не оновлює lock-файл;
встановлює саме зафіксовані версії;
зазвичай використовується в CI та production-збірках.
Типовий фрагмент CI-процесу може виглядати так:
npm ci
npm test
npm audit --omit=devЯкщо package.json і package-lock.json не відповідають один одному, npm ci завершиться з помилкою. Це корисна поведінка: вона не дозволяє непомітно змінити дерево залежностей.
Перед оновленням перегляньте застарілі пакети:
npm outdatedЦя команда показує поточну встановлену версію, версію, дозволену діапазоном у package.json, і найновішу доступну версію.
Для автоматичного застосування доступних безпечних виправлень можна використати:
npm audit fixКоманда намагається оновити залежності без порушення заявлених діапазонів версій. Після цього потрібно перевірити зміни:
git diff -- package.json package-lock.json
npm test
npm auditНе використовуйте --force без аналізу:
npm audit fix --forceЦей параметр може дозволити оновлення з major-змінами або змінити залежності так, що застосунок перестане працювати. Безпечніший процес:
створити окрему гілку;
переглянути, які пакети змінюються;
оновити залежності;
запустити тести;
перевірити основні сценарії застосунку;
перевірити результат повторним npm audit.
Якщо відомо, який пакет потрібно оновити, це можна зробити явно:
npm install express@4.21.2Команда оновить package.json і package-lock.json. Після цього перевірте:
npm test
npm auditВерсію потрібно обирати з урахуванням сумісності. Виправлення вразливості не виправдовує автоматичне встановлення major-версії без перевірки змін API.
Команда npm explain показує, чому пакет присутній у дереві залежностей:
npm explain vulnerable-packageВона допомагає відповісти на запитання:
який пакет додав залежність;
чи є вона прямою або транзитивною;
чому npm не може використати іншу версію;
який пакет потрібно оновити.
Також для загального перегляду дерева можна використати:
npm ls --allЯкщо потрібний пакет є прямою залежністю, його можна оновити безпосередньо. Якщо він транзитивний, спочатку варто перевірити, чи існує новіша версія батьківської залежності.
Іноді виправлена версія транзитивного пакета вже існує, але пакет верхнього рівня ще не випустив оновлення. npm підтримує поле overrides, яке дозволяє примусово зафіксувати версію залежності:
{
"dependencies": {
"some-framework": "2.4.0"
},
"overrides": {
"vulnerable-package": "1.8.3"
}
}Після зміни потрібно оновити lock-файл:
npm install
npm audit
npm testoverrides не гарантує сумісності. Нова версія транзитивного пакета може змінити поведінку або мати несумісний API. Використовуйте цей механізм лише після перевірки тестами.
Якщо проблема вирішена оновленням прямої залежності, краще оновити саме її, а не залишати тимчасовий override.
Оновлення залежностей потрібно розглядати як зміну коду. Під час review перевіряйте:
чи дійсно потрібне оновлення;
чи змінився package.json очікуваним чином;
які пакети змінилися в package-lock.json;
чи не з'явилися нові непотрібні прямі залежності;
чи пройшли тести;
чи зникла або зменшилася вразливість;
чи не було неочікуваних major-оновлень.
Для великих оновлень краще розділяти зміни на невеликі pull request-и. Тоді легше зрозуміти причину регресії та повернути окрему зміну.
Сканування має відбуватися не лише локально. Розробник може не помітити проблему або не запустити перевірку перед створенням pull request.
Мінімальний приклад кроків CI:
npm ci
npm test
npm audit --audit-level=high --omit=devПараметр --audit-level=high задає мінімальний рівень, за якого команда вважає перевірку невдалою. У цьому прикладі помилки рівня low і moderate не зупиняють команду, а high і critical — зупиняють.
Поріг потрібно визначати для конкретного проєкту. Надто сувора перевірка може блокувати всі збірки через проблеми, які не впливають на ваш сценарій. Надто м'яка — дозволить розгортання з небезпечними залежностями.
Навіть якщо CI використовує npm audit, результат потрібно аналізувати. Автоматична перевірка не замінює оцінювання впливу вразливості.
Практичний процес роботи із залежностями:
Перевірити репозиторій і переконатися, що lock-файл збережений.
Запустити npm audit.
Для кожної проблеми визначити пряму або транзитивну залежність.
Перевірити доступну виправлену версію.
Оновити пакет без --force, якщо це можливо.
За потреби використати overrides після перевірки сумісності.
Запустити тести та перевірити основні сценарії.
Повторно виконати npm audit.
Переглянути зміни в package.json і package-lock.json.
Додати зміни до pull request разом із поясненням.
Приклад послідовності команд:
git checkout -b security/update-dependencies
npm audit
npm outdated
npm audit fix
npm test
npm audit --omit=dev
git diff -- package.json package-lock.jsonЯкщо npm audit fix не вирішив проблему, не варто одразу запускати --force. Спочатку використайте npm explain, знайдіть пакет, який додає вразливу залежність, і перевірте можливість його окремого оновлення.
Якщо lock-файл не зберігається в репозиторії, однаковий код може працювати з різними версіями залежностей.
Для застосунку використовуйте package-lock.json і встановлюйте залежності в CI через npm ci.
npm audit fix --force--force може встановити несумісну major-версію. Після цього вразливість може зникнути, але застосунок — перестати працювати.
Спочатку перевірте, чи можна застосувати звичайне оновлення, а major-оновлення виконуйте окремо з тестуванням.
package-lock.json — це згенерований файл. Ручне видалення окремих записів може пошкодити дерево залежностей.
Змінюйте package.json, використовуйте npm-команди, а потім перевіряйте результат через npm ci.
Вразливий пакет часто є транзитивним. Перегляд лише dependencies у package.json не показує всього дерева.
Використовуйте:
npm audit
npm explain package-name
npm ls --allnpm audit перевіряє відомі вразливості, але не доводить, що код повністю безпечний. Відсутність результатів означає лише, що npm не знайшов відомих проблем у поточному дереві залежностей.
Масове оновлення може додати несумісні зміни та ускладнити пошук проблеми. Краще оновлювати залежності невеликими групами й запускати тести після кожної зміни.
npm audit знаходить відомі вразливості у прямих і транзитивних залежностях.
package-lock.json фіксує конкретне дерево пакетів і має зберігатися в репозиторії.
npm ci встановлює залежності відтворювано та підходить для CI і production.
npm audit fix може автоматично застосувати сумісні виправлення.
npm audit fix --force потрібно використовувати лише після аналізу major-змін.
npm explain допомагає знайти пакет, який додає вразливу залежність.
overrides може тимчасово зафіксувати безпечнішу транзитивну версію, але її сумісність потрібно перевірити.
Після кожного оновлення залежностей слід запускати тести, повторний аудит і переглядати зміни lock-файлу.