Ланцюг постачання і залежності
Зміст
Найгучніші інциденти останніх років приїхали не через баг у вашому коді. Вони приїхали пакетом, якого ніхто в команді не читав, або оновленням від вендора, якому довіряли за замовчуванням. Поля, куди вставити пейлоад, тут немає: є , реєстр пакетів і дерево залежностей на сотні сторонніх компонентів.
Щодня це стосується вас дуже приземлено: прочитати звіт сканера й вирішити, чи це блокер; зрозуміти, що означає червоний security-крок у пайплайні; прийняти пулреквест від бота. Глава — канонічний виклад теми на сайті: означення й стоять у главі про модель загроз, гігієна секретів у CI — у главі про секрети та конфігурацію в CI, а тут — кут ланцюга постачання.
Збої ланцюга постачання: A03 у OWASP Top 10:2025
(Software Supply Chain Failures) — це поламки чи інші компрометації процесу збирання, дистрибуції або оновлення ПЗ; найчастіша причина — вразливості або зловмисні зміни в сторонньому коді, інструментах та інших залежностях. Категорія A03:2025 розширює стару A06:2021 Vulnerable and Outdated Components на весь екосистемний ланцюг. У самій десятці вона стоїть третьою, а от в опитуванні спільноти очолила список: рівно 50% респондентів поставили її на №1 — і ці два числа плутають постійно.
Пастка тут статистична. З відповідними пов'язано лише 11 CVE, бо такі збої важко виявляти, — але там, де їх таки тестували, категорія дала найвищу середню частоту, 5,19%. Мала кількість записів означає сліпу пляму інструментів, а не безпеку продукту.
Ознаки вразливості — три питання до команди: чи є облік версій усіх компонентів, включно з транзитивними; чи є регулярне сканування й підписка на бюлетені безпеки; чи не захищений пайплайн CI/CD слабше за системи, які він збирає й розгортає. Запобігання дзеркальне: централізовано вести , моніторити CVE, NVD і OSV, брати компоненти лише з офіційних джерел і віддавати перевагу підписаним пакетам.
Сценарії варто знати поіменно: Log4Shell (CVE-2021-44228) — zero-day віддаленого виконання коду в Apache Log4j; Shai-Hulud 2025 року — «перший успішний самопоширюваний npm-хробак». А SolarWinds показує, чому дату треба атрибутувати: сторінка A03 пише «The 2019 SolarWinds compromise that led to ~18,000 organizations being compromised», шпаргалка Software Supply Chain Security — «The 2020 SolarWinds and 2021 Codecov incidents are excellent real-world examples of this». Обидва джерела канонічні й розходяться, тож рік називають разом із джерелом, а не як усталений факт.
Поверхом нижче стоїть A08:2025 Software or Data Integrity Failures: код та інфраструктура, які не захищають від того, щоб невалідні або недовірені код чи дані трактувалися як довірені й валідні, — і саме на нижчому рівні, ніж A03. Контроль симетричний: або рівноцінні механізми, що підтверджують й незміненість.
Де саме рветься ланцюг
Ланцюг постачання ПЗ шпаргалка OWASP означує цитатою з NIST: «a collection of steps that create, transform, and assess the quality and policy conformance of software artifacts». Рветься він по найслабшій ланці: дефект в одному компоненті — вразливій сторонній залежності або хибно налаштованому VCS — ставить під весь ланцюг. Категорій загроз чотири, і залежності лише одна з них.
Вектори названо поіменно: , компрометація інфраструктури постачальника, крадіжка сертифікатів підпису коду, експлуатація CI/CD. Два контролі — прямо про QA. Постачальника оцінюють до інтеграції, і серед питань стоять «чи проєкт активно підтримують» і «чи має він достатнє тестове покриття з правилами, релевантними безпеці». Рев'ю коду до злиття шукає не лише випадкові дефекти, а навмисно закладений шкідливий код. Решта інженерна: збірки ізольовані й одноразові, компоненти беруть тільки цифрово підписані, а фінальний бінарник теж скануть — «one should not simply assume that the final result is secure».
Термін шпаргалка цитує зі SLSA 1.0: «the verifiable information about software artifacts describing where, when and how something was produced»; генерує його платформа збірки, а не локальна машина розробника.
Вразлива залежність: від знахідки до рішення
Проєкти делегують стороннім бібліотекам цілі класи операцій, і плата одна: «the security posture of the real application is now resting on it». (vulnerable dependency) визначається через «відому вразливість», а та має точне формулювання: «a "known vulnerability" is a publicly reported flaw in a package».
Звідси сліпа пляма. За дослідник і постачальник разом випускають CVE; за CVE не завжди створюють, не завжди потрапляє в глобальну базу — і інструменти виявлення потенційно сліпі. Чистий звіт сканера доводить відсутність записів у базах, а не відсутність вразливостей.
Далі — процедура, названа джерелом не срібною кулею, а основою під адаптацію. Чотири випадки:
- Фікс існує. Оновлюють спокійно лише за наявності автоматизованих тестів — модульних, інтеграційних, функціональних або security — на функції, що використовують зачеплену залежність. Тести тут лагодження; критерій випуску — «all tests succeed».
- Фікса ще немає. Ігнор має покривати тільки цей CVE: пакет буває зачеплений кількома вразливостями, у кожної свій номер.
- Саморобний патч. Потрібен , який імітує вразливість: він доводить дієвість патча і стежить, щоб той не зник пізніше.
- Постачальник ще не знає — знахідка з допису про повне розкриття або з пентесту, і CVE немає.
Дві межі знімають половину суперечок. Транзитивну вразливість лікують на прямій залежності, бо втручання в транзитивну часто б'є по стабільності. А рішення «приймаємо ризик» ухвалює Chief Risk Officer на основі технічного висновку команди й показників CVSS, а не сама команда. І пара, яку постійно змішують: вразлива залежність не дорівнює зловмисній.
SCA: що роблять npm audit і pip-audit — і чого не роблять
Спершу чесно про термін. (Software Composition Analysis) канон розкриває як абревіатуру й називає місце в пайплайні, але означення не дає жодне джерело: у словнику ISTQB software composition analysis має нуль входжень. Місце в процесі описує : , і SCA або інструменти відстеження залежностей запускають під час стандартних автоматизованих релізних або за регулярним розкладом.
npm auditнадсилає опис залежностей у реєстр і просить звіт про відомі вразливості. Технічно це мережева операція: npm формує JSON з іменами й версіями кожного пакета в дереві й робитьPOSTу реєстр. Довгі ланцюжки у звіті звуться (meta-vulnerability) — залежність вразлива через залежність від вразливих версій вразливого пакета, і частину таких знахідок не можна полагодити автоматично.pip-auditсканує Python-оточення на пакети з відомими вразливостями; джерело даних назване поіменно — Python Packaging Advisory Database через PyPI JSON API.- Відтворюваність:
npm auditвимагає ; із--no-package-lockдерево перебудовується щоразу й результати можуть відрізнятися на кожному прогоні.
Контракт із CI — місце, де читання ламається найчастіше. За замовчуванням npm audit падає на будь-якій знахідці, а --audit-level цю межу зсуває: він задає мінімальний рівень, з якого команда завершується ненульовим кодом (шкала значень — null, info, low, moderate, high, critical, none). Пастку називає саме джерело: не фільтрує звіт, а лише зсуває поріг фейлу, тож зелений ран із --audit-level=high не означає, що середніх знахідок немає. У pip-audit шкала простіша (0 і 1), зате код виходу не пригнічується.
Найнеочевидніше: ненульовий код виходу не доводить нової вразливості. Коли політика свіжості релізу (min-release-age, before) не дає npm audit fix поставити пропатчену версію, npm лишає пакет на вразливій версії, попереджає, що фікс заблоковано, і виходить із ненульовим кодом. Тобто червоний крок означає три різні речі: «вразливість без фікса», «фікс є, але його блокує наша ж політика» і «поріг налаштований не так, як думає команда» — про побудову придатного гейта далі йдеться в главі про автоматизацію.
Чого SCA не робить: це не коду («it analyzes dependency trees, not code»), не захист від зловмисних пакетів і не гарантія повноти бази; фіди «not immune to extraneous or spam reports», тож шум гасять точково, за ID звіту. І в пайплайні це одна з п'яти дій: перевірити підписи або provenance, валідувати SBOM, прогнати SCA і статичний аналіз, ставити залежності строго за локом.
Lock-файл, скрипти встановлення й пакет, якого ви не замовляли
Механіка package.json і команд пакетного менеджера живе в главі про модулі та npm; тут lock-файл цікавить рівно як контроль безпеки. Дає він детерміновані інсталяції й вимушену узгодженість очікувань у команді, а шпаргалка ланцюга постачання формулює це як захід: щоб зменшити шанс непомітно підтягнути скомпрометовану чи вразливу версію, залежності обмежують конкретною версією, раніше перевіреною як легітимна й безпечна.
Сам по собі файл не гарантує нічого. Виявивши розбіжність між package.json і локом, і npm, і Yarn компенсують її за маніфестом — ставлять інші версії, ніж записані в локу, і «весь сенс lock-файла зводиться нанівець». Контролем він стає, лише коли дотримання вимагають: тоді будь-яка розбіжність перериває встановлення — yarn install --frozen-lockfile або npm ci. Прапорці встановлення теж версіонують: рекомендація — закомітити .npmrc, щоб вони проходили рев'ю разом із локом. Шпаргалка CI/CD Security додає другу половину контролю — пін версії і звірку цілісності завантаженого пакета з відомим добрим хешем; обидві дії, каже вона, можна виконати «via a platform's "lock" or similar file», а далі окремим реченням: «Remember to enforce the lockfile».
Друга поверхня — момент встановлення. npm будується на скриптах, які пакет оголошує й які виконуються в конкретних точках його інсталяції, тож зловмисники «run any arbitrary command when their package is installed». Важіль --ignore-scripts має названу межу: команди, чиє призначення — запустити скрипт (npm test, npm start), свій скрипт усе одно виконають. Дрібниця з наслідком для аудиту: --omit не звужує лок — залежності далі резолвляться й потрапляють у package-lock.json, просто не лягають на диск.
Третя поверхня — ім'я пакета: не пакет, а процес його розв'язання. Шпаргалка CI/CD Security означує це як dependency chain abuse і називає три способи — плутанина залежностей, і захоплення акаунта супровідника.
- Тайпосквотинг (typosquatting) — шкідливі модулі під іменами, дуже схожими на популярні. Ім'я пакета в пулреквесті читають так само уважно, як URL у фішинговому листі.
- Плутанина залежностей (dependency confusion) — пакет у публічному реєстрі з іменем вашого внутрішнього приватного, але з вищим номером версії. Захист названо: імена зі (
@yourorg/package-name). - (slopsquatting) — вектор, що експлуатує AI-асистентів: модель галюцинує неіснуючу назву пакета, а зловмисник публікує під нею шкідливий. Перевірка з джерела пряма:
npm view <package-name>перед встановленням будь-якого запропонованого AI пакета. Ширша тема AI-коду належить главі про безпеку й приватність при роботі з AI.
Плагіни самої CI-платформи — той самий клас: вони збільшують .
SBOM і граф залежностей: інвентар складу й шлях компонента
SBOM (Software Bill of Materials) — машиночитний інвентар компонентів; (dependency graph) — орієнтований граф, що показує відношення між ними. SBOM відповідає «що всередині», граф — «чому воно всередині». Мінімальний склад названо точно: ім'я й версія в канонізованій формі, унікальний ідентифікатор пакета (purl), контрольна сума (бажано SHA256) і ребра зв'язків — пряма проти транзитивної залежності. Генерують SBOM під час збірки, а не руками, у точно названий момент — після розв'язання залежностей і перед пакуванням; форматів два стандартних, SPDX і CycloneDX, і на реліз потрібен щонайменше один машиночитний SBOM, підписаний: «unsigned SBOMs can be forged».
Поруч живе (Vulnerability Exploitability eXchange) — машиночитний документ про те, чи відома вразливість реально зачіпає конкретний продукт і за яких умов. Це аргумент із джерелом замість «ми вирішили, що не страшно».
Останній крок і є перевіркою фікса, придатною для QA: не «PR змерджено», а «компонента більше немає у складі».
Граф на платформі GitHub будується з маніфестів і lock-файлів репозиторія, тож він рівно настільки повний, наскільки повні ці файли, і «порожній граф» є дефектом вхідних даних. По кожній залежності видно версію, ліцензію, файл-джерело й наявність відомих вразливостей, а для транзитивних — шлях, яким залежність потрапила у проєкт. Політика CI названа окремо: валити пайплайн, якщо впала генерація SBOM, але не валити на низькопріоритетних знахідках, які не потребують дії — їх виносять на дашборд .
Dependabot: три різні речі під одним іменем
У розмові це злипається в одне, а насправді механізмів три, і плутанина коштує пріоритетів.
- Алерти. Тригерів два: нова вразливість у GitHub Advisory Database або зміна графа залежностей; скан іде по гілці за замовчуванням. В алерті — зачеплений файл, опис із severity і версія з фіксом, якщо така є, а сам алерт призначають на людину, команду або агента, щоб він не завис нічий.
- Security-апдейти — автоматичні пулреквести на залежності з відомими вразливостями.
- Version-апдейти — пулреквести, що тримають залежності свіжими, навіть коли вразливостей немає: гігієна, а не реакція на інцидент. Вмикаються окремо, файлом
dependabot.yml, і за замовчуванням чекають 3 дні від релізу версії; на security-апдейти це не поширюється. CI-пайплайн теж вважається залежністю: звіряються рефи екшенів і перевикористовуваних воркфлоу.
Роль людини названа дослівно: «check that your tests pass, review the changelog and release notes ... and then merge it». Автомердж за самим зеленим чекбоксом цієї вимоги не виконує.
Межі, через які «алертів немає» не дорівнює «вразливостей немає»: алерти не ловлять кожної проблеми, база має , тригерять лише перевірені GitHub advisory, архівні репозиторії не скануються взагалі, а для GitHub Actions алерти генеруються лише для екшенів на семантичному версіюванні, не на SHA. Плюс: при увімкненні приходять сповіщення лише про нові знахідки, а якщо пулреквести ігнорують, сам ставить оновлення на паузу.
І пастка, яку ловлять на прогоні: секрети недоступні воркфлоу, які тригернули події Dependabot, а незаданий секрет повертає порожній рядок. Тест, що мовчки пішов без авторизації, дасть не помилку конфігурації, а дивний функціональний фейл.
CVE і CVSS: пріоритизація без ілюзій
Означення обох стоять у главі 1; тут — лише те, що змінює рішення про пріоритет.
- Кількість CVE не міряє захищеність. У A03 — 11 CVE при найвищій середній частоті 5,19%, а
A09:2025названо «неймовірно важкою для тестування» з лише 723 CVE. - Публічний бал — це Base чужого середовища. Постачальники оцінок, зокрема NVD, зазвичай дають лише Base Scores, позначені як CVSS-B; сама база підтверджує це переліком того, чого вона не оцінює: Environmental не рахує взагалі, натомість дає калькулятор.
- Бал — не ризик. NVD формулює прямо: «CVSS не є мірою ризику»; це фактор пріоритизації, а не готовий пріоритет.
- 10.0 інколи означає «даних немає»: коли вендор оголосив вразливість без деталей, NVD виставляє найгірший сценарій.
- Окремий запис не доводить дефекту в контексті. README навмисно вразливого стенда фіксує випадок, коли для однієї з його вразливостей завели
CVE-2023-39848. - Запис має бути придатним до дії. Мінімальний склад : резюме з описом впливу, перелік вразливих версій, перелік версій із патчем і CVE; запитати CVE — обов'язок організації, що приймає звіт.
Разом це симетрична пара: число записів не міряє захищеність, а окремий запис не доводить дефекту.
Секрети в репозиторії очима ланцюга постачання
Гігієна секретів у CI — тема розділу про Git і CI. Тут інший кут: секрет у репозиторії є самостійною поверхнею атаки, а пайплайн — ланкою ланцюга. Коли креденшели на кшталт API-ключів і паролів захардкодженими, вони стають цілями.
(secret scanning) проходить усю історію git на всіх гілках, а не робочу копію, — тож «прибрав наступним комітом» не є лагодженням. Явище має назву: , неконтрольоване розповзання креденшелів по репозиторіях. Поверхня скану ширша за код — тікети, заголовки й коментарі пулреквестів, вікі, секретні gist-и. Для QA це буквально про токен, вставлений у баг-репорт «щоб відтворити».
Порядок дій після знахідки контрінтуїтивний: ротувати зачеплений креденшел негайно, а секрету з історії — «time-intensive and often unnecessary», якщо ключ уже відкликано. Пріоритет визначають перевірки валідності: сервіс-емітент відповідає, чи ключ ще діє. Окрема межа: для партнерських провайдерів платформа повідомляє провайдера напряму, і такі знахідки в алертах репозиторія не показуються взагалі. Ловити краще раніше за коміт — в IDE або pre-commit-хуком, пам'ятаючи компроміс: секрети зі сталим форматом ловляться легше, а пароль, придуманий людиною, правила під формат пропускають.
Другий канал витоку — публікація пакета: якщо існують обидва ignore-файли, у реєстр їде все, чого немає в .npmignore. Надійніший механізм — поле files у package.json, яке працює як .
І кут, заради якого секрети стоять тут: атаки на ланцюг постачання дедалі частіше цілять саме в білд-артефакти, реєстри й CI-креденшели. Звідси правило трактувати пайплайн як прод — та сама ознака, яку A03 називає дефектом.
Сторонній JavaScript: ланка поза вашою збіркою
Аналітика, чат-віджети, теги маркетингу — це залежності, яких немає в lock-файлі. Браузер звертається до серверів третьої сторони напряму, а версію коду визначає вендор у момент запиту. Головний ризик — компрометація сервера і впорскування шкідливого коду в оригінальний тег; розкладається він на втрату контролю над змінами, виконання довільного коду в клієнта і витік даних третім сторонам.
- приїжджає без вашого релізу: щойно файли змінилися у вендора, наступний виклик із будь-якого браузера отримає вже змінений JavaScript.
- Привілеї — ті самі, що в користувача: сторонній код рідко рев'ювають перед інтеграцією, а виконується він «similar to XSS attacks» (XSS).
- Передрелізна перевірка частково втрачає силу — джерело каже це прямо, включно з IAST, RAST, SAST і DAST.
Технічний контроль — (SRI): розробник рахує метадані цілісності для скрипта вендора й додає їх до елемента script, і тоді виконується лише перевірений код. Дві умови, про які забувають: SRI працює, якщо на хості вендора ввімкнено CORS, а після оновлення у вендора сторінка залишиться «secure but not working» — очікуваний наслідок механізму, а не загадка. Жорсткіша ізоляція — винести скрипт в iframe з іншого домену, який працює як «jail». Обмеження джерел скриптів політикою — тема CSP і заголовків безпеки.
Найдешевша автоматична перевірка — наявність атрибута integrity на зовнішніх скриптах:
test('усі зовнішні скрипти мають integrity', async ({ page }) => {
await page.goto('/');
const unprotected = await page.$$eval('script[src]', (nodes) =>
nodes
.map((n) => ({ src: (n as HTMLScriptElement).src, sri: n.getAttribute('integrity') }))
.filter((s) => new URL(s.src).origin !== location.origin && !s.sri)
.map((s) => s.src),
);
expect(unprotected).toEqual([]);
});
Поруч варто мати живий інвентар: які сторонні скрипти реально виконуються на проді й із яких доменів. Такого списку в lock-файлі немає — а домен покинутого вендора зловмисник може перереєструвати на себе.
Типові помилки
- Виглядає як «сканер зелений — залежності чисті», а насправді за повного розкриття CVE може не існувати взагалі, і сканер, що живиться лише базою CVE, тут сліпий.
- Виглядає як «поставили
--audit-level=high, середніх знахідок немає», а насправді прапорець не фільтрує звіт, а лише зсуває поріг фейлу. - Виглядає як «червоний аудит — нова вразливість», а насправді ненульовий код виходу буває й тоді, коли фікс існує, але його заблокувала політика свіжості релізу.
- Виглядає як «lock-файл є, версії зафіксовані», а насправді при розбіжності з маніфестом менеджер мовчки поставить інші версії, доки дотримання лока не форсують.
- Виглядає як «ми ще нічого не запускали, лише поставили залежності», а насправді скрипти пакета виконуються під час самої інсталяції.
- Виглядає як «алертів немає — усе гаразд», а насправді архівні репозиторії не скануються, база має затримку, а партнерські знахідки в алерти не потрапляють.
- Виглядає як «секрет прибрано наступним комітом», а насправді скан іде по всій історії всіх гілок, і ключ лишається живим до відкликання.
- Виглядає як «у бюлетені 9.8 — критично для нас», а насправді це Base-бал чужого середовища, а 10.0 інколи означає, що деталей просто немає.
Підсумок
- Ланцюг рветься в чотирьох місцях: вихідний код, середовище збірки, залежності, розгортання. Пайплайн, захищений слабше за системи, які він збирає, — уже дефект за OWASP.
- Автотести на функції, що використовують залежність, — передумова оновлення, а не звіт після нього. Саморобний патч без тесту, що імітує вразливість, — незавершена робота.
- Lock-файл стає контролем лише тоді, коли його дотримання форсують (
npm ci,--frozen-lockfile). - Червоний security-крок читають за причиною, а не за кольором: «вразливість без фікса», «фікс заблокувала політика» і «поріг налаштований інакше» дають однаковий ненульовий код виходу.
- Порожній звіт не доводить нічого — ні у сканера, ні у вкладці алертів, ні в кількості CVE. Доказом виправлення є перегенерований SBOM, у якому вразливого компонента більше немає.
Можливі питання
- Що таке Software Supply Chain Failures і чому про них так багато говорять у 2025? Чекають означення через процес збирання, дистрибуції й оновлення; сильна відповідь розводить два числа —
A03у самій десятці й №1 в опитуванні спільноти — і додає парадокс 11 CVE при найвищій частоті. - Чим
npm auditвідрізняється від Dependabot? Чи розводить кандидат разовий аналіз дерева й механізм платформи з алертами та двома типами пулреквестів. - Знайшли CVE з балом 9.8 у транзитивній залежності. Ваші дії? Дивляться на порядок: зіставити з компонентом, перевірити, чи зачіпає нас, лікувати на прямій залежності, підтвердити перегенерованим SBOM.
- Навіщо в CI
npm ci, якщо єnpm install? Перевіряють кут безпеки, а не швидкість: розбіжність лока й маніфесту має перервати встановлення, а не мовчки залатати іншими версіями. - Що таке тайпосквотинг і плутанина залежностей? Дві різні механіки — схоже ім'я проти вищої версії під іменем внутрішнього пакета — і названий захист скоупом.
- Секрет випадково закомітили. Що робити першим? Перша дія — ротація й відкликання, а не переписування історії: скан бачить усю історію, а відкликаний ключ робить чистку здебільшого зайвою.
- Чи покриває SCA ризик стороннього JavaScript? Перевіряють межу: аналітичний скрипт не проходить через збірку, у lock-файлі його немає, і контроль тут інший — інвентар доменів, SRI та ізоляція.
Джерела
Збої ланцюга постачання: A03 у OWASP Top 10:2025
- OWASP Top 10:2025 — A03 Software Supply Chain Failures — означення, 50% в опитуванні, 11 CVE проти частоти 5,19%, ознаки й заходи, Log4Shell, Shai-Hulud, SolarWinds 2019.
- OWASP Top 10:2025 — Introduction — A03 як розширення A06:2021 на весь ланцюг.
- OWASP Cheat Sheet — Software Supply Chain Security — друге датування того самого інциденту: SolarWinds 2020 і Codecov 2021.
- OWASP Top 10:2025 — Software or Data Integrity Failures — A08 як нижчий рівень щодо A03, підписи, заборона непідписаних серіалізованих даних.
- OWASP Cheat Sheet — Software Supply Chain Security — означення за NIST, найслабша ланка, чотири категорії загроз і вектори, оцінка постачальника, рев'ю коду, ізольовані збірки, підписи, provenance за SLSA 1.0.
Вразлива залежність: від знахідки до рішення
- OWASP Cheat Sheet — Vulnerable Dependency Management — делегування класів операцій, два способи розкриття, чотири випадки, ігнор на конкретний CVE, тест під саморобний патч, фікс на прямій залежності, роль CRO.
- pypa/pip-audit — README — «відома вразливість» як публічно повідомлений дефект; інструмент не захищає від зловмисних пакетів.
- OWASP Cheat Sheet — Software Supply Chain Security — інвентаризація як перший крок і моніторинг залежностей після .
- GitHub Docs — Dependabot alerts — залежність із відомою вразливістю робить проєкт ціллю.
SCA: що роблять npm audit і pip-audit — і чого не роблять
- OWASP DevSecOps Guideline — розкриття абревіатури SCA у переліку перевірок пайплайна поруч із SAST, IAST і DAST.
- ISTQB Glossary —
software composition analysisу словнику відсутній: означення терміна канон не дає. - OWASP Web Security Testing Guide v4.2 (PDF, 465 стор.) — DAST, SAST і SCA під час автоматизованих релізних воркфлоу або за розкладом.
- npm CLI — npm-audit — що надсилається в реєстр, мета-вразливість, межі
npm audit fix, залежність від lock-файла, коди виходу й поведінка--audit-level. - npm CLI — config (--audit-level) — шкала значень порогу й ненульовий код виходу, коли політика свіжості блокує фікс.
- pypa/pip-audit — README — що сканує інструмент і звідки бере дані, коди виходу, межа «не аналізатор коду», шум фідів і точковий ігнор.
- OWASP Cheat Sheet — Vulnerable Dependency Management — сканер, що живиться лише базою CVE, сліпий за повного розкриття.
- OWASP Cheat Sheet — NPM Security — п'ять дій перевірки пакета в CI, серед яких SCA лише одна.
Lock-файл, скрипти встановлення й пакет, якого ви не замовляли
- OWASP Cheat Sheet — NPM Security — що дає lock-файл, тиха компенсація за маніфестом і спосіб вимагати дотримання, скрипти встановлення, тайпосквотинг, плутанина залежностей, слопсквотинг і перевірка
npm view. - OWASP Cheat Sheet — Software Supply Chain Security — пінінг версій як захід проти непомітного підтягування скомпрометованої версії.
- OWASP Cheat Sheet — CI/CD Security —
dependency chain abuseі три його способи, пін версії плюс звірка хеша, вимога форсувати lock-файл, плагіни як поверхня атаки. - npm Docs — npm ci — обов'язковість lock-файла й вихід з помилкою при розбіжності, коміт
.npmrc, межаignore-scripts, поведінка--omit, аудит-звіти під час установки.
SBOM і граф залежностей: інвентар складу й шлях компонента
- OWASP Cheat Sheet — Dependency Graph SBOM — означення SBOM, графа й VEX, мінімальний склад, момент генерації і формати, підпис, чотири кроки тріажу, політика fail-fast проти warn.
- GitHub Docs — Dependency graph — з чого будується граф, що видно по залежності, шлях транзитивної, dependency review у пулреквесті, експорт SBOM.
- OWASP Cheat Sheet — Software Supply Chain Security — автоматизація виробництва й споживання SBOM як частина CI/CD організації.
- OWASP Cheat Sheet — NPM Security — SBOM разом із provenance, щоб простежити, що зібрано і звідки прийшли входи.
Dependabot: три різні речі під одним іменем
- GitHub Docs — Dependabot alerts — тригери й склад алерту, власник, межі: затримка бази, лише перевірені advisory, архівні репозиторії, SHA-закріплені екшени.
- GitHub Docs — Dependabot version updates — різниця security- і version-апдейтів,
dependabot.yml, cooldown 3 дні, екшени як залежності, роль людини, автопауза. - GitHub Docs — Using secrets in GitHub Actions — секрети недоступні воркфлоу від подій Dependabot; незаданий секрет повертає порожній рядок.
- GitHub Docs — Dependency graph — граф як джерело даних для алертів.
CVE і CVSS: пріоритизація без ілюзій
- OWASP Top 10:2025 — A03 Software Supply Chain Failures — 11 CVE проти найвищої середньої частоти 5,19%.
- OWASP Top 10:2025 — A09 Security Logging and Alerting Failures — «неймовірно важко тестувати» й лише 723 CVE.
- FIRST — CVSS v4.0 Specification Document — публічні бази публікують лише Base Scores, позначені як CVSS-B.
- NVD — Vulnerability Metrics (CVSS) — «CVSS не є мірою ризику», бал як фактор пріоритизації, оцінювати Environmental, 10.0 за найгіршим сценарієм.
- OWASP — Vulnerability Disclosure Cheat Sheet — мінімальний склад security advisory й обов'язок організації запитувати CVE.
- digininja/DVWA — README — випадок із
CVE-2023-39848на вразливість навчального стенда. - OWASP Cheat Sheet — Vulnerable Dependency Management — рішення прийняти ризик ухвалює CRO на основі висновку команди й CVSS.
Секрети в репозиторії очима ланцюга постачання
- GitHub Docs — Secret scanning — креденшел як ціль, скан усієї історії на всіх гілках, secret sprawl, поверхня поза кодом, «ротувати негайно, історію потім», перевірки валідності, партнерські знахідки поза алертами.
- OWASP Cheat Sheet Series — Secrets Management — детект в IDE і pre-commit-хуком, компроміс правил під сталий формат, CI/CD як продове середовище.
- OWASP Cheat Sheet — NPM Security — витік через ignore-файли при публікації, поле
filesяк allow-list, атаки на артефакти, реєстри й CI-креденшели. - OWASP Top 10:2025 — A03 Software Supply Chain Failures — слабше захищений пайплайн як ознака вразливості категорії.
Сторонній JavaScript: ланка поза вашою збіркою
- OWASP Cheat Sheet — Third Party Javascript Management — прямий контакт браузера з вендором, головний ризик і три його форми, миттєве доїжджання змін, привілеї рівня користувача, втрата валідності передрелізних перевірок, SRI і вимога CORS, «secure but not working», iframe як jail, перереєстрований домен.
Що таке збої ланцюга постачання ПЗ і навіщо під них завели окрему категорію в OWASP Top 10:2025?
Це компрометація або поломка будь-якого етапу, на якому продукт збирають, розповсюджують чи оновлюють. Джерелом найчастіше стає чужий код — бібліотека, інструмент збірки, плагін , — тобто те, чого ваша команда не писала й переважно не читала. Попередня редакція десятки мала вужчий кошик A06:2021 Vulnerable and Outdated Components, і дивився він здебільшого на самі компоненти та їхні версії. Категорія A03:2025 Software Supply Chain Failures розтягує той самий погляд на всю екосистему навколо коду: реєстри пакетів, середовище збірки, канал доставки оновлень. Наслідок для тестувальника дуже приземлений — «чи оновлені у нас бібліотеки» більше не є повною відповіддю. Поруч стоять питання «звідки ми їх тягнемо», «хто здатен підмінити артефакт уже після збірки» і «чи захищений наш пайплайн бодай так само, як прод».
Кандидат каже: «Supply chain — це номер один в OWASP Top 10:2025». Він має рацію?
Половинчасто, і сильна відповідь одразу розводить два різні числа. У фінальному рейтингу десятки категорія стоїть третьою. Перше місце вона посіла в паралельному опитуванні спільноти, де рівно половина респондентів поставила її на вершину. Тобто «№1» — це не позиція в списку, а результат голосування практиків, і саме на цій підміні найчастіше плутаються. Різниця не косметична: рейтинг говорить про те, що показали дані, а голосування — про те, чого команди бояться найбільше. Якщо в розмові з менеджером обґрунтовувати бюджет на цитатою «перше місце в OWASP», варто бути готовим показати, звідки взялося це перше місце.
У категорії A03 з відповідними CWE пов'язано лише 11 CVE. Виходить, проблема рідкісна?
Навпаки — це підручниковий приклад того, як мала кількість записів вводить в оману. тут мало, бо такі збої важко виявляти: компрометацію реєстру або середовища збірки типовий інструмент просто не бачить. Зате там, де категорію таки перевіряли, вона показала найвищу серед усієї десятки середню частоту спрацювання — 5,19%. Рідкісні не інциденти, рідкісні записи про них, і мала кількість CVE тут вимірює сліпоту інструментів, а не захищеність продукту. Той самий ефект видно в A09:2025, яку канон називає надзвичайно важкою для тестування — при всього 723 CVE. Для QA це прямо перекладається в правило: не будувати пріоритети на кількості знайдених записів, бо порожньо там, куди не дістає автоматика.
Чим A03 відрізняється від A08 Software or Data Integrity Failures?
A03 — про сам ланцюг: як ПЗ збирають, доставляють і оновлюють, і що в цьому процесі можна зламати. A08 — про наслідок довіри: код та інфраструктура не боронять від того, щоб недовірені або невалідні код чи дані потрапили в обіг як довірені й валідні. Канон розводить їх за рівнем: A08 стоїть нижче, ніж A03, тобто описує вужчий і базовіший клас поломок. Контроль в A08 названо прямо — або рівноцінний механізм, що підтверджує артефакта та його незміненість. На практиці межа зручна для баг-репорта: «ми тягнемо пакети з неофіційного дзеркала» — це розмова про ланцюг, а «ми ставимо непідписаний артефакт і ніде не звіряємо його хеш» — розмова про цілісність.
Сканер відпрацював і нічого не знайшов. Чи можна писати у звіті «залежності чисті»?
Ні, і це одна з тих фраз, за які на співбесіді чіпляються навмисно. Почати відповідь варто з означення: визначається через відому , а відома вразливість — це публічно повідомлений дефект у пакеті. Тобто інструмент порівнює ваше дерево із базою публічних записів, а не аналізує код бібліотеки, і порожній звіт доводить рівно одне: збігів у цих базах немає. Далі спрацьовує різниця у способах розкриття. За дослідник і постачальник узгоджують публікацію й разом випускають CVE; за про дефект можуть просто написати в блозі — CVE не створюють, у глобальну базу запис не потрапляє, і засіб виявлення тут потенційно сліпий. Поза звітом лишаються ще свіжі знахідки, які база не встигла проіндексувати, зловмисні пакети — від них аналіз дерева залежностей не боронить у принципі, — і , що приїжджає в браузер повз вашу збірку. Коректне формулювання у звіті — «збігів із базою відомих вразливостей не знайдено», із зазначенням інструмента й дати прогону, бо завтра та сама команда може дати інший результат без жодної зміни коду.
Що таке SCA і чого вона принципово не робить?
SCA (Software Composition Analysis) — клас інструментів, які розбирають склад продукту: які сторонні компоненти в ньому є, яких вони версій і чи не значаться ці версії у базах вразливостей. Тут варто бути чесним із термінологією: канон розкриває абревіатуру й називає місце SCA в пайплайні поруч із , та IAST, але окремого означення не дає жодне джерело — у словнику ISTQB терміна немає взагалі. Запускають такі перевірки або в межах автоматизованих релізних , або за регулярним розкладом. Меж три, і всі три люблять питати. SCA — не коду: вона працює з деревом залежностей, а не з тілом функцій. Вона не є захистом від зловмисних пакетів: свіжозалитий шкідливий пакет у базі відомих вразливостей ще не значиться. І вона не гарантує повноти бази, а самі фіди не застраховані від сміттєвих звітів — тому шум гасять точково, за ідентифікатором конкретного звіту, а не глобальним прапорцем «ігнорувати все».
Як npm audit технічно дізнається про вразливості й що таке мета-вразливість?
Це не локальний аналіз, а мережева операція. npm збирає опис дерева — імена й версії кожного встановленого пакета — пакує його в JSON і робить POST у реєстр, а звідти отримує звіт про відомі вразливості. Розуміння цього одразу знімає кілька питань: чому команда не працює без мережі, чому в закритому контурі потрібне дзеркало реєстру й чому опис вашого дерева залежностей залишає периметр. Довгі ланцюжки у звіті звуться (meta-vulnerability): пакет позначено вразливим не сам по собі, а через те, що він залежить від вразливих версій іншого пакета. Частина таких знахідок автоматичному лагодженню не піддається — оновлення довелося б протягнути через кілька рівнів чужих релізів. Ще одна деталь про відтворюваність: без дерево перебудовується щоразу, тож два прогони поспіль можуть дати різні результати.
Що робить прапорець --audit-level=high і в чому тут пастка?
Він зсуває поріг, з якого команда завершується ненульовим кодом: за замовчуванням npm audit падає на будь-якій знахідці, а з порогом — лише починаючи з названого рівня (шкала — info, low, moderate, high, critical, плюс службові null і none). Пастка в тому, що не фільтрує звіт: знахідки нижче порогу нікуди не діваються, вони так само в ньому є, просто більше не валять крок. Тобто зелений пайплайн із --audit-level=high не є твердженням «середніх знахідок немає» — він означає «ми домовилися не зупинятися на середніх». Різниця стає критичною, коли команда через півроку читає історію ранів як доказ чистоти. Практичне рішення — тримати поріг для гейта окремо від повного звіту, який публікують артефактом , щоб було видно і рішення, і те, що під ним сховано.
Пайплайн упав на кроці аудиту. Це вже означає, що з'явилася нова вразливість?
Ні, і це найкорисніша річ, яку тут можна знати. Ненульовий код виходу означає щонайменше три різні ситуації, і вигляд у них однаковий. Перша — справді нова знахідка, для якої фікса ще немає. Друга — фікс існує, але поставити його не дає власна політика свіжості релізу: npm лишає пакет на вразливій версії, попереджає, що виправлення заблоковано, і все одно виходить із ненульовим кодом. Третя — поріг налаштований не так, як думає команда, і крок падає на рівні, який усі вважали прийнятним. Тому червоний крок читають за причиною, а не за кольором: спершу дивляться, які саме записи в звіті нові порівняно з попереднім прогоном, і лише потім вирішують, блокер це чи тікет у .
У пайплайні стоїть npm install. Чому ревʼювер просить замінити його на npm ci?
Питання здається про швидкість, а перевіряють у ньому безпековий кут. Lock-файл сам по собі версій не гарантує: виявивши розбіжність між package.json і локом, і npm, і Yarn мовчки компенсують її за маніфестом — тобто поставлять інші версії, ніж ті, що записані в локу, і весь сенс файлу зникає. Контролем лок стає лише тоді, коли його дотримання вимагають примусово: npm ci або yarn install --frozen-lockfile на будь-якій розбіжності перериває встановлення замість того, щоб тихо її залатати. Кут ланцюга постачання тут прямий: фіксація версії — це спосіб не підтягнути непомітно скомпрометований або вразливий реліз, а фіксація працює тільки з примусом. Поруч зазвичай згадують ще дві речі: закомітити .npmrc, щоб прапорці встановлення проходили рев'ю разом із локом, і звіряти цілісність завантаженого пакета з відомим добрим хешем.
Ми ще нічого не запускали, тільки поставили залежності. Чи міг виконатися чужий код?
Міг, і це головна причина, чому «просто встановити пакет подивитися» — не безневинна дія. npm побудовано навколо скриптів, які пакет оголошує сам і які виконуються у визначених точках його інсталяції, тож зловмисний пакет отримує можливість запустити довільну команду просто під час установлення. Важіль проти цього є — --ignore-scripts, — але в нього названа межа: команди, чиє призначення і є запуск скрипта (npm test, npm start), свій скрипт виконають попри прапорець. Тому дослідження підозрілого пакета роблять не на робочій машині з живими токенами, а в одноразовому ізольованому оточенні. Ще одна деталь, важлива саме для аудиту: виключення груп через --omit складу lock-файла не змінює — такі залежності все одно резолвляться й лишаються записаними в package-lock.json, просто на диск вони не лягають.
Чим тайпосквотинг, плутанина залежностей і слопсквотинг відрізняються один від одного?
Спільне в них те, що не пакет, а процес розв'язання його імені; канон збирає це під назвою dependency chain abuse. (typosquatting) — шкідливий модуль під іменем, дуже схожим на популярне: розрахунок на одрук у команді встановлення або в пулреквесті. (dependency confusion) працює інакше: зловмисник публікує в публічному реєстрі пакет з іменем вашого внутрішнього приватного, але з вищим номером версії, і менеджер обирає «свіжіше». Захист від неї названо конкретно — імена зі на кшталт @yourorg/package-name. (slopsquatting) — свіжий вектор навколо AI-асистентів: модель вигадує назву неіснуючого пакета, а зловмисник цю назву займає. Перевірка проста й дешева: перед установленням будь-якого запропонованого асистентом пакета виконати npm view <package-name> і подивитися, що це взагалі за пакет і хто його супроводжує. Для тестувальника з цього випливає банальна дисципліна рев'ю — ім'я залежності в дифі читають так само уважно, як домен у підозрілому листі.
Чому автотести називають передумовою оновлення залежності, а не звітом після нього?
Бо порядок тут визначає, чи можна взагалі ухвалити рішення. Канонічна процедура каже оновлювати спокійно лише тоді, коли на функції, які використовують зачеплену залежність, уже є автоматизовані тести — модульні, інтеграційні, функціональні або security, — а критерієм випуску є їхній зелений прогін. Якщо таких тестів немає, оновлення перетворюється на ставку: ніхто не знає, що саме зламається, і команда відкладає патч «до кращих часів», лишаючи вразливість жити. Окремий випадок — саморобний патч, коли фікса від постачальника ще немає: до нього обов'язково пишуть , який відтворює саму вразливість. Він робить дві речі одразу: доводить, що патч справді працює, і стереже, щоб виправлення не зникло тихо під час наступного оновлення. Тобто внесок QA тут не в тому, щоб перевірити реліз після , а в тому, щоб існувало ще до появи алерту.
Прилетів бюлетень: бал 9.8, компонент сидить глибоко в дереві залежностей. Що робите?
Оцінюють не швидкість реакції, а порядок кроків. Спершу — чи є цей компонент у складі того, що реально їде в прод: питання вирішується зіставленням запису зі , а не пошуком назви по чату. Далі — чи зачіпає вразливість саме нас: уразливим часто буває конкретний режим або функція, і саме на це відповідає . Якщо зачіпає, дивляться на тип зв'язку: транзитивну вразливість лікують оновленням прямої залежності, бо втручання просто в транзитивну зазвичай б'є по стабільності, — а знайти прямого предка допомагає , який показує шлях компонента у проєкт. Потім прогін тестів на функції, що цю залежність використовують. І фінальний крок, який власне і є перевіркою від QA: перегенерувати SBOM і переконатися, що вразливого компонента у складі більше немає. Окремо варто сказати те, чого від кандидата чекають: бал 9.8 сам собою пріоритету не задає — це Base чужого середовища. А якщо команда вирішує прийняти, таке рішення ухвалює не команда, а Chief Risk Officer, спираючись на її технічний висновок.
Чим SBOM відрізняється від графа залежностей?
SBOM (Software Bill of Materials) — машиночитний інвентар складу: що саме всередині продукту. Граф залежностей (dependency graph) — орієнтований граф відношень між компонентами: чому кожен із них там опинився і яким шляхом. Мінімум, без якого документ марний, теж названо: канонізовані ім'я та версія, purl як унікальний ідентифікатор пакета, контрольна сума (бажано SHA256) і ребра, що розводять пряму залежність та транзитивну. Генерують його автоматично під час збірки в конкретний момент — після розв'язання залежностей і перед пакуванням, — а не пишуть руками; стандартних форматів два — SPDX та CycloneDX, — а кожен реліз має супроводжувати бодай один машиночитний документ, і обов'язково підписаний, бо непідписаний інвентар підробити нескладно. Граф на платформі GitHub, своєю чергою, є похідною від маніфестів і lock-файлів у репозиторії: чого немає в них, не буде й у ньому, тому порожній граф читають як дефект вхідних даних, а не як добру новину.
Що ховається під іменем Dependabot і чому «алертів немає» не дорівнює «вразливостей немає»?
Механізмів насправді три, і плутанина між ними коштує пріоритетів. Алерти спрацьовують на дві події: поява свіжого запису в GitHub Advisory Database і будь-яка зміна самого графа залежностей; усередині алерту — зачеплений файл, severity й версія з фіксом, якщо така існує; алерт варто призначати на людину чи команду, інакше він зависає нічий. Security-апдейти — це пулреквести, які бот відкриває сам, коли залежність значиться вразливою. Version-апдейти — пулреквести заради свіжості, навіть коли жодної вразливості немає; вони вмикаються окремо файлом dependabot.yml і за замовчуванням витримують три дні від релізу версії, причому на security-апдейти ця не поширюється. Меж у алертів кілька, і всі вони пояснюють, чому порожня вкладка нічого не доводить: запис доходить до бази із затримкою, і то лише якщо його перевірив сам GitHub; заархівоване репо випадає зі сканування цілком; а екшен, закріплений по SHA, каналу сповіщень не має взагалі — алерти по GitHub Actions ідуть тільки для семантичного версіювання. І остання деталь про роль людини: канон вимагає перевірити прогін тестів і прочитати changelog та release notes перед мерджем — автомердж за самим зеленим чекбоксом цієї вимоги не виконує.
У пулреквесті від Dependabot тест раптом падає дивною функціональною помилкою, хоча на інших гілках усе зелене. З чого почати?
З перевірки секретів, а не з логіки тесту. Воркфлоу, які тригернули події , не отримують доступу до секретів репозиторія, а незаданий секрет підставляється порожнім рядком — не помилкою конфігурації, а порожнечею. Далі все залежить від того, як написаний тест: запит іде без токена, отримує 401 або взагалі анонімну відповідь, і на екрані ви бачите не «немає доступу», а щось на кшталт «елемент не знайдено» чи «список порожній». Симптом упізнаваний: падає рівно на гілках Dependabot і ніде більше. Лікується або окремою конфігурацією для таких воркфлоу, або, що корисніше для команди загалом, явною перевіркою на старті — , що обов'язкові змінні оточення не порожні. Це той рідкісний випадок, коли правильна дія тестувальника — зробити фейл голоснішим і ранішим, а не полагодити його.
Що насправді означає бал CVSS 9.8 у бюлетені й чи можна на нього спиратися?
Це серйозність базових характеристик, порахована поза вашим контекстом. Постачальники оцінок, зокрема NVD, публікують здебільшого лише Base Scores з позначкою -B; групу Environmental вони не рахують узагалі й натомість пропонують калькулятор, щоб її порахували ви. Далі йде формулювання, яке варто вміти процитувати змістовно: CVSS не є мірою ризику, це один із факторів пріоритизації. Окрема пастка — 10.0: коли вендор оголосив вразливість без деталей, оцінку виставляють за найгіршим сценарієм, тож максимальний бал інколи означає не «підтверджено найстрашніше», а «даних немає». І дзеркальна теза до історії з кількістю записів: наявність окремого CVE не доводить дефекту у вашому контексті — показовий випадок є навіть у навмисно вразливого навчального стенда , на одну з вразливостей якого завели CVE-2023-39848. Разом виходить дзеркальна пара тверджень: за кількістю записів захищеності не виміряти, а з окремого запису не випливає, що у вашому продукті є проблема.
Секрет випадково закомітили в репозиторій. Яка перша дія?
Ротувати й відкликати зачеплений креденшел — негайно, ще до будь-яких дій із git. Причина в тому, як влаштоване : воно проходить усю історію на всіх гілках, а не робочу копію, тож «прибрав наступним комітом» лагодженням не є — ключ і далі лежить в історії, а головне, він і далі живий. секрету з історії канон називає витратним і здебільшого зайвим, якщо ключ уже відкликано, тож послідовність саме така: спершу зробити ключ марним, потім вирішувати, чи взагалі варто чистити історію. Пріоритет у купі знахідок задають перевірки валідності — сервіс-емітент відповідає, чи ключ ще діє. Тестувальника це стосується безпосередньо: скан бачить не лише код, а й тікети, коментарі та заголовки пулреквестів, вікі й секретні gist-и, — тобто рівно ті місця, куди зручно вставити «робочий токен, щоб відтворити баг». Явище має назву — , розповзання креденшелів по всіх цих поверхнях.
Чи покриває SCA ризик стороннього JavaScript на сторінці?
Ні, і це хороший спосіб перевірити, чи розуміє кандидат межі інструментів. Аналітика, чат-віджети й маркетингові теги — це теж залежності, але вони не проходять через вашу збірку й у lock-файлі їх немає: браузер користувача звертається до серверів вендора напряму, а яку саме версію коду віддати, вирішує вендор у момент запиту. Звідси головний ризик — компрометація сервера стороннього скрипта й впорскування коду в оригінальний тег, після чого приїжджає без жодного вашого релізу, а виконується цей код із привілеями самого користувача, як при XSS. Канон прямо визнає, що передрелізна перевірка тут частково втрачає силу — включно з SAST, DAST, IAST і RAST, — бо перевіряли ви іншу версію файлу. Контроль інший: (SRI), тобто метадані цілісності на елементі script, за яких браузер виконає лише той код, що збігся з очікуваним відбитком; жорсткіший варіант — винести вендора в iframe з іншого домену як . Дві умови варто назвати відразу: SRI потребує ввімкненого CORS на хості вендора, а після планового оновлення у вендора сторінка законно опиниться в стані «захищено, але не працює» — це наслідок механізму, а не загадка. І поруч має бути живий інвентар доменів, з яких на проді реально виконуються скрипти: покинутий вендорський домен цілком може бути перереєстрований на когось іншого.
Три ситуації, у яких тема ланцюга постачання перестає бути теорією: червоний крок аудиту в , який треба прочитати перш ніж зупиняти реліз; бюлетень із високим балом на транзитивну залежність — від алерту до доказу, що фікс справді доїхав; і , якого немає в , але який щодня виконується у ваших користувачів. Скрізь — що робити і чому саме так.
Кейс 1. Червоний npm audit: три різні причини під одним кодом виходу
Пайплайн зупинився на security-кроці. У чаті вже пишуть «нас зламали» або, навпаки, «та це знову бот шумить». Обидві реакції передчасні: ненульовий код виходу сам собою не називає причину.
$ npm audit
# npm audit report
acme-parser 1.2.0 - 1.4.3
Severity: high
Prototype pollution in acme-parser
fix available via `npm audit fix`
node_modules/acme-parser
acme-report <=2.8.1
Depends on vulnerable versions of acme-parser
node_modules/acme-report
acme-fetch 3.0.0 - 3.1.2
Severity: moderate
Inefficient regular expression complexity
No fix available
node_modules/acme-fetch
3 vulnerabilities (1 moderate, 2 high)
$ npm audit --audit-level=high ; echo "exit=$?"
exit=1
Читаємо звіт по рядках, а не по кольору:
| Що в звіті | Що це насправді означає | Дія |
|---|---|---|
acme-parser, fix available | Пряма залежність, патч існує | Оновити — але спершу переконатися, що на функції, які її використовують, є автотести |
acme-report з поміткою «depends on vulnerable versions» | Мета-вразливість: пакет зачепило через його власну залежність | Лікувати на прямому предку; автоматичне виправлення тут спрацьовує не завжди |
acme-fetch, No fix available | Патча ще немає | Тимчасовий ігнор рівно на цей CVE, тікет на повернення, оцінка досяжності коду |
exit=1 при --audit-level=high | Поріг перетнула щонайменше одна знахідка рівня high | Дивитися, які записи нові порівняно з попереднім прогоном |
У звіті лишився moderate, хоча поріг high | Прапорець зсунув поріг фейлу, звіт він не фільтрує | Не читати зелений ран як «середніх знахідок немає» |
Гейт у CI зазвичай розводять на два кроки: один вирішує долю збірки, другий зберігає повну картину.
- name: Security gate
run: npm audit --audit-level=high
- name: Full audit report (не валить білд)
if: always()
run: npm audit --json > audit.json
- name: Publish report
if: always()
uses: actions/upload-artifact@v4
with:
name: audit-report
path: audit.json
Що дивитися і чому:
- Однаковий
exit=1буває з трьох причин. Це справді нова без фікса; це фікс, який існує, але його блокує власна політика свіжості релізу — npm лишає пакет на вразливій версії, попереджає й усе одно виходить ненульовим кодом; це поріг, налаштований не так, як думає команда. Перший крок розслідування — не «що зламалося», а «що змінилося з попереднього прогону». - Звіт і гейт — різні речі. Якщо в пайплайні лишиться тільки крок із порогом, історія ранів через півроку читатиметься як доказ чистоти, хоча під порогом весь цей час жили знахідки. Артефакт із повним
--jsonкоштує нуль і знімає цю ілюзію. - Без lock-файла цифри плавають.
npm auditспирається на зафіксоване дерево; із--no-package-lockвоно перебудовується щоразу, і два прогони поспіль можуть дати різні результати. Червоний крок, який «сам полагодився на ретраї», — привід перевірити саме це, а не радіти. npm audit— мережева операція. Опис дерева з іменами й версіями всіх пакетів їдеPOST-ом у реєстр. У закритому контурі це означає дзеркало реєстру, а не «аудит не працює».- Порожній звіт не є доказом. Інструмент шукає збіги з базою публічних записів; дефект, розкритий без , і свіжозалитий зловмисний пакет у ній не значаться.
Кейс 2. CVE 9.8 у транзитивній залежності: від алерту до доказу фікса
Уранці прилітає алерт: у складі продукту є компонент із базовим балом 9.8. Спокуса — або зупинити реліз, або відмахнутися. Робочий шлях лежить між цими крайнощами й складається з кроків, кожен з яких дає перевірний результат.
Крок 1. Чи це взагалі наш компонент і як він до нас потрапив. Транзитивну залежність шукають не пошуком по чату, а графом:
$ npm ls acme-parser
app@2.4.0 /srv/app
└─┬ acme-report@2.8.1
└── acme-parser@1.4.3
Прямого предка видно одразу: acme-report. Саме він і є місцем лікування — правити транзитивну залежність напряму зазвичай дорожче для стабільності, ніж оновити прямого предка.
Крок 2. Чи зачіпає це нас. Тут корисно розкласти рішення на таблицю, бо в реальному тікеті всі чотири випадки виглядають однаково — «є CVE».
| Ситуація | Що робимо | Що лишається в трекері |
|---|---|---|
| Фікс існує, тести на зачеплену функціональність є | Оновлюємо прямого предка, прогін тестів — критерій випуску | Нічого, окрім запису про оновлення |
| Фікс існує, тестів немає | Спершу пишемо тести на функції, що використовують залежність | Тікет на покриття як блокер оновлення |
| Фікса ще немає | Ігноруємо рівно цей CVE, не пакет цілком | Тікет на повернення + обґрунтування у VEX |
| Патчимо самі | Пишемо модульний тест, що імітує вразливість | Тест лишається назавжди — він стереже патч |
Крок 3. Тест, який стереже саморобний патч. Він відтворює саме вразливу поведінку, а не «щось поруч»:
import { test, expect } from '@playwright/test';
import { parseReport } from '../src/report';
// регресія на саморобний патч: пейлоад, який раніше забруднював прототип
test('парсер звіту не приймає __proto__ з тіла відповіді', async () => {
const payload = '{"__proto__": {"isAdmin": true}}';
const parsed = parseReport(payload) as Record<string, unknown>;
expect(parsed.isAdmin, 'властивість просочилась у результат').toBeUndefined();
expect(({} as Record<string, unknown>).isAdmin, 'забруднено глобальний прототип').toBeUndefined();
});
Крок 4. Доказ. Не «пулреквест змерджено», а склад продукту після збірки:
# SBOM генерує крок збірки — після розв'язання залежностей і перед пакуванням
$ jq -r '.components[] | select(.name == "acme-parser") | .version' sbom.cdx.json
2.0.0
Що дивитися і чому:
- Бал 9.8 — це Base чужого середовища. Публічні бази дають переважно Base Scores з позначкою -B і Environmental за вас не рахують. Плюс окремий випадок: 10.0 інколи означає, що вендор оголосив вразливість без деталей і оцінку виставили за найгіршим сценарієм.
- Рішення «приймаємо ризик» — не рішення команди. Технічний висновок готує команда, ухвалює Chief Risk Officer. Формулювання «ми подивилися, воно нас не чіпає» без запису й без власника — це не прийнятий , а забутий тікет.
- Пулреквест від не мерджиться за одним зеленим чекбоксом. Канон вимагає перевірити прогін тестів і прочитати changelog та release notes: security-апдейт може принести breaking change, а зелений CI лише каже, що наявні тести не почервоніли.
- Перегенерований закриває питання остаточно. Компонент або є у складі релізу, або його немає — це перевірний факт, на відміну від статусу тікета. Якщо генерація SBOM у пайплайні впала, валять; а от низькопріоритетні знахідки, які не потребують дії, білд валити не мають — їм місце на дашборді .
- Порожня вкладка з алертами нічого не доводить. База має , тригерять лише перевірені advisory, архівні репозиторії не скануються взагалі, а для GitHub Actions алерти йдуть лише по екшенах на семантичному версіюванні, не по закріплених SHA.
Кейс 3. Сторонній JavaScript: інвентар доменів і SRI-гейт
Аналітика, чат підтримки, теги маркетингу — залежності, яких немає ані в package.json, ані в локу. Браузер користувача забирає їх із серверів вендора напряму, і яку версію коду віддати, вирішує вендор у момент запиту. Тобто зміна може доїхати до продакшену без жодного вашого релізу, а виконається вона з тими самими привілеями, що й код сторінки.
Перша перевірка — інвентар: які чужі домени взагалі виконують у нас скрипти.
import { test, expect } from '@playwright/test';
const APP_ORIGIN = 'https://app.example.com';
const ALLOWED = new Set(['https://cdn.analytics.example', 'https://widget.support.example']);
test('інвентар сторонніх скриптів не змінився без нашого відома', async ({ page }) => {
const seen = new Set<string>();
page.on('request', (req) => {
if (req.resourceType() !== 'script') return;
const origin = new URL(req.url()).origin;
if (origin !== APP_ORIGIN) seen.add(origin);
});
await page.goto(`${APP_ORIGIN}/`);
await page.getByTestId('support-widget-launcher').click(); // віджет вантажиться ліниво
const unknown = [...seen].filter((origin) => !ALLOWED.has(origin));
expect(unknown, 'зʼявився сторонній домен, якого немає в інвентарі').toEqual([]);
});
Друга — технічний контроль цілісності. Канон називає умову роботи SRI прямо: на хості вендора має бути ввімкнено CORS. Атрибут integrity видно в DOM, а CORS — лише у відповіді вендора, тож тест дивиться в обидва місця:
test('сторонні скрипти мають integrity, а вендор віддає CORS', async ({ page }) => {
const corsHeader = new Map<string, string | undefined>();
page.on('response', (res) => {
if (res.request().resourceType() !== 'script') return;
corsHeader.set(res.url(), res.headers()['access-control-allow-origin']);
});
await page.goto(`${APP_ORIGIN}/`);
const external = await page.locator('script[src]').evaluateAll(
(nodes, appOrigin) =>
nodes
.map((node) => node as HTMLScriptElement)
.filter((node) => new URL(node.src).origin !== appOrigin)
.map((node) => ({ src: node.src, sri: node.getAttribute('integrity') })),
APP_ORIGIN,
);
expect(
external.filter((s) => !s.sri).map((s) => s.src),
'зовнішній скрипт виконується без перевірки цілісності',
).toEqual([]);
expect(
external.filter((s) => !corsHeader.get(s.src)).map((s) => s.src),
'на хості вендора немає CORS — перевірити відбиток браузер не зможе',
).toEqual([]);
});
Що дивитися і чому:
- Тест на інвентар ловить не вразливість, а зміну. Новий домен у списку означає, що хтось інтегрував вендора повз рев'ю або що чинний вендор почав тягнути код зі свого субпідрядника. І те, і те — привід для розмови до релізу, а не після інциденту.
- Ліниві скрипти треба спровокувати. Чат-віджети й частина тегів вантажаться після взаємодії або за таймером, тож інвентар, зібраний з одного
goto, буде неповним. Клік по лаунчеру у прикладі — не декорація. - Сам атрибут
integrity— лише половина контролю. Канон ставить умову на боці вендора: без увімкненого CORS перевірити відбиток браузер не зможе. Тому розділено на два — про розмітку й про відповідь вендора. - «Захищено, але не працює» — очікуваний стан, а не баг. Коли вендор планово оновив файл, відбиток перестає збігатися й скрипт не виконується. Це нормальна робота механізму: до неї потрібен процес оновлення хешів, а не зняття
integrity«щоб не заважало». - Передрелізна перевірка тут частково втрачає силу. Канон говорить це прямо про , , IAST і RAST: ви перевіряли ту версію файлу, яку вендор віддав вам, а користувач завтра отримає іншу. Тому контроль зміщується в рантайм — інвентар, SRI, ізоляція вендора в iframe з іншого домену.
- Покинутий домен — окремий ризик. Вендор може зникнути, а його домен зареєструє хтось інший — і тег на вашій сторінці слухняно виконає новий код. Інвентар доменів корисний ще й тим, що показує, кому ви досі довіряєте за інерцією.
A03:2025 як категорія
- Можу означити збої ланцюга постачання через процес збирання, дистрибуції й оновлення ПЗ і сказати, чим
A03:2025ширша за старий кошикA06:2021. - Не плутаю два числа: третє місце в самому рейтингу десятки й перше в опитуванні спільноти, де категорію обрала половина респондентів; так само розумію пару «11 CVE проти найвищої середньої частоти 5,19%» — мало записів означає сліпоту інструментів, а не безпеку продукту.
- Знаю три ознаки вразливості за A03 (немає обліку версій усіх компонентів разом із транзитивними, немає регулярного сканування й підписки на бюлетені, захищений слабше за системи, які він збирає) і розрізняю A03 та
A08:2025, де контроль — або рівноцінний механізм.
Де рветься ланцюг
- Знаю чотири місця розриву — код, середовище збірки, залежності, доставка й рантайм, — те, що дефект в одному компоненті ставить під весь ланцюг, і чотири вектори: викрадені сертифікати для підпису коду, зламаний CI/CD, компрометація інфраструктури вендора, .
- Знаю два контролі, що прямо стосуються QA (оцінка постачальника до інтеграції — чи активно підтримують проєкт і чи є в нього релевантне безпеці тестове ; рев'ю, яке шукає й навмисно закладений код), і що таке за SLSA 1.0, який генерує платформа збірки, а не машина розробника.
Вразлива залежність: від знахідки до рішення
- Знаю означення відомої вразливості як публічно повідомленого дефекту в пакеті й чому за інструмент виявлення потенційно сліпий.
- Розрізняю чотири випадки (фікс є, фікса немає, саморобний патч, постачальник ще не знає) і пам'ятаю, що тести на функції, які використовують зачеплену залежність, — оновлення, а саморобний патч потребує , що імітує .
- Знаю три межі, які знімають половину суперечок: транзитивну вразливість лікують на прямій залежності, ігнор виставляють точково на конкретний , а рішення «приймаємо ризик» ухвалює Chief Risk Officer — і не плутаю зі зловмисною.
SCA і читання червоного кроку
- Можу розкрити абревіатуру , чесно сказати, що канонічного означення терміна немає (у словнику ISTQB теж), і назвати межі: не аналізатор коду, не захист від зловмисних пакетів, не гарантія повноти бази, а шум від сміттєвих звітів гасять точково, за ідентифікатором.
- Розумію, що
npm audit— мережева операція з описом усього дерева, що без результати прогонів різняться, і що таке , яка не завжди лагодиться автоматично. - Розумію, що
--audit-levelзсуває поріг фейлу, а не фільтрує звіт, і можу назвати три причини однакового ненульового коду виходу: вразливість без фікса, фікс заблокований політикою свіжості релізу, поріг налаштований не так, як думає команда.
Lock-файл, установка, ім'я пакета
- Розумію, чому lock-файл стає контролем лише за форсованого дотримання (
npm ci,yarn install --frozen-lockfile), і що при розбіжності з маніфестом менеджер тихо поставить інші версії; поруч тримаю закомічений.npmrcі звірку хеша пакета. - Знаю, що скрипти пакета виконуються під час самої інсталяції, і межу
--ignore-scripts: команди, чиє призначення — запустити скрипт, свій скрипт усе одно виконають. - Розрізняю , плутанину залежностей і , знаю захист іменами зі та перевірку
npm view <package-name>перед установленням пакета, підказаного AI-асистентом.
SBOM, Dependabot і пріоритизація
- Можу назвати мінімальний склад (ім'я й версія, purl, контрольна сума, ребра «пряма проти транзитивної»), момент його генерації — після розв'язання залежностей і перед пакуванням — та причину підписувати документ; знаю роль і те, що порожній є дефектом вхідних даних.
- Вважаю доказом фікса перегенерований SBOM без вразливого компонента, а не змерджений пулреквест чи закритий алерт.
- Розрізняю три механізми , знаю cooldown у три дні (на security-апдейти не поширюється), межі алертів ( бази, лише перевірені advisory, архівні репозиторії, екшени по SHA) і те, що секрети недоступні від його подій, а незаданий секрет підставляється порожнім рядком.
- Розумію, що публічний бал — це Base чужого середовища з позначкою -B, що CVSS не є мірою , а 10.0 інколи означає «деталей не дали».
Секрети й сторонній JavaScript
- Знаю, що скан секретів проходить усю історію на всіх гілках і поверхні поза кодом (тікети, коментарі пулреквестів, вікі, gist-и), тож перша дія після знахідки — негайна ротація, а не переписування історії.
- Розумію, чому SCA не покриває , і можу назвати контроль: інвентар доменів, SRI з умовою CORS і станом «захищено, але не працює», ізоляція вендора в iframe.
Квіз
Перед стартом
- Питань: 17
- Поріг «зараховано»: ≥70% правильних відповідей.
- Результат впливає на прогрес; завалені питання підуть у чергу повторення.
- Квіз впливає на компліт теми: тема стає «пройдено», лише коли прочитано теорію І квіз складено на ≥70%.
Питання
Які чотири категорії загроз ланцюга постачання називає канон?