Налаштування SAST через CodeQL у GitHub Actions: пошук уразливостей коду

Перевірки коду часто дають сотні попереджень, і команда перестає їх переглядати. Ми налаштовуємо CodeQL під ваш потік змін: прибираємо шум, залишаємо лише реальні уразливості та передаємо готові правила разом із регламентом. Обговоримо задачу і повернемося з оцінкою та планом.
Часті запитання

Налаштування CodeQL не приживається: хибні спрацьовування та шум у звітах

Формально перевірка в конвеєрі є, гілка захищена, звіт відкривається — і при цьому ніхто в нього не дивиться.

Налаштування CodeQL найчастіше зводиться до ввімкнення готового набору запитів «на все» без зазначення кроків збирання: аналізатор розбирає код частково, шлях даних обривається на межах модулів, і у звіті з’являються десятки знахідок, які не стосуються вразливостей.

Причина хибних спрацьовувань передбачувана. Інструмент не знає про саморобні обгортки над введенням, внутрішні санітайзери та корпоративний шар доступу до даних, тому вважає небезпечним будь-яке введення, що дійшло до запиту.

Один і той самий патерн повторюється в сотнях файлів — база наповнюється однотипними записами, а справжня небезпечна конкатенація тоне серед них.

Головна втрата від такого шуму — не час на розбір, а довіра до інструменту. Розробники починають закривати знахідки не дивлячись, ставлять винятки на каталоги, вимикають крок перевірки під час термінового релізу — і статичний аналіз тихо випадає з процесу.

Ігнорування знахідок стає нормою раніше, ніж команда встигає оцінити реальний ефект від впровадження.

Правильна конфігурація відповідає на три питання: які гілки та каталоги сканувати, які кроки збирання передати аналізатору, які запити взагалі мають сенс для цієї кодової бази.

Далі йде робота із самим звітом — розподіл знахідок за критичністю, закріплення базової лінії, щоб новий код перевірявся строго, а старий борг не блокував злиття.

Розбираємо, як це закривається: від збирання коректної бази та налаштування порогів до правил, за яких перевірка стає обов’язковою, але не заважає випуску.

Що змінюється після впровадження: час перевірки та покриття коду

Перевірка перестає бути вузьким місцем злиття

Перший прогін CodeQL будує базу за всією історією проєкту й займає хвилини, далі аналіз іде інкрементально — перераховуються лише змінені файли та пов’язані модулі. Вердикт за запитом на злиття надходить за хвилини, паралельно з тестами. Правки йдуть у ту саму гілку, де знайдені.

Покриття аналізом видно в цифрах

Налаштовуємо область: нові коміти, гілки випуску, мови проєкту. Метрика — частка змінених рядків, що пройшли перевірку; на старті вона зазвичай нижча за половину, після ввімкнення обов’язкової перевірки на злиття сягає 100% за новим кодом, історію підтягуємо порціями. Незакриті ділянки видно, а не «десь, напевно».

Класи дефектів, які не доходять до злиття

Підстановка в запити, небезпечна десеріалізація, обхід шляхів, витік секретів у конфігураціях. Аналіз іде за потоком даних від джерела до небезпечного виклику, тому ловить і складені ланцюжки, а не лише збіги за зразком. Знахідки закриваються в гілці — у випуск іде код без відомих шляхів експлуатації.

Дефекти стають метрикою, а не разовою акцією

Рахуємо кількість зауважень на випуск, частку хибних спрацьовувань і час від знахідки до виправлення. Поріг на злиття не дає накопиченням зростати, порівняння за релізами показує, чи працюють правила. Через кілька циклів видно, які класи помилок команда перестала допускати.

Режими запуску перевірок: порівняння варіантів та обмежень

CodeQL не розбирає файл окремо від решти коду: перед перевіркою збирається база даних проєкту, і від її складу залежить, які ланцюжки передавання даних побачить рушій.

Режим запуску — це вибір між швидкістю зворотного зв’язку та повнотою покриття, а не просто налаштування тригера. Нижче порівняння варіантів сканування, між якими реально обирають на старті.

Варіант запуску Коли підходить Обмеження аналізу
Аналіз змінених файлів під час push Гілки задач, швидкий відгук авторові правки Міжфайлові ланцюжки не збираються: витік через два-три модулі залишиться непоміченим
Перевірка на кожен коміт у pull request Обов’язкова перевірка до злиття в основну гілку База перезбирається на кожному оновленні; за частих правок збирання стають у чергу
Повний прогін за розкладом Нічний контроль усього репозиторію, включно з давно не зміненими модулями Знахідка надходить із затримкою, а винуватця серед паралельних правок шукати довше
Аналіз релізних гілок і тегів Перед збиранням артефакту, який іде в продуктив Пізнє виявлення: правка вже лежить зверху й потребує перегляду
Ручний запуск Розбір інциденту або перевірка перед зовнішнім аудитом Регулярності немає: пропущений запуск ніхто не помітить

