# Валідація

## Що це таке

Валідація — це крок, на якому програма порівнює вхідні дані з правилами, яких ці
дані мають дотримуватися, і відхиляє їх, якщо правил порушено, ще до того, як
щось буде збережено, виконано чи передано далі. Залежно від рівня її називають
по-різному: валідація введення для полів форм і запитів до API, валідація за
схемою, коли правила записано як JSON Schema або опис OpenAPI, і валідація
розмітки, коли вебсторінку перевіряють за граматикою HTML. Дані, що пройшли
перевірку, називають валідними.

Настанови OWASP ділять цю роботу на два питання. Синтаксис визначає, чи має
значення очікуваний тип і форму: чи це дата, чи це ціле число, чи розбирається
тіло запиту як JSON. Семантика визначає, чи має значення сенс для конкретної
операції: бронювання готелю потребує двох коректних дат, а ще й того, щоб дата
виїзду була пізнішою за дату заїзду. Перетворення рядка «500» на ціле число
відповідає на перше питання і нічого не каже про те, чи прийнятне проживання на
500 ночей.

Валідація розмітки — найстаріша публічна форма цієї ідеї у вебі. Сервіс W3C
Markup Validation Service описує валідацію як перевірку документа за граматикою,
яку той сам декларує, і порівнює свій валідатор із lint для C. Документація
сервісу обережно пояснює, що саме це доводить: валідна сторінка не обов'язково
добра, це лише сторінка, яку інструмент визнав відповідною певному набору
правил. Та сама обережність стосується всіх інших різновидів. Валідація показує,
що дані відповідають написаним правилам, і нічого не каже про те, чи були ці
правила слушними.

Із валідацією часто плутають два сусідні поняття. Санітизація змінює вхідні
дані, щоб зробити їх прийнятними, вирізаючи чи переписуючи частини, тоді як
валідація приймає або відхиляє дані такими, як вони є. Екранування й
параметризація роблять дані безпечними для місця, куди вони потрапляють, —
сторінки HTML чи запиту SQL, — і вони потрібні й після того, як валідацію
пройдено.

## Чому це важливо

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

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

Останній вибір — суворість. Список дозволеного (allowlist) описує прийнятне і
відхиляє все інше; OWASP радить саме цей підхід замість спроб розпізнати кожен
зловмисний рядок. Відхилення полів, яких схема не знає, рано виявляє одруківки й
застарілих клієнтів. Відхилити весь запит на першій же проблемі, з чіткою
помилкою, безпечніше, ніж продовжувати з даними, перевіреними лише частково.

## Як це виглядає на практиці

API компанії починався з валідації запитів, написаної вручну для кожного
ендпоінта, і
[перехід на Huma v2 перетворив ці перевірки на типи для 649 шляхів і 760 операцій](/uk/portfolio/migrated-the-http-api-from-fiber-to-huma-60/).
Тепер кожен ендпоінт має вхідні та вихідні структури, Huma генерує з них
валідацію запитів, а опис OpenAPI виходить із тих самих типів. Два ендпоінти
WebSocket лишаються на чистому Fiber, бо модель запиту й відповіді Huma там не
підходить. Перевірка в CI порівнює маршрути, які реєструє сервер, із тими, що їх
декларує опис, паритет становить 100%, а цілий клас помилок, коли хтось забув
перевірити поле, зник разом із рукописним кодом.

Конфігурація потребує того самого, і
[посилення захисту SSH на хості компанії тепер проходить три етапи перевірки за кожного зведення](/uk/portfolio/hardened-ssh-with-three-stage-validation-111/).
Файл-кандидат перевіряють на синтаксис перед встановленням, зібрану конфігурацію
перевіряють після встановлення, а потім зчитують власні розв'язані параметри
демона й звіряють із ними 24 директиви. Третій етап з'явився тому, що звіт про
доступ у вересні 2026 року показав два параметри, які автоматизація записувала
за кожного зведення, а демон перекривав: власні усталені значення операційної
системи читалися першими, а для цих двох параметрів перемагає перше входження.
Як сказано на сторінці, пошук директиви доводить лише те, що її було записано.

Валідація розмітки теж входить до звичної роботи:
[перевірка HTML — одна з перевірок у воротах коміту з 22 лінтерів](/uk/portfolio/built-a-22-linter-commit-gate-127/)
без рівня попереджень, де кожна діагностика є помилкою, а вбудовані придушення
заборонено, тож невалідна сторінка потрапляє в коміт лише виправленою.

## Де виникають проблеми

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

**JSON-схема приймає все, про що в ній не сказано.** У JSON Schema перелічена
властивість ще не стає обов'язковою і не забороняє інших полів, а за типовим
словником 2020-12 ключове слово `format` збирається як анотація, і перевірку
формату типово вимкнено. Ця схема виглядає суворою:

```json
{
    "type": "object",
    "properties": {
        "email": { "type": "string", "format": "email" }
    }
}
```

Вона приймає `{}`, `{"email": "nope"}` і `{"emial": "a@example.com"}`. Варто
додати `required` і `"additionalProperties": false` та ввімкнути перевірку
формату у валідаторі або перевіряти формат у коді. Huma зі свого боку типово
відхиляє невідомі поля в тілі запиту, документує `readOnly` і `writeOnly`, але
їх не застосовує, і пропускає невідомі параметри рядка запиту, доки це окремо не
налаштовано.

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

**Список заборон зустрічає наступний варіант.** Блокування відомих небезпечних
символів відкидає цілком законні дані, скажімо апостроф у прізвищі, і водночас
залишає запит до бази даних небезпечним. Слід визначити, що приймає кожне поле,
і відхиляти решту.

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

## Що почитати далі

- [OWASP Input Validation Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html)
  — синтаксис і семантика, списки дозволеного, межі розбору й типові пастки.
- [W3C Markup Validator documentation](https://validator.w3.org/docs/) —
  власний опис валідації розмітки від валідатора, зі сторінками довідки про те,
  що валідність доводить і де її межі.
- [JSON Schema Validation, draft 2020-12](https://json-schema.org/draft/2020-12/json-schema-validation)
  — ключові слова валідації, зокрема типова поведінка `format`.
- [Huma: Request Validation](https://huma.rocks/features/request-validation/) —
  валідація з тегів структур Go, із суворим і м'яким режимами полів.
- [OpenAPI Specification](https://spec.openapis.org/oas/latest.html) — формат
  опису, схеми якого застосовує багато валідаторів API.

<https://engineer.company/uk/glossary/validation/>
