vyvchy
    Теми розділу

    11 · Security для QA

    Автоматизація security-перевірок для AQA/SDET

    Зміст

    Попередні чотирнадцять глав розділу відповідали на питання «що шукати». Ця — на питання «як зробити, щоб про це памʼятав пайплайн, а не людина». AQA тут не стає пентестером: він бере інваріанти, які вже вміє формулювати словами — чужа роль не бачить чужого рахунку, сесійна кука має бути HttpOnly, адмінський відповідає 403 анонімові — і перетворює їх на , файли й кроки (pipeline).

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

    Куди в пайплайні це ставлять

    Типова хиба планування — уявити «процес безпеки поруч» із наявним пайплайном. Канонічна модель робить навпаки: «we can add more steps in Dev pipelines» — це додані кроки в тому самому пайплайні, а мета сформульована як швидкість: «detect security issues (by design or application vulnerability) as fast as possible».

    Порядок стадій у гайдлайні OWASP і є відповіддю на «куди»: (нульовим кроком, до коду) → pre-commit із керуванням секретами й лінтингом → сканування вразливостей (, , IAST, , інфраструктура, ) → аудит відповідності. Шпаргалка OWASP про безпеку CI/CD формулює те саме вимогою — вбудувати ці перевірки в пайплайн, а перед у прод лишити ручний апрув. Там-таки стоїть застереження, з якого росте решта цієї глави: «one should never blindly rely on default vendor settings».

    Рівня в ці джерела security-перевіркам не призначають — поняття піраміди в них не вживається взагалі. Місце визначає стадія пайплайна й те, що перевірці потрібно на вході: сканеру коду — репозиторій, DAST — запущений застосунок за доступною адресою, асерту в тесті — відповідь сервера. Самі стадії й інструменти докладніше — «Інструменти й процес security-тестування»; тут вони потрібні лише як координати, куди лягають наступні розділи глави.

    Security-асерти: предмет перевірки — відповідь, а не екран

    Автотест не бачить «безпеки». Він бачить конкретні поля: код відповіді, заголовки, кукі, TLS-дані й стан на сервері після дії. (security assertion) — не окремий жанр тесту, а перевірка, предметом якої стала властивість безпеки відповіді.

    Змішаний сценарій «дію робимо в браузері, перевіряємо запитом» дока Playwright санкціонує прямо: виклики до HTTP-API з UI-тесту потрібні, щоб підготувати стан сервера або перевірити постумови після дії в браузері. Це дає те, чого UI не дає ніколи: відповідь на питання, чи справді сервер . Те саме в OWASP: «Relying on UI hiding is insufficient; the API layer must enforce the check».

    Три канали й межа кожного

    • Код відповіді ≠ «успішність». ok() означає буквально «status in the range 200-299», тож 403 і 404 дадуть однакове false — а різниця між ними і є предметом перевірки доступу. Потрібен status().
    • Заголовки мають дві форми, і одна з них бреше. Масивна підписана в доці окремо: «Headers with multiple entries, such as Set-Cookie, appear in the array multiple times.» Перевірка прапорців кукі по обʼєкту заголовків мовчки побачить одне значення на імʼя.
    • TLS-дані доступні, але порожнеча тут виглядає як успіх: для не-HTTPS повертається null, а для редиректу — дані останнього запиту ланцюга. «Сертифікат ок» і «сертифіката не дивилися» розрізняє тільки перевірка самого факту наявності даних.
    const res = await request.get('/api/invoices/42', {
      headers: { Authorization: `Bearer ${tokenOfAnotherUser}` },
    });
    
    // не ok(): 403 і 404 обидва дали б false
    expect(res.status()).toBe(403);
    
    // прапорці — тільки з масиву: Set-Cookie у відповіді може бути кілька
    const setCookie = res
      .headersArray()
      .filter((h) => h.name.toLowerCase() === 'set-cookie');
    expect(setCookie.every((h) => /HttpOnly/i.test(h.value))).toBe(true);

    У WebDriver робота з кукі вбудована: конкретна кука читається за іменем, усі — одним викликом, привʼязаним до живого контексту, а набір полів обмежений специфікацією. Окрема пастка — момент асерту: дока Playwright підписує очікування коментарем просто в прикладі логіну — «Sometimes login flow sets cookies in the process of several redirects», тож треба дочекатися кінцевого URL, а фіксованої паузи дока не пропонує взагалі.

    Що саме перевіряти в заголовках — глава про заголовки безпеки, CSP і клікджекінг; прапорці як предмет — «Сесії та токени» і «CSRF і SameSite»; механіка самої кукі — розділ 03.

    Негативні сценарії зі шпаргалки REST Security

    Шпаргалка OWASP дає перелік, з якого очікування тестів виводяться майже механічно:

    • Коди відмови різні за змістом: 401 — «Wrong or no authentication ID/password provided»; 403 — автентифікація пройшла, але прав на ресурс немає. Поруч: 405 на метод поза списком дозволених, 429 на перевищення частоти, 413 на завеликий пейлоад, 406/415 на неочікуваний тип вмісту.
    • Токен: заборона (alg: none), заборона обирати алгоритм перевірки за заголовком самого токена, мінімум клеймів — iss, aud, exp, nbf. Найцікавіший кейс — «запит токеном після логауту»: джерело описує розрив між життям токена й станом сесії, завершеної явним виходом або .
    • Порядок кроків процесу: чи можна викликати ендпоінти поза послідовністю, чи валідує кожен поточний стан, чи перевикористовуються токени між кроками. Звичайні перевірки прав цього не ловлять, бо кожен ендпоінт авторизується окремо.
    • Не тільки код, а й текст: чи внутрішні деталі в тілі — знахідка навіть при «правильному» статусі.

    Ще один прийом задарма: офіційний приклад зміни запиту в Playwright — це видалення заголовка перед route.continue(). Так перевіряють, що доступ тримає сервер, а не клієнт, який слухняно надіслав службовий заголовок.

    Матриця авторизації як код

    Причина, чому матрицю виносять у файл, названа прямо: «most of the problems with authorizations occur because features are added/modified in updated releases without determining their effect on the application's authorizations». Авторизація ламається не на старті, а в наступних релізах — звідси й рекомендація автоматизувати оцінювання прав і ганяти тест на кожен реліз. Надбудова називає це проблемою «другого дня».

    (authorization matrix) має два виміри й необовʼязковий третій: фіча, логічна роль і — за потреби — дані. Роль під час прогону називають (Point Of View, POV).

    Файл-півот обслуговує двох читачів одразу: його легко обробити програмою і легко прочитати людині. Тести не переписують правил, а беруть файл входом; падіння читабельне, бо називає саму комбінацію — сервіс DeleteMessage, точка зору ANONYMOUS, отриманий 200 замість очікуваних 403. Обидві половини перевірки беруться з того самого файлу: дозволений доступ і заборонений рівноправні. Блоків три: roles, services (з ролями, яким дозволено виклик) і services-testing (пейлоад для сервісів, що беруть дані не лише з URL); значення у фігурних дужках {} — плейсхолдери, які тест підставляє під час прогону.

    так

    ні

    Файл-півот: roles / services / services-testing

    Генерація кейсів: сервіс × POV

    Клітинка дозволена?

    Очікуємо успіх

    Очікуємо 403 або 404

    Падіння називає сервіс, POV, фактичний і очікуваний код

    так

    ні

    Файл-півот: roles / services / services-testing

    Генерація кейсів: сервіс × POV

    Клітинка дозволена?

    Очікуємо успіх

    Очікуємо 403 або 404

    Падіння називає сервіс, POV, фактичний і очікуваний код

    Формат: дві шпаргалки OWASP кажуть протилежне

    Тут канон сам із собою не збігається, і знати про це корисніше, ніж вибрати сторону навмання.

    • Шпаргалка Authorization Testing Automation: «the two dimensions of each authorization should be listed in a spreadsheet that is called an authorization matrix» — і приклад там тримається у файлі XML.
    • Шпаргалка Authorization Regression Testing робить той самий вибір навпаки й прямо: «Store this matrix in a machine-readable format (e.g., JSON, YAML, or structured test fixtures) rather than a spreadsheet. This allows testing frameworks to dynamically generate test cases, reducing manual maintenance as the authorization policy evolves.»

    Обидва джерела чинні й канонічні, причому другий сам позиціює себе як надбудову над першим — він «extends that foundation by focusing on the continuous regression dimension». Практично: питання на ревʼю «чому таблиця, а не YAML» закривається посиланням на конкретну шпаргалку, а не суперечкою про смаки.

    Набір і патерни

    Один тест-кейс на точку зору — тоді результати профілюються за рівнем доступу. звичайні (pytest, Jest, JUnit), і тести йдуть у тому самому CI-пайплайні, що функціональні; набір виділяють тегом або окремим скриптом, щоб ганяти локально, а перемикання ролі роблять підміною токена в заголовку, а не повним логіном.

    import matrix from './authorization-matrix.json';
    
    for (const service of matrix.services) {
      for (const pov of matrix.roles) {
        const expected = service.allowedRoles.includes(pov.name) ? 200 : 403;
    
        test(`${service.name} · POV ${pov.name}`, async ({ request }) => {
          const res = await request.fetch(service.url, {
            method: service.method,
            headers: pov.headers, // підміна токена, а не повторний логін
            data: service.payload,
          });
    
          expect(
            res.status(),
            `${service.name} з POV ${pov.name}: очікували ${expected}`,
          ).toBe(expected);
        });
      }
    }
    патернрецепткритерій
    Горизонтальна ескалація (IDOR)користувач B тієї ж ролі читає, змінює й видаляє ресурс користувача A«must return a 403 Forbidden or 404 Not Found ... never a 200 OK»
    Вертикальна ескалаціяусі неадміністративні ролі, включно з неавтентифікованою, стукають в адмінські ендпоінтиперевірку робить API; приховування в UI не рахується
    Ізоляція тенантівширокий читальний запит від користувача тенанта Beta по даних тенанта Alpha«Even a single leaked record identifier constitutes a critical failure.»

    Окремий важіль — контракт як джерело істини: якщо специфікація вимагає для ендпоінта read:invoices, фреймворк має сам перевірити, що токен без нього дістає 401 або 403. І перевірка потрібна на кожному ендпоінті, бо кожен виклик REST самостійний.

    Межа теми. Матриця як комбінацій — які клітинки бувають, які коди очікувані, чому дефолт незаповненої клітинки «заборонено» — живе в главі «Авторизація в API»; атакувальний кут і означення ескалацій — «Авторизація та контроль доступу».

    ZAP у пайплайні: baseline проти повного скану

    ( Baseline scan) — не окремий продукт, а скрипт із Docker- ZAP. Він пускає павука по цілі (типово хвилину) і чекає завершення . Чому його не бояться ставити в кожен прогін, сказано прямо: скрипт «doesn't perform any actual 'attacks'» і триває кілька хвилин щонайбільше, тож придатний для CI/CD «even against production sites».

    Повний скан — інша тварина, і дока сама називає його клас: це запуск «ZAP Full Scan to perform Dynamic Application Security Testing (DAST)».

    baselineповний скан
    павуктипово 1 хвилинабез ліміту часу
    активний сканнемаєповний активний скан
    дозвіл на цільпридатний навіть проти продакшну«WARNING this action will perform attacks on the target website»
    побічні ефектинемає атаксабмітить форми — «Contact us» і коментарі полетять реальними повідомленнями

    Попередити треба не лише власника застосунку: дока окремо каже звірити прогін із хостингом і CDN. Третій пакетний скан — активний прогін по API, визначених OpenAPI або GraphQL.

    Дві деталі оточення зʼїдають години. Найлегший образ bare пакетних сканів не містить. І окремо: «By default Docker does not allow apps running in one container to access an app running in another container» — сканер, який «нічого не знайшов», міг просто не достукатися до цілі. GitHub-дії при цьому не є окремим сканером: вони обгортають ті самі пакетні скани, ведуть один issue з алертами, кладуть звіт артефактом і працюють на штатному токені дії.

    Перше, що робиться після першого прогону, — конфіг-файл: він генерується параметром -g, а далі «Edit the file and change WARN to IGNORE to ignore rule or FAIL to fail as required», тобто рішення ухвалюється по кожному правилу. Відомий борг виносять не в IGNORE, а в progress-файл: там знахідки позначаються як IN-PROGRESS, тож у звіті видно саме нові.

    Сканування коду в репозиторії

    (code scanning) — функція GitHub, якою аналізують код у репозиторії, щоб знайти вразливості безпеки й помилки кодування; знайдені проблеми показуються в самому репозиторії. Механізм закриває дві задачі одразу — наявний борг і недопуск нового, — а запускається за розкладом або на подію на кшталт пушу. Одиниця результату — алерт, і його цикл замикається сам: виправили код, який його спричинив, — алерт закривається.

    Це не один аналізатор, а місце для аналізаторів: можна взяти CodeQL, можна сторонній інструмент, бо сумісність тримається на форматі — (Static Analysis Results Interchange Format); сам аналізатор при цьому може крутитися у зовнішньому CI. Дві межі видно в бюджеті: скан витрачає хвилини GitHub Actions, а для приватних репозиторіїв потрібна ліцензія Code Security. І одна межа, яку видно, лише якщо про неї знати: порожній звіт має два пояснення — чистий код і невідпрацьований інструмент. Під це є окрема сторінка стану інструментів.

    Гейти: коли знахідка валить білд

    Сканер знаходить — (CI/CD gating) вирішує. І вирішує він не про знахідку, а про код виходу. Сенс конструкції джерело формулює без помʼякшень: «The value of an authorization regression suite is only realized if it prevents vulnerable code from merging.» Механіка та сама, що в будь-якої перевірки: набір має бути required check, і якщо тест авторизації падає, не мерджиться. Без обовʼязкової перевірки падіння не заважає — і гейтом не є.

    ні — прапорець -I або дія без fail_action

    так

    ні

    так

    інструмент не відпрацював

    Сканер знайшов алерт

    Класифікація: типово WARN, у FAIL переводить конфіг

    Хтось читає код виходу як падіння?

    Крок зелений

    Перевірка є required check?

    Червоно, але мердж не заблоковано

    Мердж заблоковано

    ні — прапорець -I або дія без fail_action

    так

    ні

    так

    інструмент не відпрацював

    Сканер знайшов алерт

    Класифікація: типово WARN, у FAIL переводить конфіг

    Хтось читає код виходу як падіння?

    Крок зелений

    Перевірка є required check?

    Червоно, але мердж не заблоковано

    Мердж заблоковано

    Місце перше: за замовчуванням знахідка — не FAIL, а WARN. Дока baseline-скану ZAP каже це прямо: «By default all alerts found by ZAP will be treated as WARNings.» Коди виходу там же: «0: Success ... 1: At least 1 FAIL ... 2: At least one WARN and no FAILs ... 3: Any other failure» — тобто сама лише знахідка теж дає ненульовий код, просто інший. І окремий прапорець, який це падіння знімає: «-I do not return failure on warning».

    Місце друге: обгортка ігнорує код виходу контейнера. README обох GitHub-дій — і baseline, і повного скану — каже слово в слово: «By default ZAP Docker container will fail with an exit code, if it identifies any alerts. Set this option to true if you want to fail the status of the GitHub Scan if ZAP identifies any alerts during the scan.»

    Це місце й ламає інтуїцію найчастіше. Контейнер справді віддає ненульовий код навіть на самих лише WARN — але крок GitHub-дії від цього не падає, поки не виставлено fail_action: true. Мовчазний зелений крок при цьому виглядає рівно як пройдений гейт. Дзеркальна деталь: з fail_action: true крок червоніє на будь-яких алертах, а не тільки на переведених у FAIL.

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

    Четверте місце — не про мовчання, а про хибне читання червоного. Аудит залежностей npm має параметр, що задає мінімальний рівень вразливості, з якого команда повертає ненульовий код (info / low / moderate / high / critical / none). Але ненульовий код буває й тоді, коли фікс існує: якщо політика свіжості релізу не дає поставити пропатчену версію, «npm keeps the package at its vulnerable version, warns that the fix was blocked, and exits with a non-zero code». Тобто червоний гейт може означати не «зʼявилася нова вразливість», а «фікс є, але його заблокувала ваша ж політика». Ланцюг постачання й пріоритизація знахідок — окрема глава розділу.

    Корисний побічний сигнал того самого джерела: незвично великий обсяг 401/403 у прогонах варто виносити окремо — це натяк, що функціональна зміна зачепила наявні контролі доступу.

    Чого канон не дає, знати корисно: порогу «який рівень знахідки валить білд» не встановлює жодне з цих джерел — вони описують механіку (конфіг правил, -I, коди виходу, fail_action, мінімальний рівень аудиту), а політику лишають команді. Немає ні процедури винятку повз гейт, ні межі «блокуючий гейт проти попереджувального». Загальне питання «що взагалі блокує мердж» — тема розділу про Git і CI/CD.

    Типові помилки

    • Виглядає як «сканер у пайплайні є, отже, безпека перевіряється». Насправді типово всі алерти ZAP — WARN, а крок дії не падає без fail_action: true.
    • Виглядає як перевірка прав: негативний асерт на res.ok(). Насправді ok() — це діапазон 200–299, тож 403 і 404 зливаються в одне «не вдалось».
    • Виглядає як перевірка прапорців кукі. Насправді читання через обʼєкт заголовків бачить одне значення на імʼя, а Set-Cookie у відповіді буває кілька.
    • Виглядає як перевірка сесійної кукі одразу після логіну. Насправді кукі виставляються через ланцюг редиректів, і асерт до кінцевого URL перевіряє проміжний стан.
    • Виглядає як контроль доступу: кнопки немає в інтерфейсі. Насправді це приховування в UI, яке обходиться тривіально.
    • Виглядає як «зʼявилася нова вразливість»: червоний аудит залежностей. Насправді фікс може існувати, а заблокувала його політика свіжості релізу.
    • Виглядає як «код чистий»: порожній звіт сканера. Насправді так само виглядає невідпрацьований інструмент — або сканер, який не достукався до застосунку в сусідньому контейнері.

    Підсумок

    • Security-асерт — це асерт на відповідь, а не на екран. Статус замість ok(), масив заголовків замість обʼєкта, запит до сервера замість читання UI.
    • Матриця авторизації живе у файлі, а не в тестах. Тести беруть її входом; падіння називає комбінацію «сервіс + POV + фактичний код + очікуваний». Формат канон описує двома шпаргалками, які вимагають протилежного.
    • Baseline безпечний, повний скан — . Перший придатний навіть проти продакшну; другий вимагає дозволу, попередження хостингу й розуміння, що форми будуть засабмічені.
    • Гейт — це рішення про код виходу, і воно проходить три місця: як сканер класифікував знахідку (типово WARN, плюс прапорець -I), чи валить обгортка статус кроку (fail_action), чи є перевірка required check. Мовчить будь-яке — знахідка не блокує мердж.
    • Порогу severity канон не дає. Механіка є, політика — рішення команди; вигадана цифра гірша за чесне «джерела цього не задають».

    Можливі питання

    • «Як ви перевіряєте заголовки безпеки й прапорці кукі в автотестах?» Слухають не назву методу, а те, чи знаєте ви про множинні Set-Cookie й про момент перевірки після редиректів.
    • «Чим перевірка доступу в тесті відрізняється від перевірки, що кнопки немає?» Перевіряють розуміння, що клієнтські контролі обходяться тривіально. Сильна відповідь одразу згадує різницю 401 і 403.
    • «Як би ви автоматизували матрицю ролей?» Очікують не список тестів, а артефакт: файл із ролями й сервісами, генерація кейсів «сервіс × POV», обидві половини — дозволено й заборонено — і читабельне падіння.
    • «Що поставите в CI: baseline чи повний скан?» Питання про ціну: baseline не атакує, повний скан — DAST з активними атаками, сабмітом форм і павуком без ліміту часу.
    • «У нас зелений security-крок. Що це доводить?» Найкраще питання розділу. Правильна відповідь — «майже нічого, поки не перевірено три речі»: як класифіковано знахідки (WARN чи FAIL) і чи не знято падіння прапорцем, чи ввімкнено падіння статусу в обгортці, чи інструмент узагалі відпрацював і побачив ціль.
    • «Який рівень вразливості має валити білд?» Пастка на вигадану цифру: джерела порогу не задають, вони дають механіку. Сильний кандидат каже це прямо й переводить розмову на те, як таку політику ухвалює команда.

    Джерела

    Куди в пайплайні це ставлять

    • OWASP DevSecOps Guideline — мета «detect as fast as possible» для дефекту дизайну та вразливості застосунку.
    • OWASP DevSecOps Guideline — latest — модель «додані кроки в наявний пайплайн», порядок стадій від threat modeling до compliance і шість підрозділів стадії сканування.
    • OWASP Cheat Sheet — CI/CD Security — вимога вбудувати SAST/DAST у пайплайн, ручний апрув перед продом і заборона довіряти дефолтам вендора.

    Security-асерти: предмет перевірки — відповідь, а не екран

    • Playwright — APIResponse classstatus, ok, headersArray, securityDetails: які поля відповіді доступні асерту і де межа кожного.
    • Playwright — API testing — санкція на перевірку постумов на сервері після дії в браузері.
    • Playwright — Authentication — кукі виставляються через ланцюг редиректів; чекати кінцевого URL, а не фіксованої паузи.
    • Playwright — Network — видалення заголовка перед route.continue() як спосіб перевірити серверний рубіж.
    • Selenium — Working with cookies — вбудовані методи WebDriver для кукі й обмежений набір серіалізовних полів.
    • OWASP Cheat Sheet — REST Security — коди відмови, дозволені методи, вимоги до JWT, порядок кроків процесу й заборона технічних деталей у помилці.
    • OWASP Cheat Sheet — Authorization Regression Testing — приховування в UI не є контролем доступу: перевірку має робити API.

    Матриця авторизації як код

    • OWASP Cheat Sheet — Authorization Testing Automation — навіщо матриця переживає реліз, два виміри й POV, три блоки файлу-півота, формат «spreadsheet» і вимоги до падіння.
    • OWASP Cheat Sheet — Authorization Regression Testing — проблема «другого дня», вимога машиночитного формату, три патерни , контракт як джерело кейсів і структура набору.
    • OWASP Cheat Sheet — REST Security — контроль доступу на кожному ендпоінті, бо кожен виклик REST окремий і stateless.

    ZAP у пайплайні: baseline проти повного скану

    • OWASP ZAP — Baseline Scan — що це за скрипт, павук плюс пасивний скан без атак, конфіг-файл -g і перекваліфікація правил, progress-файл.
    • OWASP ZAP — Docker — образ bare без пакетних сканів, API Scan за OpenAPI/GraphQL, ізоляція контейнерів.
    • zaproxy/action-baseline — README — дія як обгортка: issue з алертами, звіт артефактом і штатний токен дії.
    • zaproxy/action-full-scan — README — повний скан як DAST, попередження про атаку, узгодження з хостингом і CDN, сабміт форм.

    Сканування коду в репозиторії

    • GitHub Docs — Code scanning — означення, дві задачі й тригери, життєвий цикл алерта, CodeQL і сторонні інструменти через SARIF, хвилини й ліцензія, сторінка стану інструментів.

    Гейти: коли знахідка валить білд

    Пояснення

    «Поясни» працює з власним API-ключем Anthropic: запит іде з вашого браузера прямо до Anthropic.

    Свій API-ключ (BYOK)

    Вставте власний ключ Anthropic — пояснення працюватиме на реальній моделі. Ключ зберігається лише у вашому браузері (localStorage), нікуди не надсилається, крім api.anthropic.com, і не логується.