Як режими поєднуються

На практиці їх не протиставляють: швидкий фільтр за зміненими шляхами дає авторові правки зворотний зв’язок за хвилини, перевірка на кожен коміт у pull request закриває основну гілку, а повний прогін за розкладом добирає те, що не потрапило в зріз — згенерований код, конфігурації, застарілі модулі.

Розбіжність результатів між режимами розбирають через винятки в конфігурації запитів, щоб одна й та сама знахідка не з’являлася повторно на релізі.

Налаштування CodeQL за кроками: від аудиту репозиторіїв до передавання

Налаштування CodeQL починається не з правки конфігурації, а з інвентаризації: поки не видно, які репозиторії що містять і як часто змінюються, будь-який набір правил стріляє в молоко.

Нижче — порядок, за яким ідемо на кожному проєкті; кожен крок закінчується артефактом, який передаємо команді.

  1. Інвентаризація репозиторіїв. Збираємо список сервісів, мов і релізних гілок, окремо позначаємо архівні проєкти — за ними аналіз не запускаємо.
  2. Збирання бази коду. Розгортаємо базу для кожної мови: для інтерпретованих — чистий аналіз, для компільованих — прохід із компіляцією; залежності та генерацію коду фіксуємо в описі задачі.
  3. Набір правил запитів. Типовий набір беремо за основу, додаємо правила під наш стек і вимикаємо шумні — результат версіонується як файл конфігурації, а не як ручні правки в інтерфейсі.
  4. Інтеграція в конвеєр. На змінах — швидкий прохід, у нічному прогоні — повний; звіти не блокують злиття, якщо зламався сам аналізатор.
  5. Відсікання хибних спрацьовувань. Кожну пачку знахідок розбираємо з розробниками: частину закриваємо правкою коду, частину — винятком із причиною та власником.
  6. Перевірка на реальних змінах. Прогоняємо на справжніх гілках: чи ловить аналіз нову вразливість і чи не рве збирання там, де не повинен.
  7. Передавання. Віддаємо конфігурації, список винятків з обґрунтуваннями, інструкцію з читання звітів і порядок ескалації чужих знахідок.

Що передаємо на виході: конфігурації, правила та регламенти

Усе, що з’являється під час налаштування CodeQL, залишається в команди в репозиторії та в базі знань — не в листуванні й не в чужій голові.

Конфігурація живе поруч із кодом і проходить рев’ю як звичайна зміна, тому перевірки не розсипаються за зміни підрядника чи виходу інженера з проєкту.

  • Файл конфігурації аналізу коду — умови запуску (зміна в гілці, запит на злиття, нічний прогін за розкладом), список мов і гілок, глибина розбору.
  • Набори правил — розділені на обов’язкові та рекомендаційні, з прив’язкою до критичності модуля: платіжні та авторизаційні шляхи перевіряються строгіше за решту.
  • Схема винятків — що і чому не перевіряємо, з відповідальним і строком перегляду, щоб список не перетворився на звалище.
  • Політика доступу до звітів — хто бачить знахідки, хто має право закрити, як результати розподіляються за ролями та зонами відповідальності.
  • Регламент реагування на знахідки — строки розбору за рівнями критичності, хто веде первинний розбір, у який момент заводиться задача.
  • Зв’язка із системою задач — автостворення дефектів за шаблоном, з мітками критичності та відповідальним за модулем.
  • Документація щодо перевірок — як запустити аналіз локально, як читати звіт, як додати або послабити правило.
  • План розвитку — які мови та перевірки підключаються наступними, що поступово виходить із винятків.

З таким комплектом новий інженер виходить на самостійну роботу за один день, без дзвінків і переказу контексту.

Кейс: скоротили шум і час перевірки на потоці змін

Репозиторій із десятком сервісів, спільний конвеєр збирання, статичний аналіз запускається — але звіт ніхто не читає. За квартал накопичилося близько чотирьохсот знахідок, більше половини хибні: згенерований код, тестові каталоги, рутинні приведення типів. Розробники навчилися обходити блокуючу перевірку, і вона перестала ловити будь-що.

Налаштування CodeQL почали з того, що розвели аналіз на два профілі. Швидкий іде за зміненими файлами на кожен запит на злиття, повний — за розкладом на всю гілку.

Згенерований код і фікстури пішли у винятки, поріг спрацьовування прив’язали до критичності: перевірка падає, тільки якщо знайдено шлях експлуатації, а не потенційно небезпечний виклик. Додатково описали правила під наш шар доступу до даних — там, де загальний набір мовчав.

Частка хибних спрацьовувань знизилася з 62% до 14%. Час проходження перевірки на запиті на злиття скоротився з 18–25 хвилин до 4–6 — результат надходить раніше, ніж автор переключився на наступну задачу.

Частка знахідок, закритих до злиття, зросла з 30% до 87%: правка в момент написання коду займає хвилини, розбір наслідків у робочому середовищі — години.

