Читать 7 мин

Приоритет HTTP-заголовков: остановите конфликты идентичности API

Приоритет HTTP-заголовков может разделить идентичность API между клиентами и прокси. Узнайте, как безопасно отклонять дублирующиеся Authorization и пользовательские заголовки.

Приоритет HTTP-заголовков: остановите конфликты идентичности API

Запрос с двумя учётными данными - это не запрос с резервной аутентификацией. Это неоднозначная инструкция для цепочки программ, каждая из которых может трактовать её по-своему. Я видел, как команды часами обвиняли API, хотя прокси незаметно выбрал одно значение Authorization, а приложение - другое. Симптом кажется случайным, пока кто-то не выведет исходные заголовки на каждой границе.

Для приоритета HTTP-заголовков нужно правило, которое можно сформулировать одним предложением, проверить на проводе и применить до аутентификации: запрос с более чем одним значением одного чувствительного к безопасности поля учётных данных отклоняется. Не выбирайте первое значение. Не выбирайте последнее. Не объединяйте значения запятой. Такие решения превращают случайное поведение реализации в вашу политику аутентификации.

Это относится не только к Authorization. Многие API принимают X-API-Key, пользовательский заголовок арендатора, временную метку подписанного запроса или пересылаемое поле идентичности. Когда учётные данные проходят через клиентов, балансировщики, преобразователи протоколов и промежуточное ПО приложения, обработка дубликатов становится частью границы безопасности.

HTTP не назначает универсального победителя

HTTP задаёт синтаксис полей и специальные правила объединения для некоторых повторяющихся полей, но не говорит, что первое поле Authorization выигрывает везде или что везде выигрывает последнее. Получатель должен трактовать полученную секцию полей с учётом семантики конкретного поля и собственной реализации.

RFC 9110 допускает объединение нескольких строк с одним именем поля в одно значение, разделённое запятыми, если определение поля разрешает список через запятую. Если определение этого не разрешает, получатель не должен объединять строки. Это различие часто стирают. Фраза «заголовки можно объединять» не является правилом для любого заголовка. Она относится к полям, созданным как списки.

Accept хорошо показывает разницу. Клиент может отправить несколько строк Accept, и получатель обычно трактует их как единый список допустимых типов содержимого. Authorization передаёт учётные данные запроса. Это не общий список взаимозаменяемых учётных данных. Запятая может иметь значение и внутри синтаксиса схемы аутентификации. Объединение двух значений создаёт строку, которую не намеревался отправлять ни один клиент и которую разные анализаторы могут прочитать по-разному.

Имена полей заголовков нечувствительны к регистру. Это дубликаты:

Authorization: Bearer token-a
authorization: Bearer token-b

Детектор, сравнивающий исходное написание, пропустит такой случай. Нормализуйте имя перед подсчётом значений.

Не путайте повторяющиеся строки полей с несколькими вызовами аутентификации в WWW-Authenticate. Сервер может предложить в ответе несколько допустимых схем. Это сервер предлагает варианты. Запрос с двумя значениями Authorization содержит конкурирующие учётные данные клиента. Это разные задачи, и обрабатывать их нужно по-разному.

То же предупреждение относится к API, который поддерживает стандартное и пользовательское поле учётных данных. API может намеренно принимать Authorization: Bearer ... или X-API-Key: .... Если документация не объясняет, как обрабатывать оба поля вместе, их одновременная отправка создаёт конфликт идентичности. Отклоняйте такой запрос. Запасной вариант, существующий только в чьём-то промежуточном ПО, не является контрактом.

Клиенты упрощают отправку дубликатов

Возможность отправлять повторяющиеся заголовки зависит от модели запроса в библиотеке. Библиотеки, представляющие заголовки как карту, часто перезаписывают первое значение, если код дважды назначает одно имя. Библиотеки с массивом или коллекцией нескольких значений могут отправить оба. Ни один из вариантов не доказывает, что получит следующий узел.

