Помилки URL-кодування рідко помітні під час code review. Посилання правильно працює з тестовим словом, а потім ламається через амперсанд у назві, плюс у номері телефону або вже закодований параметр redirect. У більшості випадків система плутає дані зі структурою: кодує надто багато, надто мало або на неправильному шарі.
Подвійне кодування змінює початкове значення
Після першого кодування пробіл стає %20. Якщо закодувати результат ще раз, знак відсотка перетвориться на %25, і утвориться %2520. Отримувач після одного декодування побачить буквальний текст %20, а не пробіл.
Таке трапляється, коли middleware, helper і бізнес-код одночасно вважають себе власниками серіалізації. Усередині застосунку потрібно передавати структуровані незакодовані значення. Кодування виконується один раз під час побудови URL, а декодування — один раз після парсингу.
Плюс не завжди означає пробіл
У form-style query encoding плюс представляє пробіл. В інших компонентах URL це звичайний символ. Телефонний номер або Base64-рядок пошкоджується, якщо generic query parser мовчки перетворює плюс на пробіл. Літеральний плюс потрібно передати як %2B.
Документація API повинна пояснювати, чи query використовує правила application/x-www-form-urlencoded або строге percent-encoding. Тести мають окремо містити пробіли й буквальні плюси.
Reserved characters потрібно кодувати як дані
Амперсанд розділяє параметри, знак рівності — назву й значення, hash починає фрагмент, question mark — query, а slash — сегмент шляху. Якщо такий символ належить значенню, незакодований запис змінить структуру адреси.
Component-aware builder вирішує більшість цих проблем. Він знає, що слеш може бути структурним у повному шляху, але має бути закодований усередині одного динамічного сегмента. Універсальна функція заміни не розуміє контексту.
Раннє декодування створює прогалини безпеки
Security filter може перевірити сирий URL, а маршрутизатор пізніше його декодує. Нападник кодує небезпечну послідовність так, що фільтр бачить нешкідливий текст, а застосунок — traversal або заборонений маршрут. Можлива й протилежна ситуація, коли proxy нормалізує адресу агресивніше за backend.
Усі шари, які приймають рішення про маршрутизацію, авторизацію, кеш або підпис, повинні працювати з погодженим канонічним представленням. Некоректне кодування краще відхилити на вході, ніж дозволяти різним компонентам «ремонтувати» його по-своєму.
Open redirect ховається у звичайному параметрі
Login і payment flow часто приймають адресу повернення. Кодування цього значення не робить його довіреним. Зловмисник може передати закодовану зовнішню адресу й після легітимної дії перенаправити користувача на phishing-сторінку.
Return destination варто обмежити дозволеними хостами або представляти внутрішнім route identifier. Адресу потрібно розібрати, перевірити схему й хост, врахувати misleading subdomain, user-info syntax та альтернативні представлення.
Підписані URL потребують точної нормалізації
Для підпису важлива кожна текстова відмінність: порядок параметрів, регістр hexadecimal escape, плюс чи %20, наявність стандартного порту. Дві логічно еквівалентні адреси можуть мати різні підписи.
Протокол має точно визначити canonicalization. Підписувач і перевіряльник повинні застосовувати однакову реалізацію та мати тести для Unicode, повторених ключів, порожніх значень і reserved characters.
Malformed escape потрібно відхиляти явно
Знак відсотка без двох hexadecimal символів не є правильним escape. Парсери поводяться по-різному: один відхиляє значення, інший залишає текст, третій замінює байт. Кілька можливих трактувань створюють неоднозначність між security layers.
Строга перевірка на вході робить помилку відтворюваною та відокремлює неправильний транспортний синтаксис від валідного значення, яке не проходить бізнес-валідацію.
Діагностуйте значення на кожній межі
Коли параметр приходить пошкодженим, порівняйте початкове структуроване значення, побудований URL, сирий request target і результат парсингу фреймворком. Browser devtools, proxy, server log і framework можуть показувати різні — уже декодовані або нормалізовані — форми.
Корисно відтворити проблему мінімальним запитом без основного клієнта. Це швидко показує, чи помилка виникає під час побудови адреси, у транспорті або після отримання запиту.
Cache key повинен використовувати ту саму семантику
Якщо proxy кешує сирий URL, а застосунок вважає кілька представлень еквівалентними, одна відповідь може бути збережена під багатьма ключами. Гірше, різна нормалізація іноді дозволяє обійти security rule або отримати відповідь іншого користувача.
Правила canonicalization мають бути погоджені між CDN, gateway і backend. Параметри, які впливають на авторизацію або контент, не можна ігнорувати лише для покращення cache hit rate.
Не декодуйте значення повторно «про всяк випадок»
Повторне декодування може перетворити безпечний літеральний текст на структурний символ. Наприклад, закодований percent sign після першого етапу стає звичайним %, а після другого — новим escape. Саме так виникають обходи фільтрів і traversal-перевірок.
Кожен компонент повинен знати, чи отримує сирий request target, уже parsed URL або декодоване значення. Неоднозначний тип «string» недостатній для security-sensitive межі.
Правильна відповідальність робить кодування передбачуваним
Зберігайте внутрішні значення сирими, будуйте адресу через компонентний API, декодуйте один раз і перевіряйте сенс окремо від синтаксису. Такий підхід прибирає «випадкові» баги, які насправді виникають через те, що забагато шарів змінюють один рядок.
URL-кодування є простим, коли система чітко знає, де закінчуються дані й починається структура.
Помилки мають бути видимими для клієнта
Коли URL або параметр неправильний, API має повертати зрозумілу 4xx-відповідь, а не мовчки змінювати значення. Повідомлення може вказати проблемне поле й допустимий формат без розкриття внутрішніх правил безпеки.
Метрики malformed URL допомагають побачити зламаний клієнт або спробу атаки. Важливо агрегувати причину, не записуючи повні адреси з можливими секретами.
Такі помилки варто виправляти на стороні producer, а не приховувати поблажливим parser.
Це зберігає контракт передбачуваним для всіх клієнтів.