Окремо закріпили регламент: обхід обов’язкової перевірки можливий лише за явним рішенням відповідального і залишається в історії. Через два місяці таких випадків — одиниці, а необхідність самої перевірки перестала бути предметом спору. Правила переглядаємо в міру зростання кодової бази, інакше набір знову почне шуміти.

Що робити, якщо перевірки вже розгорнуті, але звіти ігнорують?

Розгорнуті перевірки, звіти яких ніхто не відкриває, — це не безпека, а фонове навантаження на збирання.

Причина зазвичай не в CodeQL, а в тому, як вбудовані його результати: перші тижні приносять сотні знахідок у успадкованому коді, і довіра до звіту падає швидше, ніж знаходиться час на розбір.

Першим ділом переглядають поріг блокування злиття. Перевірку вішають лише на нові зміни — правила застосовуються до рядків, які реально правляться в запиті на злиття, а старий код іде в базовий зріз.

Збирання починає падати на кількох знахідках замість сотень, і в команди з’являється причина їх розібрати, а не вимкнути перевірку цілком.

Другий крок — перегляд набору правил: вимикають запити з високим рівнем хибних спрацьовувань, виключають з аналізу каталоги тестів, згенерованого коду та зовнішніх залежностей. Залишаються правила, де знахідка означає експлуатований ризик, — такий список не вчаться ігнорувати.

Третій крок — відповідальність. За кожним каталогом закріплюють власника у файлі CODEOWNERS, а розбір знахідок — за черговим інженером із вікном реакції в одну добу.

Знахідка без власника живе до застарівання, знахідка з власником закривається або позначається як прийнятий ризик з обґрунтуванням.

Результат відстежують за метриками: частка розібраних знахідок, час до першого розбору, кількість придушень з обґрунтуванням. Коли ці числа йдуть у потрібний бік, аналіз перестає бути формальністю і починає впливати на рішення про випуск.

Технічні сумніви: збирання, закриті репозиторії та навантаження

Перед стартом приходять одні й ті самі сумніви — вони стосуються не можливостей аналізу, а того, як він вбудується в поточний процес розробки.

Нижче короткі відповіді за кожним пунктом, з яким стикаємося найчастіше: нестандартні збирання, закриті репозиторії, навантаження на конвеєр, мультимовні проєкти та власні правила.

Збирання нестандартне — аналіз взагалі запрацює?

Для компільованих мов розбір іде за фактом збирання, тому ключова умова — відтворюваність кроку збирання в чистому середовищі.

Якщо збирання тягне дані ззовні або потребує ручних дій, перемикаємося на аналіз без компіляції: частина перевірок на межах потоків даних втрачається, базовий набір дефектів залишається. На пілотній гілці це видно за один прогін, до розкатки на всі репозиторії.

Репозиторії закриті, код не повинен виходити назовні

Розбір запускається на виконавцях усередині вашого контуру, вихідні коди залишаються у вашій інфраструктурі. Назовні за потреби віддаємо лише агреговані результати без фрагментів коду — або не віддаємо взагалі, якщо звіти збираються локально.

Наскільки це сповільнить конвеєр?

Повний прогін за основним набором правил займає хвилини і ставиться на нічний розклад. У запитах на злиття працює інкрементальний режим — перевіряються лише змінені файли та те, що від них залежить, тому очікування розробника залишається в межах звичайного кроку перевірок.

У нас кілька мов в одному проєкті

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

Можна додати свої правила перевірки?

Так, описуємо власні правила під ваші внутрішні домовленості: заборона небезпечних викликів, обов’язкові перевірки прав доступу, вимоги до обробки персональних даних. Такі правила версіонуються разом із конфігурацією, щоб зміни проходили рев’ю, а не з’являлися мовчки.

Обговоримо задачу: підберемо склад перевірок під ваш проєкт

Розкажіть, які репозиторії та мови потрібно закрити аналізом, як зараз влаштований конвеєр і що заважає читати звіти. Проведемо аудит на пілотній гілці, покажемо, скільки знахідок реально значущі, і запропонуємо конфігурацію під ваш потік змін. Обсяг робіт і строки розраховуються індивідуально після розбору задачі.

Наші компетенції:

Останні роботи

  • Розробка сайту компанії B2B ADVANCE
    Розробка сайту компанії B2B ADVANCE
    1542
  • Розробка веб-додатків для компанії FEEDME
    Розробка веб-додатків для компанії FEEDME
    1393
  • Розробка веб-сайту для компанії БЕЛФІНГРУП
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    1081
  • Розробка інтернет магазину для компанії FURNORO
    Розробка інтернет магазину для компанії FURNORO
    1368
  • Розробка логотипу компанії B2B Advance
    Розробка логотипу компанії B2B Advance
    815
  • Розробка веб-додатків для компанії Enviok
    Розробка веб-додатків для компанії Enviok
    1139