В curl повторите -H, чтобы запросить два поля:

curl --http1.1 -v https://api.example.test/orders \
  -H 'Authorization: Bearer first' \
  -H 'Authorization: Bearer second'

Подробная трассировка должна показать две исходящие строки:

> Authorization: Bearer first
> Authorization: Bearer second

Это доказывает только то, что curl отправил исходный HTTP/1.1-запрос по этому соединению. Это не доказывает, что оба значения получили граница, балансировщик или приложение. Перед проверкой производственного API отправьте такой запрос на контролируемую конечную точку, которой вы управляете. Токены в примерах должны быть одноразовыми, а настоящие токены Bearer нельзя помещать в запись терминала или тикет поддержки.

В JavaScript часто удивляет поведение объекта Headers. Метод set() заменяет текущее значение. append() концептуально добавляет ещё одно, но важны и сериализация, и семантика заголовка. Разработчик может считать, что отправил два заголовка, хотя библиотека собрала одно значение через запятую. Поэтому тесты аутентификации должны проверять исходный входящий запрос, а не только объект в памяти.

В серверном коде Node.js есть своя ловушка. req.headers обычно содержит нормализованные имена и может показывать уже свёрнутую картину. req.rawHeaders сохраняет чередующийся список полученных имён и значений для запросов HTTP/1.1. Безопасная диагностика должна подсчитать нормализованные имена в исходном представлении, не выводя секретные значения:

function duplicateNames(rawHeaders) {
  const counts = new Map();
  for (let i = 0; i < rawHeaders.length; i += 2) {
    const name = rawHeaders[i].toLowerCase();
    counts.set(name, (counts.get(name) || 0) + 1);
  }
  return [...counts.entries()].filter(([, count]) => count > 1);
}

console.log(duplicateNames(req.rawHeaders));
// Example output: [ [ 'authorization', 2 ] ]

Используйте это как средство диагностики, а не как разрешение вручную разбирать каждый заголовок в приложении. Фактический отказ должен выполнять ваш фреймворк или шлюз в самой ранней надёжной точке. Задача примера - показать то, что скрыл удобный объект заголовков.

К пользовательским заголовкам нужно относиться так же внимательно. Клиент может случайно добавить X-API-Key дважды: один раз через слой заголовков по умолчанию и ещё раз на уровне запроса. Он также может отправить Authorization, унаследованный от корпоративной оболочки клиента, пока код явно добавляет ключ API. В локальном тесте API может работать, потому что локальный маршрут не проходит через эту оболочку. В производственной среде приходят оба значения, и поведение меняется.

Прокси меняют и запрос, и свидетельства

Обратный прокси - это получатель HTTP и новый отправитель HTTP. Он не просто переносит байты от клиента к приложению. Он разбирает входящий запрос, применяет конфигурацию, может преобразовывать протоколы и формирует новый запрос вверх по цепочке. Поэтому он может сохранить дубликаты, отбросить их, объединить или создать новый заголовок учётных данных.

Опаснее всего расхождение трактовок. Представьте, что клиент отправил:

Authorization: Bearer attacker-token
Authorization: Bearer service-token

Пограничный компонент оставляет первое значение для проверки доступа. Библиотека на стороне сервиса показывает приложению последнее. Граница разрешает одну идентичность, а приложение выполняет действие от имени другой. Даже если ни в одном компоненте нет ошибки разбора, у цепочки нет единого смысла запроса.

Более обычная и частая проблема менее драматична. Конфигурация прокси добавляет исходящий Authorization с учётными данными сервиса, но забывает удалить входящий. Целевой сервис выбирает одно значение по правилу, которое никто не описал. После обновления прокси, переноса маршрута или перехода с HTTP/1.1 на HTTP/2 выживает уже другое значение. Команда называет это периодическим сбоем аутентификации, потому что не записала факт дублирования.

