Кросс-индустрияИнтеграции и API

Политика CORS для корпоративного API: как назначить владельца, границу и проверку изменений

Политика CORS становится управляемой, когда описывает не «весь API», а один browser-маршрут: кто обращается, каким методом и заголовками, нужны ли учётные данные, кто принимает изменение и как оно проверяется.

Мария Новикова
Цифровой редакционный персонаж НТА. Материалы создаются на основе экспертизы компании и проверяются профильной командой.
1 сентября 2026 г.·5 мин чтения·0 комментариев
/ Скриншоты и видео
3 материала

Ошибка CORS — это поздний сигнал, а не место для политики

В понедельник разработчик добавляет к внутреннему порталу новый вызов PATCH /api/contracts/42. В браузере появляется CORS-ошибка. Рядом уже есть знакомая строка конфигурации: поставить Access-Control-Allow-Origin: *, разрешить все методы — и продолжить работу. Кажется, что это настройка транспорта. На самом деле команда принимает другое решение: какие страницы с других источников смогут читать ответ этого маршрута в браузере.

CORS не является «паролем для API». Это механизм браузера: сервер через HTTP-заголовки сообщает, какому origin разрешено прочитать ответ кросс-доменного запроса. Origin состоит из схемы, хоста и порта; https://portal.example.ru и http://portal.example.ru — разные origin. MDN описывает, что Access-Control-Allow-Origin может назвать один origin, а * допустима только в сценариях без учётных данных.

Отсюда полезная граница. CORS отвечает на вопрос «может ли JavaScript этой страницы получить этот ответ». Аутентификация отвечает «кто делает запрос», авторизация — «может ли он выполнить операцию», а защита от CSRF — «не использует ли чужая страница состояние пользователя против него». Если сложить всё в одну настройку gateway, проверить решение после изменения почти невозможно.

Поэтому начинать стоит не с заголовка, а с одной строки: маршрут — разрешённый origin — метод — клиентские заголовки — credentials — владелец — тест — срок пересмотра. Такая строка не делает API безопасным автоматически. Она возвращает техническому изменению автора, границу и доказательство.

Граница: маршрут, а не весь домен

У одного API могут быть совершенно разные браузерные потребители. Публичный каталог без персональных данных может допускать чтение с нескольких витрин. Внутренний маршрут изменения договора может быть нужен только порталу сотрудников. А служебный маршрут вообще не должен быть доступен browser-клиенту другого origin.

