После выката frontend на новый host интерфейс не получает данные или mutation заканчивается ошибкой. Самое рискованное действие — сделать CORS глобально permissive или выключить CSRF «для проверки». Такой change расширит доступ без review. Диагностика начинается с request contract, не с флага middleware. Цена ошибки — дать origin доступ к данным или mutation.
Маршрут разделяет CORS error, OPTIONS failure и CSRF rejection. Локальный fixture не изображает сеть: его inputs — заданные labels, выход — решения учебного контракта. Он не сообщает response proxy, cookie delivery или access log. Для этого нужен реальный browser evidence в контролируемой среде, без переноса production cookie и secrets в заметку.
Соберите факты до первого исправления
Начните с пяти значений: полный origin страницы, URL target, method, content type и имена request headers. Потом добавьте status и response headers, которые видны в browser DevTools или на boundary proxy, а также application reason, если он не раскрывает token. Разница между https://app.example.test и https://app.example.test:8443 существенна; разница между POST form и PATCH JSON тоже существенна. Лог «CORS failed» без этих полей — не доказательство причины.
Не переиспользуйте пользовательскую production cookie в диагностике. Для реального browser check создайте разрешённую test session, заранее определите тестовую запись и ожидаемый безопасный side effect, а затем удалите или изолируйте данные согласно правилам среды. Если такой стенд пока не готов, зафиксируйте contract review и in-memory fixture как подготовку, но не пишите, что CORS или CSRF уже проверены. Отсутствие evidence — полезный результат: он показывает, какой артефакт нужен до релиза.
| Симптом | Вероятная причина | Проверка | Первое действие |
|---|---|---|---|
| JS не читает response | origin отсутствует в CORS allow-list | сверить full tuple с exact response header | добавить только нужный origin |
| Credentials response заблокирован | wildcard или нет Allow-Credentials: true | смотреть пару response headers вместе | вернуть exact origin, не расширять wildcard |
| OPTIONS не проходит | method или custom header не разрешён | сверить declared method/header с preflight response | разрешить узкий нужный набор |
| POST form получил 403 | CSRF token отсутствует или не совпал | проверить server reason до mutation | починить выдачу/передачу token |
| Mutation прошёл, UI увидел error | CORS visibility и server action различаются | сверить server evidence и response policy | исправить CORS без снятия CSRF |
| 403 после token check | у пользователя нет business permission | разделить reason CSRF и authorization | чинить policy ресурса, не token |
Не выводите server action из console message
CORS error сообщает о том, что browser code не получил ожидаемый cross-origin response по правилам модели. Он не является универсальным сообщением «сервер не видел запрос». Для некоторых request shape сервер мог уже принять HTTP traffic, а JavaScript всё ещё не сможет прочитать representation. Для других, требующих preflight, actual request может не начаться после отказа OPTIONS. Эти варианты нельзя различить по одной строке консоли, поэтому в проверке должны быть и browser evidence, и proxy/application log с безопасным корреляционным идентификатором.
Не делайте из этого аргумент «CORS бесполезен». Он ограничивает, какие foreign origin получают доступ к данным через browser API, и этим уменьшает поверхность чтения. Просто у него другая ответственность, чем у CSRF. Для cookie-backed mutation сервер должен решить, почему request разрешён: token совпал с привязанной к сессии записью, origin прошёл узкую policy, а пользователь имеет нужное право. CORS response header не содержит это доказательство и не должен заменять его в code review.
Разберите credentials и preflight по отдельности
Если endpoint действительно нужен cookie-authenticated frontend на другом origin, проверить надо две пары значений. Первая — client credentials mode и server Access-Control-Allow-Credentials: true. Вторая — exact source origin и Access-Control-Allow-Origin. Для credentials wildcard запрещён исторической CORS Recommendation. Нельзя исправить одну половину и считать contract готовым: response может всё ещё быть скрыт от JavaScript, а добавление произвольного origin создаст новый trusted reader без явной причины.
Для preflight соберите actual method и header names из client code, затем сверяйте их с узким OPTIONS contract. Например, X-CSRF-Token может требовать разрешения как custom header, а PATCH — как method. Не отвечайте «все методы, все headers» ради скорости: это затрудняет review и превращает следующую ошибку клиента в неявно допустимый API path. Когда endpoint поддерживает form POST, обязательно выполните отдельный CSRF test, потому что form-shaped request не обязан проходить через тот же preflight путь.
Проверьте терминологию локально, не подменяя стенд
В скрипте рядом со статьёй fixture держит несколько отрицательных веток: credentialed wildcard отклоняется договором; port 8443 создаёт другой origin; same-origin POST без token всё равно получает deny-token; form-shaped POST тоже не проходит серверное CSRF-условие. Эти assertions полезны перед рефакторингом middleware, потому что запрещают склеить CORS и CSRF в одну переменную. Но они не запускают gateway, framework, browser или HTTP server.
node web/scripts/upgrade-2023-03.mjs --verify-fixture
# PASS означает только следующее:
# входные labels прошли 12 assertions in-memory contract.
# Команда не открывала сеть, не вызывала Fetch, не посылала cookie,
# не создавала OPTIONS и не проверяла реальный API или браузер.
После PASS не пишите «сетевой repro завершён». Верная запись короче: «in-memory contract подтвердил 12 утверждений о заданных labels; реальный browser/API evidence требуется отдельно». Такой текст не выглядит слабее; он показывает, где заканчиваются данные. В качестве следующего artifact приложите к PR скрин или HAR из test environment, server status, выбранный exact origin и отрицательный case без token. Секреты, сами token values и production account identifiers в evidence не нужны.
CSRF-проверка должна пережить CORS-исправление
Для token-based flow найдите место, где сервер выдаёт token, место передачи и точку сравнения до side effect. Если frontend получает token из HTML, проверьте, что route, который рендерит страницу, не кэширует чужую сессию. Если token передаётся custom header, проверьте его имя в narrow preflight allow-list. Если framework уже даёт CSRF middleware, сначала изучите его contract и тесты вместо замены собственной краткой функцией. Самодельная проверка чаще всего забывает rotation, logout, error handling или исключения route.
Origin check удобен как дополнительная защита, но его политика должна быть строгой. Не сравнивайте endsWith('example.test'), не делайте null trusted по умолчанию и не принимайте отсутствующий header молча, если это не описанная compatibility ветка. Если API принимает webhooks, native clients или service-to-service traffic, не пытайтесь заставить их выглядеть как browser CSRF flow. У каждого типа caller должен быть свой явный authentication contract, а исключение из CSRF middleware — ограниченный route, а не отключение на всём приложении.
Маршрут: симптом → причина → проверка → действие
- Симптом. Зафиксируйте одну failing операцию и её цену: UI не читает данные, mutation отклонён или action мог выполниться без видимого response. Не объединяйте несколько endpoints в один кейс.
- Причина. Отделите origin tuple, CORS response, request shape/preflight, CSRF proof и business permission. У каждого пункта должен быть свой owner и log source.
- Проверка evidence. В test environment сопоставьте DevTools или HAR с proxy/application status. Если actual request неизвестен, запишите это неизвестное, а не вывод из console text.
- Проверка контракта. Запустите fixture и прочитайте отрицательные assertions. Он защищает только смысл статейного договора, не сеть и не session implementation.
- Действие. Добавьте exact origin, один method/header в OPTIONS response либо недостающую передачу token. После change повторите только исходную операцию и её отрицательную ветку.
- Закрепление. Оставьте автоматический route test на missing/mismatch token и reviewable CORS config. Для нового frontend origin потребуйте отдельный security review, а не копирование существующей строки.
Ограничения и критерий готовности
Этот маршрут не заменяет penetration test, cookie audit, CSP review, XSS defense или authorization test. XSS особенно меняет картину: script на доверенном origin может использовать доступные ему token и API, поэтому CORS и CSRF не являются защитой от выполнения чужого JavaScript в собственном origin. Статья также не назначает TTL token, набор SameSite attributes, format error response или универсальный список trusted origins. Эти параметры зависят от framework, browser support, session model и threat model.
Критерий готовности для одного endpoint узкий и проверяемый: reviewer видит точный source origin, разрешённый credentialed CORS response, нужный preflight contract при наличии custom header, server-side rejection без CSRF proof и отдельную permission check. Если есть только CORS header, работа не готова. Если есть только token test, frontend может не прочитать expected response. Если есть только fixture PASS, нет evidence реальной интеграции. Соберите все три слоя, но не объявляйте один из них заменой другого.
Историческая граница марта 2023
Нормативные ссылки этой партии датированы до конца марта 2023: RFC 6454 опубликован в 2011 году, W3C CORS Recommendation — в 2014 году, а WHATWG Fetch snapshot — 24 марта 2023 года. Они достаточны, чтобы проверить модель origin, CORS credentials и preflight. Конфигурацию конкретного proxy, browser quirks и middleware version всё равно нужно сверять в документации используемой платформы и в test environment.
Проверяемые источники
- WHATWG Fetch Standard, commit snapshot 8f109835, 24 марта 2023 — зафиксированная версия стандарта, доступная до конца марта 2023; нужна для терминов CORS, credentials и preflight, а не как описание конкретной реализации браузера.
- RFC 6454: The Web Origin Concept, декабрь 2011 — стандарт IETF: origin определяется tuple scheme, host и port; документ также описывает заголовок Origin.
- W3C Cross-Origin Resource Sharing, Recommendation 16 января 2014 — стабильная историческая рекомендация, доступная задолго до марта 2023; отдельно описывает credentials, preflight и то, что state-changing simple requests требуют CSRF-защиты.