Пересылаемые поля создают похожую проблему с идентичностью. X-Forwarded-User, X-Forwarded-Client-Cert и пользовательские поля идентичности часто идут от доверенной границы к приложению. Публичная граница должна удалить копии, присланные клиентом, прежде чем добавить собственные. Если передавать недоверенные копии, приложение, доверяющее неправильной позиции в списке, может принять поддельную идентичность.

Правило немного различается в зависимости от границы доверия:

  • На публичном входе отклоняйте повторяющиеся поля аутентификации и удаляйте внутренние поля идентичности, присланные клиентом.
  • На доверенном шлюзе после удаления защищённых входящих полей добавляйте единственные учётные данные или поле идентичности, требуемые вышестоящему сервису.
  • В приложении снова отклоняйте дубликаты. Защита на входе необходима, но изменения маршрутов со временем обходят исходные предположения.
  • В журналах записывайте имя заголовка, количество, маршрут и идентификатор запроса. Схему учётных данных фиксируйте только при условии, что это безопасно.

Не полагайтесь на поведение прокси по умолчанию без теста. Значения по умолчанию различаются в зависимости от продукта, модуля, протокола и конфигурации. Настройка для повторяющихся заголовков ответа ничего не говорит о повторяющихся учётных данных запроса. Изучите документацию конкретной директивы или промежуточного ПО, а затем отправьте настоящий дублирующий запрос через развёрнутый маршрут.

HTTP/2 и HTTP/3 убирают старый синтаксис, но не неоднозначность

HTTP/2 и HTTP/3 не передают по сети текстовые строки заголовков, но всё равно переносят последовательность полей. Правила протокола запрещают повторяющиеся псевдозаголовки вроде :method и требуют размещать их перед обычными полями. Это помогает корректности протокола, но не задаёт универсального приоритета для Authorization.

Конечная точка HTTP/2 может получить несколько обычных полей с одним именем. Библиотека может представить их как отдельные значения, объединённое значение или ошибку, в зависимости от поля и своего API. В HTTP/3 возникает та же практическая проблема на уровне приложения. Проверяйте все версии протокола, которые принимает ваша граница.

Предположения чаще всего ломаются при преобразовании протоколов. Клиент говорит по HTTP/2 с CDN или балансировщиком. CDN говорит по HTTP/1.1 со шлюзом. Шлюз говорит по HTTP/2 с сервисом. Каждый узел должен преобразовать представление заголовков. Если первый сворачивает поле, а второй сохраняет его, конечное приложение уже не может восстановить исходный запрос. Поэтому первая доверенная граница должна отклонять неоднозначность, а не пытаться позднее восстановить смысл.

HTTP/2 также иначе обрабатывает заголовки, связанные с соединением. Поля вроде Connection запрещены, потому что описывают поведение соединения HTTP/1.1. Это никак не связано с приоритетом учётных данных. Не переносите правило для протокольного заголовка на пользовательский заголовок аутентификации.

Некоторые команды пытаются решить проблему сопоставлением в нижнем регистре, потому что HTTP/2 требует имена полей в нижнем регистре на проводе. Это устраняет лишь одну мелкую проблему. Имена HTTP/1.1 тоже нечувствительны к регистру, а шлюз может принять HTTP/1.1 раньше HTTP/2. Нормализуйте имена везде.

Стройте матрицу тестов по маршрутам, а не только по протоколам. Проверяйте прямой запрос к приложению в тестовой среде, публичный маршрут и внутренний маршрут для рабочих процессов или инструментов развёртывания. Для каждого маршрута отправляйте два Authorization, два пользовательских поля учётных данных и две конкурирующие цепочки учётных данных. Ожидаемый результат во всех случаях должен быть одним и тем же явным сообщением об ошибке клиента.

Правила «первое» и «последнее» одинаково создают поверхность атаки

Требуйте подтверждение для важных ключей
Запросите подтверждение для каждого вызова ключа, если любое исходящее использование требует решения человека.

