← Все статьи

User-Agent Client Hints - теперь у вас три сигнала, которые обязаны совпадать

3 мин чтения fingerprintingbrowser-internalsguide

Браузеры перестали класть большую часть полезных подробностей о платформе прямо в заголовок User-Agent. Эти подробности не исчезли, они переехали в отдельный механизм под названием User-Agent Client Hints, разделённый между набором заголовков запроса и JavaScript-API. Для тех, кто строит или оценивает автоматизацию браузера, практическое следствие не в том, что снимать отпечатки стало труднее. Оно в том, что теперь есть три места, где браузер описывает собственную платформу, и все три обязаны говорить одно и то же.

Два уровня подсказок

Client Hints разделены на низкоэнтропийный и высокоэнтропийный уровни, и разделение существует потому, что высокоэнтропийный раскрывает достаточно, чтобы сузить круг до конкретного устройства, поэтому браузер выдаёт его только по явному запросу.

Низкоэнтропийные подсказки прикрепляются к запросам автоматически, без всякого согласия:

Высокоэнтропийные подсказки отправляются, только если сервер их запросил заголовком ответа Accept-CH (или эквивалентным метатегом), после чего браузер включает их в последующие запросы к этому источнику. Сюда входят полная версия платформы, архитектура процессора, разрядность, модель устройства и полный список брендов и версий вместо сокращённого. Те же данные доступны JavaScript страницы через navigator.userAgentData.getHighEntropyValues(), который возвращает промис, чтобы браузер мог ограничивать или притормаживать доступ к ним.

Три места, которые обязаны совпадать

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

  1. Сама строка User-Agent - замороженная и урезанная, но всё ещё присутствующая и всё ещё проверяемая серверами, которые не перешли на Client Hints.
  2. navigator.userAgentData - читается JavaScript страницы, включая любой скрипт детекта, который крутит сайт.
  3. Заголовки запроса Sec-CH-UA-* - читаются на стороне сервера, независимо от того, что сообщает JavaScript страницы.

Настоящий немодифицированный браузер порождает все три из одного внутреннего состояния, поэтому они всегда согласованы. Подход к подмене, который правит только одно из них, чаще всего слой JavaScript как самый доступный для инъекции скрипта, оставляет два других показывать настоящие значения браузера. Сайт, сверяющий navigator.userAgentData.platform с полученным заголовком Sec-CH-UA-Platform или с подстрокой, всё ещё присутствующей в User-Agent, ловит несовпадение мгновенно. Это несовпадение - сигнал сильнее, чем был бы обычный немодифицированный отпечаток, потому что настоящее устройство никогда не противоречит само себе в том, что оно такое. Залатать один слой и забыть про два других - именно так попытка подмены изготавливает ровно то противоречие, которое ищет скрипт детекта.

Выгрузите все три сами

Выполните это в консоли браузера, чтобы увидеть, что сообщает JavaScript:

(async () => {
  console.log('User-Agent string:', navigator.userAgent)
  console.log('userAgentData (low-entropy):', navigator.userAgentData)
  const high = await navigator.userAgentData.getHighEntropyValues([
    'platform', 'platformVersion', 'architecture', 'bitness', 'model', 'fullVersionList'
  ])
  console.log('userAgentData (high-entropy):', high)
})()

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

// минимальный обработчик Node http или любой маршрут Express/Astro
export function logClientHints(req) {
  console.log('User-Agent:', req.headers['user-agent'])
  console.log('Sec-CH-UA:', req.headers['sec-ch-ua'])
  console.log('Sec-CH-UA-Mobile:', req.headers['sec-ch-ua-mobile'])
  console.log('Sec-CH-UA-Platform:', req.headers['sec-ch-ua-platform'])
}

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

Когда всё это можно не трогать

Если ваш целевой сайт читает только обычный заголовок User-Agent и никогда не выставляет Accept-CH и не запрашивает navigator.userAgentData в своих скриптах, ничего из вышеописанного не участвует: проверяется только замороженная строка, и согласовывать больше нечего. Большинство простого скрапинга и малообъёмной автоматизации вообще не встречает сайтов, запрашивающих высокоэнтропийные подсказки, а строить логику согласованности для сигнала, который цель никогда не читает, - потраченный впустую труд. Проверьте, что цель реально запрашивает, прежде чем считать, что вам нужно управлять всеми тремя.

Поскольку AntiBrow применяет отпечаток в самом движке браузера, а не через инъекцию скрипта, строка User-Agent, navigator.userAgentData и заголовки Sec-CH-UA-* порождаются из одной личности и остаются согласованными по построению, в том числе после перезапуска. Общая проверка согласованности, охватывающая не только этот сигнал, - в чек-листе согласованности отпечатка. Что такое отпечаток и почему его вообще оценивают - в статье что такое отпечаток браузера.

Попробуйте на бесплатном тарифе.

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