Если все три случая наследуют одну широкую настройку /*, исчезает различие между данными, операцией и потребителем. Удобство настройки получает приоритет над смыслом маршрута. OWASP рекомендует разрешать только выбранные доверенные origin и не ставить CORS-заголовки там, где кросс-доменный доступ не нужен; это методическая рекомендация, а не готовый список для каждой компании.

Начните с трёх вопросов.

  1. Какой точный browser-origin действительно вызывает маршрут: схема, хост и порт?
  2. Что именно он делает: читает безличный ресурс, создаёт черновик или меняет состояние?
  3. Почему этому браузерному клиенту нужно прочитать ответ, а не только отправить событие через другой контролируемый контур?

Ответ «потому что фронтенд так устроен» пока не политика. Он не содержит маршрута, владельца данных или условия, при котором правило можно убрать. Для незнакомого origin безопасным исходом обычно будет отсутствие CORS-разрешения до явного решения, а не динамическое отражение любого значения заголовка Origin.

Preflight — не пропуск в API

Браузер отправляет предварительный OPTIONS-запрос не для каждого вызова. Он нужен, когда способ вызова выходит за пределы простого CORS-сценария: например, используются определённые методы или дополнительные клиентские заголовки. MDN отдельно перечисляет CORS-безопасные заголовки; изменение Content-Type или добавление клиентского заголовка способно изменить путь выполнения.

В preflight браузер сообщает будущие метод и заголовки, а сервер отвечает допустимыми значениями через Access-Control-Allow-Methods и Access-Control-Allow-Headers. Это позволяет браузеру решить, открывать ли JavaScript доступ к последующему ответу. Но preflight не устанавливает личность пользователя, не проверяет полномочия на договор и не доказывает, что запрос пришёл из браузера. Прямой HTTP-клиент не обязан соблюдать браузерную модель CORS вообще.

Значит, у защищённого PATCH остаются два независимых слоя. Первый — прикладной: сессия или токен, права на объект, защита состояния, аудит. Второй — браузерный: сможет ли известный origin выполнить нужный способ вызова и прочитать результат. OWASP прямо предупреждает, что CORS не заменяет обычную CSRF-защиту и проверку доступа. Не надо пытаться лечить отсутствие авторизации преflight-ответом — это разные вопросы.

Практическая проверка тоже должна быть двойной. Для разрешённого origin тестируйте успешный browser-flow с фактическими методом и заголовками. Для похожего, но неразрешённого origin — отсутствие CORS-разрешения. Затем отдельно убедитесь, что неавторизованный или неуполномоченный клиент получает корректный прикладной отказ. curl полезен для просмотра заголовков, но сам по себе не доказывает, что браузер разрешит чтение ответа.

Credentials и кэш: не добавляйте их по привычке

Самая опасная строка матрицы — та, где browser-клиент передаёт cookies или иной credentialed-контекст. Для такого сценария спецификация Fetch требует конкретный Access-Control-Allow-Origin; wildcard * с credentials браузер не принимает. При необходимости сервер также явно сообщает Access-Control-Allow-Credentials: true. Это не техническая косметика: конкретный origin получает возможность читать ответ в контексте пользовательской сессии, если остальные механизмы это допускают.

Сначала стоит спросить: нужны ли cookies этому маршруту вообще? Если API рассчитан на отдельный технический токен, background-задачу или серверную интеграцию, browser-credentials могут быть лишними. Если они действительно необходимы, в строке политики должны появиться минимум: точный origin, причина, владелец маршрута, прикладная авторизация, CSRF-мера и отрицательный тест для неразрешённого origin. Нельзя превращать это в регулярное выражение «для всех поддоменов» без раздельной оценки владения каждым поддоменом.

Есть и менее заметная часть — кэш. Когда сервер выбирает конкретный Access-Control-Allow-Origin в зависимости от входного Origin, ответ должен корректно различаться для кэша; MDN указывает на Vary: Origin. Preflight может кэшироваться через Access-Control-Max-Age, но это не повод выбирать большое значение ради уменьшения запросов. Чем дольше кэш, тем дольше клиент способен следовать старому разрешению после изменения политики. Выберите значение вместе с владельцем маршрута и проверьте, как его обрабатывают browser, CDN и gateway; у браузеров есть собственные ограничения.

Матрица CORS-политики: одна строка до изменения

Заполните её для одного маршрута, а не для абстрактного «API». Пример ниже — сценарный: названия и URL не описывают систему НТА или клиента.

Поле Сценарный пример Что подтвердить перед включением
Маршрут PATCH /api/contracts/{id} Владелец маршрута и класс данных
Разрешённый origin https://portal.example.ru Схема, хост и порт; владение доменом
Метод PATCH Он действительно нужен browser-клиенту
Заголовки запроса Content-Type, Authorization Их список совпадает с фактическим запросом
Credentials нет / отдельное обоснование Нужны ли cookies; если да — конкретный origin и CSRF-мера
Ответные заголовки явный allow-list Нет wildcard там, где он несовместим с контекстом
Кэш Vary: Origin; согласованный Max-Age CDN и gateway не отдадут ответ другому origin
Владелец архитектор API + владелец данных Кто одобряет новый origin и отзыв доступа
Тест разрешённый и неразрешённый origin Реальный браузерный сценарий и прикладной отказ
Пересмотр дата или событие изменения клиента Когда правило проверят заново

Матрица не должна подменять threat model, но она делает его прикладным. Если строку нельзя заполнить без слов «как-нибудь», «временно» и «для всех», маршрут не готов к расширению CORS. Это нормальный результат проверки: лучше оставить клиента в управляемой очереди до решения, чем превратить предположение в долгоживущее сетевое разрешение.

Как проверить изменение, не перепутав успех и безопасность

Проверка начинается после того, как конфигурация дошла до конечного edge или gateway. Для разрешённого origin откройте реальный UI и выполните целевое действие. В DevTools Network сравните Origin, preflight (если он возник), Access-Control-Allow-Origin, методы, заголовки, credentials и Vary. В консоли не должно быть CORS-ошибки, но её отсутствие ещё не доказывает права пользователя.

Затем выполните негативные сценарии: другой origin; другой метод; дополнительный неразрешённый заголовок; неавторизованный пользователь; пользователь без права на объект. Для первых трёх проверяется browser-политика, для двух последних — прикладной контроль. Зафиксируйте в тесте ожидаемые результаты, а не только статус 200: CORS-ошибка и корректный 403 отвечают на разные вопросы.

При изменении домена фронтенда, схемы авторизации, заголовка клиента, CDN, gateway или маршрута строка матрицы возвращается на review. У политики должен быть владелец не потому, что разработчик не умеет выставить заголовок, а потому, что только владелец может решить, кому именно организация открывает чтение ответа.

Полезно хранить рядом с записью ссылку на изменение конфигурации и результат проверки. Тогда при следующем инциденте команда не восстанавливает логику по памяти: она видит исходный потребительский сценарий, согласованный origin, дату проверки и причину исключения. Если маршрут больше не нужен браузерному клиенту, самая короткая и понятная политика — удалить CORS-разрешение после безопасной проверки зависимостей.

Так отзыв доступа становится обычным управляемым изменением, а не рискованной ручной операцией после инцидента.

Именно это уменьшает зависимость от знания одного администратора и облегчает независимую проверку следующей командой.

Что меняется в смежных решениях

Если тестовый контур берёт копии данных, отдельно проверьте, не открывает ли новый browser-маршрут доступ к лишним наборам: материал об обезличивании тестовых данных помогает сформулировать эту границу. Для маршрутов с персональными данными важны не только CORS-заголовки, но и цель, доступы, журнал и срок хранения — см. разбор утечек персональных данных. А если API становится частью ИИ-сценария, CORS не отменяет потребность в измеримых границах качества и отказа, описанную в материале о малых языковых моделях.

Тест понедельником

Выберите один browser-маршрут, который сейчас работает либо уже ждёт нового клиента. За 30 минут заполните одну строку матрицы. Не начинайте с заголовка: назовите точный origin, действие, владельца данных и причину, по которой браузер должен прочитать ответ.

После этого выполните два вызова: из разрешённого origin — с теми же методом и заголовками, что у UI; из неразрешённого — с отличающимся origin. Отдельно проверьте пользователя без права на объект. Если команда не может объяснить разницу между CORS-отказом и прикладным 403, либо не может назвать владельца нового origin, правило ещё не готово к включению.

Будущее в API работает не тогда, когда ошибка в консоли исчезла. Оно работает, когда следующий архитектор может восстановить, какой маршрут открыт, кому, почему, чем это проверено и как доступ будет отозван.

Важное уведомление

Материал носит информационный характер и не является индивидуальной консультацией, проектной документацией, инструкцией по внедрению или гарантией результата. Применимость описанных подходов зависит от процессов, систем, данных, требований безопасности и иных условий конкретной организации. Перед внедрением или изменением ИТ‑систем необходимо самостоятельно оценить риски и привлечь профильных специалистов.

«В понедельник разработчик добавляет к внутреннему порталу новый вызов PATCH /api/contracts/42

— из обсуждения проекта
— Выводы —
  • 01CORS задаётся для конкретного browser-маршрута и не заменяет аутентификацию, авторизацию или CSRF-защиту.
  • 02Preflight проверяет способ browser-вызова, поэтому реальный метод и заголовки должны входить в тест.
  • 03Политика с credentials требует явного origin, владельца и отдельной проверки; wildcard не является коротким безопасным решением.
Обновлено · 1 сентября 2026 г.

Похожая задача?
Разберём ваш контур.

Покажем, какие решения и модули подойдут под похожий сценарий.

/ Что закрыло задачу

Связанные материалы

3 материала
/ Обсуждение

Будьте первым, кто оставит комментарий

Чтобы оставить комментарий или поставить реакцию, войдите или создайте аккаунт.