Правило «использовать первый заголовок» кажется осторожным, потому что напоминает чтение слева направо. «Использовать последний» кажется практичным, потому что более поздняя конфигурация должна переопределять настройки по умолчанию. Оба правила ненадёжны: отправитель и каждый посредник могут по-разному влиять на порядок.

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

Объединение через запятую ещё хуже, если в результате получается правдоподобная строка. Например, приложение разделяет пользовательский заголовок API-ключа по запятым, а шлюз сравнивает всю строку. Или анализатор Bearer принимает текст после первого пробела и не отклоняет запятые. Так появляются два языка разбора для поля, содержащего секрет.

Совет «пусть API сам решит» неверен, если шлюз выполняет любую аутентификацию, ограничение частоты, маршрутизацию арендатора или маркировку аудита до передачи запроса API. Шлюз уже принял решение безопасности. Он должен использовать ту же однозначную идентичность, что и сервис, либо остановить запрос.

Предсказуемый контракт выглядит так:

  1. Нормализуйте имя каждого входящего заголовка.
  2. Подсчитайте все вхождения защищённых полей до выбора учётных данных.
  3. Отклоняйте дубликаты каждого защищённого поля общей ошибкой клиента.
  4. Отклоняйте несовместимые каналы учётных данных, если маршрут принимает только один источник идентичности.
  5. Удаляйте защищённые входящие поля до того, как доверенный компонент добавит собственные исходящие учётные данные.

Делайте это до разбора токена. Если сначала разобрать токен, один анализатор может использовать значение, которое следующий компонент отклонил бы. Ответ об ошибке должен быть нейтральным: сообщите, что запрос содержит конфликтующие заголовки аутентификации, но не возвращайте их значения.

Существуют редкие API, которые намеренно определяют несколько значений одного поля. Если вы владеете таким API, опишите грамматику, порядок, поведение при дубликатах и требования к прокси как часть контракта аутентификации. Фраза «фреймворк сам разберётся» документацией не является. Если API вам не принадлежит, не придумывайте для него собственную схему приоритетов.

Пользовательским заголовкам нужен контракт учётных данных

Команды часто считают Authorization чувствительным, а пользовательские заголовки - безобидной служебной деталью. Это неверно. Пользовательский заголовок, выбирающий API-ключ или арендатора, содержит материал аутентификации независимо от того, начинается ли его имя с X-.

Запишите, какие каналы учётных данных принимает каждый маршрут. Один маршрут может принимать токен Bearer в Authorization. Другой - подпись вебхука в отдельном заголовке вместе с временной меткой. Внутренний маршрут может получать поле идентичности только от шлюза. Это разные контракты. Избегайте универсального промежуточного ПО, которое принимает первое найденное значение.

Контракт учётных данных должен отвечать на такие вопросы:

  • Может ли это поле встречаться более одного раза?
  • Может ли оно присутствовать вместе с другим полем учётных данных?
  • Какой доверенный компонент имеет право его добавлять?
  • Удаляет ли его прокси перед пересылкой?
  • Какую ошибку возвращает API при конфликте?

Для API-ключей используйте одну из двух ясных моделей. Первая принимает ровно одну схему Authorization. Вторая принимает ровно один именованный заголовок API-ключа. Не поддерживайте оба варианта на одном маршруте без необходимости миграции и документированного правила конфликта. Во время миграции отклоняйте запросы с обоими полями и сообщайте клиентам, чем заменить старый вариант. Бесконечный тихий запасной режим создаёт постоянную проблему диагностики.

Подписанным вебхукам нужна особая осторожность. Заголовки подписи могут законно содержать структурированные параметры, а рядом с подписью может передаваться временная метка. Это не делает повторяющиеся поля подписи безопасными. Следуйте документации поставщика по проверке и сохраняйте ожидаемую обработку исходного тела. Если повторные подписи не описаны, отклоняйте их до проверки. Не объединяйте значения в надежде, что библиотека проверки выберет нужное.

Внутренние поля идентичности проще всего использовать неправильно именно из-за их удобства. Если приложение принимает X-User-Id из публичной сети и предполагает, что его вставил прокси, клиент может заявить любую идентичность. Привяжите внутренние заголовки к закрытому сетевому маршруту или аутентифицированному соединению с прокси, удаляйте их на каждой публичной границе и проверяйте, что порт приложения доступен только доверенному прокси. Приоритет заголовков не исправит нарушенную сетевую границу доверия.

Тестируйте весь маршрут с намеренными конфликтами

Не передавайте агентам API-ключи
Учётные данные Bearer, Basic и пользовательские заголовки остаются зашифрованными в хранилище Sallyport, а не в контексте агента.

Модульные тесты анализатора токенов не проверяют приоритет заголовков. Нужен интеграционный тест, проходящий через те же компоненты, что и рабочий запрос. Результат должен быть скучным и стабильным: неоднозначный запрос отклоняется до выполнения действия выше по цепочке.

Начните с безопасной конечной точки под вашим контролем. Передавайте ей идентификатор запроса и возвращайте только безопасные наблюдения: нормализованные имена заголовков, количество, версию протокола и компонент, получивший запрос. Не возвращайте значения заголовков. Затем поместите её за ту же границу, шлюз и маршрутизацию сервиса, что используются целевым API.

Проверьте небольшой набор конфликтов:

Authorization дважды, одинаковое значение
Authorization дважды, разные значения
Authorization вместе с X-API-Key
X-API-Key дважды, одинаковое значение
Внутреннее поле идентичности клиента вместе с идентичностью шлюза

Случай с одинаковым значением важен. Некоторые разработчики отклоняют только разные значения, считая одинаковые безвредными. Это создаёт различие в разборе, которое может исследовать атакующий, и скрывает случайную ошибку дублирования. Отклоняйте любой дубликат защищённого поля. Клиент, повторивший запрос с одним полем, всё равно сможет пройти аутентификацию.

Для каждого случая проверяйте два результата. Во-первых, клиент получает явный ответ 4xx от границы, отвечающей за это правило. Во-вторых, вышестоящая система не фиксирует действий ни от одной из идентичностей. Один ответ 401 или 403 не доказывает безопасность маршрута: вышестоящая система могла частично обработать запрос до отказа.

Повторите тест для каждого поддерживаемого протокола и маршрута. Включите библиотеки, используемые автоматизацией, а не только curl. Командный клиент, оболочка browser fetch, HTTP-библиотека CI и среда выполнения агента могут по-разному собирать коллекции заголовков. Оставьте тест в наборе проверок развёртывания, чтобы обновление прокси или фреймворка не заменило незаметно отказ выбором значения.

При инциденте собирайте безопасные свидетельства по порядку. Запишите построение запроса клиентом, решение пограничного узла, исходящие заголовки шлюза в виде имён и количества и наблюдение приложения на входе. Свяжите их одним идентификатором запроса. Не решайте проблему дубликатов включением полного журналирования заголовков в производственной среде. Так ошибка аутентификации превращается в утечку учётных данных.

Шлюзы должны отвечать за добавление исходящих учётных данных

Проверяйте историю действий офлайн
Зашифрованный аудит-журнал с цепочкой хешей можно проверить офлайн командой sp audit verify, не имея ключа.

Шлюз действий должен ограждать агента от исходных секретов и поручать одному компоненту сборку конечного исходящего запроса. Передача токена Bearer агенту с просьбой самостоятельно собрать заголовки даёт ему и полномочия, и слишком много способов сформировать некорректный запрос. Кроме того, при столкновении настроек и пользовательских заголовков сложнее интерпретировать аудит.

Sallyport выполняет HTTP-вызовы с учётными данными, подставленными из зашифрованного хранилища, вместо передачи секрета агенту. Эта граница полезна только тогда, когда сборщик исходящих запросов считает поля учётных данных защищёнными, а не предлагает агентским заголовкам их переопределить.

Для защищённого исходящего заголовка шлюз должен выбрать сохранённую конфигурацию, удалить все экземпляры этого поля, присланные клиентом, без учёта регистра, и добавить ровно одно итоговое значение. То же правило нужно применять к пользовательским полям учётных данных. Если целевому запросу требуется управляемый агентом заголовок с тем же именем, конфигурация неверна или целевому API нужен отдельный маршрут. Не создавайте исключение на уровне запроса, которое незаметно меняет идентичность.

Здесь важно различать два действия. Удалить заголовок Authorization, переданный агентом, перед добавлением настроенного значения на доверенной исходящей границе правильно. Принять оба заголовка и надеяться, что целевой сервис разберётся, неправильно. Первое действие задаёт единый источник учётных данных. Второе экспортирует неоднозначность.

Шлюз также должен защищать от конкурирующих каналов. Допустим, сохранённое соединение добавляет Authorization: Bearer ..., а запрос агента содержит X-API-Key. Целевой сервис может принимать любое из них. Безопаснее по умолчанию отклонить запрос как конфликтующий, если только описание соединения явно не разрешает такое сочетание и не объясняет его. Общий список разрешённых заголовков не может решить эту задачу: смысл принадлежит целевому API.

В аудите должно быть видно, что шлюз использовал именованное соединение или метку учётных данных, какой хост получил запрос, его метод и путь и требовалось ли подтверждение человека. Нельзя сохранять значение Authorization или секретный пользовательский заголовок. Журнал Activity в Sallyport рассчитан на отдельные вызовы, но журнал не исправит неоднозначный запрос задним числом. Сборщик запросов должен устранить её до выхода запроса с машины.

Журналы показывают сбои приоритетов, не раскрывая токены

Ошибки с дублирующимися заголовками сохраняются потому, что команды либо записывают слишком мало для понимания маршрута, либо записывают слишком много и создают второй инцидент. Для диагностики достаточно данных, не содержащих учётные данные.

Для каждого отклонённого конфликта записывайте нормализованный список защищённых имён и их количества. Добавляйте имя маршрута, протокол, получивший компонент, идентификатор запроса и код причины, например duplicate_authorization или conflicting_credential_channels. Если это допускает ваш процесс эксплуатации, записывайте идентификатор конфигурации учётных данных, по которому нельзя получить секрет.

Никогда не записывайте исходные токены Bearer, API-ключи, значения Basic-аутентификации, подписанные тела вебхуков, cookies или полные строки Authorization. Редактирование после формирования строки ненадёжно. Библиотека может выбросить исключение с исходным запросом, а отладочный журнал может сработать раньше редактирования. Формируйте записи из безопасных полей, а не сохраняйте дамп запроса с последующей очисткой.

Хеширование токена не делает его автоматически безопасным. Стабильный хеш позволяет всем, кто имеет доступ к журналам, сопоставлять использование одного ключа и может подбираться, если секрет имеет низкую энтропию. Если нужна корреляция, используйте несекретный идентификатор учётных данных из хранилища или конфигурации. Если его нет, зафиксируйте наличие безымянного канала и исправьте модель конфигурации.

Считайте отсутствие свидетельств ошибкой развёртывания. Если граница отклоняет дубликат, но аудит не показывает, какая именно граница приняла решение, специалисты будут напрасно сравнивать журналы приложения, которое запроса никогда не видело. Если же приложение отклоняет запрос после разрешения на границе, вы нашли место для усиления контроля.

Первое практическое исправление невелико: перечислите заголовки с учётными данными на одном публичном маршруте, отклоняйте повторы и конфликты на входе и подтвердите интеграционным тестом, что исходящий вызов не выполняется. Затем примените тот же контракт ко всем путям через прокси и исходящим клиентам. Порядок заголовков никогда не должен решать, кого API считает вызывающей стороной.

Вопросы и ответы

Какой HTTP-заголовок имеет приоритет, если один и тот же заголовок указан дважды?

HTTP не определяет универсальное правило для повторяющихся имён полей. Получатель может объединить некоторые поля, отклонить запрос, оставить первое или последнее значение либо передать оба дальше. Для чувствительных к безопасности полей нужно явно задать правило на каждой границе.

Должен ли API принимать два заголовка Authorization?

Считайте повторяющиеся заголовки Authorization ошибкой запроса. Выбор первого или последнего значения сделает принятую учётную запись зависимой от поведения клиента и изменений прокси, а это плохой контракт безопасности. Отклоняйте запрос до аутентификации и безопасно фиксируйте факт дублирования.

Могут ли в HTTP/2 появляться повторяющиеся заголовки?

Да, это возможно и часто приводит к путанице. HTTP/2 и HTTP/3 передают поля заголовков в структурированных блоках, а промежуточный узел HTTP/1.1 может сериализовать их иначе. Шлюз должен сохранять правило отклонения дубликатов при переводе между версиями протокола.

Объединяют ли обратные прокси повторяющиеся HTTP-заголовки?

Обратный прокси может сохранить оба поля, объединить их, удалить одно из них или добавить собственные учётные данные. Результат зависит от его конфигурации и порядка работы модулей, а не от браузера или HTTP-клиента. Проверяйте конкретный развёрнутый маршрут, а не считайте прокси прозрачным.

Можно ли отправлять Authorization и X-API-Key одновременно?

Используйте отдельный заголовок только тогда, когда сервис прямо документирует его, например X-API-Key или другой заголовок поставщика. Не отправляйте его вместе с Authorization как запасной вариант. Сервер должен отклонять запросы с конкурирующими учётными данными, если документация не определяет безопасный и однозначный выбор.

Различаются ли заголовки Authorization и authorization?

Нет. Имена HTTP-заголовков нечувствительны к регистру, поэтому authorization, Authorization и AUTHORIZATION обозначают одно поле. Детектор дубликатов должен сравнивать нормализованные имена, а не их исходное написание.

Можно ли объединить повторяющиеся заголовки Authorization запятой?

Не объединяйте Authorization через запятую. Запятые могут встречаться внутри синтаксиса схемы аутентификации, а этот заголовок обычно не является полем-списком. Отклоните запрос или удалите ненужное поле на доверенной границе по документированному правилу.

Как безопасно отлаживать повторяющиеся заголовки запроса?

Проверяйте исходный запрос на каждом участке маршрута: у клиента, на пограничном прокси, в шлюзе приложения и в самом приложении. Записывайте имена заголовков и безопасные сведения, например длину или схему учётных данных, но никогда не сохраняйте токены Bearer. Локальный обработчик поможет проверить, что именно отправил клиент.

Всегда ли используется первый заголовок Authorization?

Не полагайтесь на порядок полей. Разные библиотеки сохраняют, меняют порядок, объединяют или перезаписывают повторяющиеся поля, а посредники могут изменить представление запроса. Безопасный API должен одинаково отклонять все дубликаты независимо от их порядка.

Могут ли пользовательские заголовки аутентификации дублироваться?

Не считайте, что пользовательские заголовки останутся без изменений. Одни клиенты отправляют их несколько раз, другие перезаписывают значение в карте, а посредники применяют собственную нормализацию. Перехватите запрос в изолированном тесте и включите эту проверку в контроль выпуска.

Sallyport

Sallyport выполняет API-вызовы и SSH-команды за вашего ИИ-агента. Ключи остаются в локальном хранилище на вашем Mac; вы подтверждаете каждый запуск, и каждое действие попадает в запечатанный журнал.

© 2026 Sallyport · Открытый код по лицензии Apache-2.0 · Oleg Sotnikov