Ainda dá para confiar na string de user agent?
Veja quais dados do user-agent são confiáveis, quais os navegadores congelam e como Client Hints do Chromium e a detecção de recursos evitam análises frágeis.
Em parte. A string de user agent ainda informa com confiabilidade a família do navegador, a versão principal, se o dispositivo é móvel ou desktop e a família do sistema operacional. Ela não informa com confiabilidade a versão do sistema operacional, o modelo do dispositivo, a arquitetura da CPU nem, nos navegadores Chromium, a versão secundária do navegador.
Se você encontrou Android 10; K nos seus logs vindo de um celular que claramente não roda Android 10, ou Mac OS X 10_15_7 vindo de um MacBook com chip da série M, não há nada quebrado. Esses valores são placeholders, e os navegadores os enviam de propósito.
Este artigo desmonta uma string atual do Chrome e mostra quais campos estão congelados em cada engine. Em seguida, explica o que substitui a string no Chromium e quando ainda faz sentido fazer o parsing dela.
Principais conclusões
- Todos os principais navegadores ainda começam a string de User-Agent com
Mozilla/5.0, um token de compatibilidade que não diz nada sobre o navegador que a envia. - O Chrome informa
Windows NT 10.0; Win64; x64,Macintosh; Intel Mac OS X 10_15_7,X11; Linux x86_64ouLinux; Android 10; K, independentemente da versão real do sistema operacional. Os números secundário, de build e de patch são sempre0.0.0. - A partir do Safari 26, o Safari no iOS, iPadOS e visionOS informa uma versão congelada do sistema operacional. O Safari no Mac congelou a versão do macOS desde 2017.
- As User-Agent Client Hints existem apenas em navegadores baseados em Chromium. Firefox e Safari não enviam nenhum header
Sec-CH-UA-*. - Qualquer cliente pode enviar qualquer User-Agent, então o header só filtra os bots que se identificam.
O que uma string de user agent do Chrome realmente diz?
Apenas um segmento da string de UA atual do Chrome para desktop muda entre versões: a versão principal. Todo o resto é um token de compatibilidade fixo ou um valor de plataforma congelado. Este é o Chrome 154 no Windows:
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36
| Segmento | O que afirma | É verdade? |
|---|---|---|
Mozilla/5.0 | Um navegador compatível com Mozilla | Sem significado. Todos os navegadores enviam |
(Windows NT 10.0; Win64; x64) | Windows 10, x86 de 64 bits | Congelado. Windows 11 e máquinas ARM enviam o mesmo valor |
AppleWebKit/537.36 (KHTML, like Gecko) | Uma engine WebKit derivada do KHTML e semelhante ao Gecko | Resquício de compatibilidade. A engine do Chrome é o Blink |
Chrome/154.0.0.0 | Chrome 154.0.0.0 | A versão principal é real. 0.0.0 é um placeholder |
Safari/537.36 | Safari | Não é Safari. Permanece para que códigos que procuram “Safari” continuem encontrando |
A referência do Firefox no MDN descreve Mozilla/5.0 como um token genérico que declara compatibilidade com Mozilla. Quase todos os navegadores o enviam, seja qual for a engine que usam. A referência de User-Agent do MDN confirma que os navegadores baseados em Blink carregam KHTML, like Gecko e Safari apenas como tokens de compatibilidade. Em uma string do Chrome, AppleWebKit/537.36 (KHTML, like Gecko) e Safari/537.36 não descrevem nem a engine do Chrome nem o Safari. São tokens fixos, mantidos para que códigos antigos de sniffing continuem funcionando. Para ver a mesma análise da sua própria string, cole-a no parser de user agent da OpenReplay.
Quais campos do user agent estão congelados e quais ainda são confiáveis?
As três engines congelaram as partes de alta entropia da string: versão do sistema operacional, modelo do dispositivo e arquitetura da CPU. O Chromium também zera a versão secundária do navegador. As partes de baixa entropia continuam refletindo o navegador real. O plano de User-Agent Reduction do Chrome começou a fixar os números secundário, de build e de patch em 0.0.0 no Chrome 101 (2022). O deprecation trial que permitia aos sites manter a string completa terminou em 23 de setembro de 2023, e desde então todo carregamento de página recebe a string reduzida. O guia de redução do UA do MDN lista os valores de plataforma fixos, incluindo Android 10; K no Android.
| Chrome / Edge | Firefox | Safari | |
|---|---|---|---|
| Família do navegador, versão principal | Real | Real | Real (Version/) |
| Móvel vs. desktop, família do SO | Real | Real | Real |
| Versão do SO | Congelada | Limitada (macOS 10.15, Android 10) | Congelada (macOS; iOS desde a 26) |
| Modelo do dispositivo | K no Android | Nunca enviado | Nunca enviado |
| Arquitetura da CPU | Congelada | Congelada | Mac sempre “Intel” |
| Versão secundária | 0.0.0 | Não exposta | Real (Version/) |
| UA Client Hints | Sim | Não | Não |
O Edge usa os mesmos tokens congelados do Chrome e acrescenta Edg/. Desde o Firefox 87, o Firefox informa todas as versões do macOS a partir do Big Sur como 10.15 e identifica Macs com Apple Silicon como Intel. Desde o Firefox para Android 122, ele informa Android 10, qualquer que seja a versão real. Segundo o post de lançamento do Safari 26.0 no blog do WebKit, o Safari no Mac envia o mesmo valor Intel Mac OS X 10_15_7 desde 2017. A partir do Safari 26 (setembro de 2025), o Safari no iOS, iPadOS e visionOS também passou a informar uma versão congelada do iOS 18 em vez da versão real. O Safari 26.0 enviava 18_6. A partir do Safari 26.2, o WebKit fixa o valor na última versão do iOS 18, então as strings atuais enviam 18_7. O token Version/ continua sendo atualizado a cada versão.
A redução do Chromium abrange o Chrome no Windows, macOS, Linux, ChromeOS e Android. Ela não abrange o Android WebView nem o Chrome para iOS. Na prática:
- Windows 10 e Windows 11 aparecem de forma idêntica.
- Macs com Apple Silicon informam “Intel” nas três engines.
- Uma string do Chrome para Android com
Android 10; Knão descreve um dispositivo com Android 10. Todo Chrome no Android envia essa versão e o modelo placeholderK.
Por que a detecção de recursos é melhor que a detecção de navegador?
O nome de um navegador não diz nada confiável sobre o que ele consegue fazer. A detecção de recursos (feature detection) verifica a capacidade diretamente, e isso já era verdade antes de qualquer string ser congelada. Uma verificação baseada no UA falha quando a string mente, quando um novo navegador passa a oferecer a API ou quando uma versão antiga não a tem. O Baseline oferece uma visão entre navegadores de quando é seguro depender de um recurso. Quando não é, uma verificação em tempo de execução cobre a lacuna:
// Brittle: guesses capability from a name
if (/Chrome\/\d+/.test(navigator.userAgent)) enableShareButton();
// Direct: asks the browser
if ('share' in navigator) enableShareButton();
Como funcionam as User-Agent Client Hints?
As User-Agent Client Hints são um conjunto de headers de requisição Sec-CH-UA-* que os navegadores baseados em Chromium enviam para fornecer os detalhes que a string de UA não carrega mais. Chrome e Edge enviam Sec-CH-UA, Sec-CH-UA-Mobile e Sec-CH-UA-Platform por padrão. Firefox e Safari não enviam nenhum deles. O MDN classifica o Sec-CH-UA-Platform como uma hint de baixa entropia. Por isso, o navegador o inclui sem que o servidor precise pedir, a menos que uma permissions policy o bloqueie. Todas as outras hints precisam ser solicitadas.
Para solicitar hints, o servidor as lista em Accept-CH, e o navegador as inclui nas requisições seguras subsequentes para aquela origem.
O Accept-CH não tem efeito na primeira requisição. Para receber uma hint de alta entropia já na primeira requisição, o servidor deve nomeá-la em Critical-CH, além de em Accept-CH. Em vez de renderizar essa primeira resposta, o navegador reenvia a requisição, desta vez com a hint. O servidor também deve adicionar a hint ao header Vary, para que os caches armazenem cada versão separadamente.
import https from 'node:https';
import { readFileSync } from 'node:fs';
https.createServer(
{ key: readFileSync('key.pem'), cert: readFileSync('cert.pem') },
(req, res) => {
const platform = req.headers['sec-ch-ua-platform']; // default hint
const version = req.headers['sec-ch-ua-platform-version']; // opt-in only
res.setHeader('Accept-CH', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch');
res.setHeader('Critical-CH', 'Sec-CH-UA-Platform-Version');
res.setHeader('Vary', 'Sec-CH-UA-Platform-Version, Sec-CH-UA-Arch');
res.setHeader('Content-Type', 'text/plain');
res.end(`platform=${platform ?? 'n/a'} version=${version ?? 'n/a'}\n`);
}
).listen(8443);
O Node converte os nomes dos headers para minúsculas. Os valores chegam como strings de structured fields entre aspas, como "Windows". No navegador, navigator.userAgentData.getHighEntropyValues() retorna os mesmos dados sem nenhuma configuração de headers. O MDN marca uaFullVersion como obsoleto em favor de fullVersionList.
async function describeClient() {
const uaData = navigator.userAgentData;
if (!uaData) return { source: 'ua', ua: navigator.userAgent }; // Firefox, Safari
const high = await uaData.getHighEntropyValues([
'platformVersion', 'architecture', 'model', 'fullVersionList',
]);
return { source: 'ua-ch', brands: uaData.brands, mobile: uaData.mobile, ...high };
}
Quando ainda faz sentido fazer o parsing da string de user agent?
O parsing ainda faz sentido quando você só precisa dos campos confiáveis e quando um erro custa pouco.
Agrupamentos de analytics. O parsing do user agent é preciso para agrupamentos de analytics como “Chrome 154, desktop, Windows”. Um agrupamento “Windows 10” ou “Android 10” não é: ele absorve silenciosamente as versões mais recentes, então trate-o apenas como a família do sistema operacional. Verifique Edg/ antes de Chrome/, e Chrome/ antes de Safari/, porque cada string contém os tokens que vêm depois dela nessa lista.
Filtragem de bots. A RFC 9110 define o User-Agent como um header fornecido pelo cliente, e nada o verifica. Qualquer cliente pode enviar qualquer valor. O header identifica os crawlers que se apresentam e não faz nada contra os bots que não se identificam.
Tickets de suporte. Quando um usuário reporta um bug, normalmente você precisa saber aproximadamente o que ele estava usando, não o build exato. Ferramentas de session replay registram o UA em cada sessão, o que basta para ver que um bug se reproduz no Firefox no macOS, mas não no Chrome. Registrar junto o resultado das suas verificações de recursos restringe ainda mais o problema.
Conclusão
Você pode confiar na string de user agent para a família do navegador, a versão principal, a distinção entre móvel e desktop e a família do sistema operacional. Trate todo o resto como placeholder. Para colocar essas conclusões em prática, audite seu código em busca de qualquer ramificação que leia da string a versão do sistema operacional, o modelo do dispositivo ou a versão secundária. Substitua as verificações de capacidade baseadas no UA por detecção de recursos. Quando você realmente precisar de detalhes da plataforma, use as Client Hints, com um fallback para Firefox e Safari, que não as enviam.
Perguntas frequentes
Como diferenciar o Windows 11 do Windows 10 se o user agent diz Windows NT 10.0?
Solicite a client hint platformVersion, seja com navigator.userAgentData.getHighEntropyValues(['platformVersion']) ou enviando Accept-CH: Sec-CH-UA-Platform-Version. A Microsoft documenta os valores de 1.0.0 a 10.0.0 como Windows 10 e 13.0.0 ou superior como Windows 11, e o código de exemplo dela trata uma versão principal igual ou superior a 13 como Windows 11. O Firefox não envia Client Hints, então não é possível distinguir os dois nele.
Por quanto tempo um navegador continua enviando as hints solicitadas com Accept-CH?
O Chrome salva em disco as preferências de Accept-CH de cada origem e, desde o Chrome 103, elas não têm prazo de expiração fixo. Elas duram até o usuário limpar os cookies ou os dados do site daquela origem, e também são apagadas junto com os cookies de sessão. Um servidor pode reenviar o Accept-CH para substituir a lista, enviar um Accept-CH vazio para interromper todas as hints ou enviar Clear-Site-Data: 'clientHints'.
Por que o header Sec-CH-UA contém uma marca como Not A;Brand?
É uma entrada deliberadamente falsa, conhecida como GREASE. O Chromium adiciona uma marca intencionalmente incorreta, com um número de versão baixo, e varia a pontuação e a posição dela na lista. Isso obriga os servidores a fazer o parsing correto do header, em vez de comparar com uma string fixa ou uma lista fixa de marcas. Faça o parsing do Sec-CH-UA como uma lista de structured fields, procure a marca de que você precisa, como Chromium, Google Chrome ou Microsoft Edge, e ignore qualquer entrada que você não reconheça.
Por que meu parser de user agent identifica o Chrome no iPhone como Safari?
O Chrome para iOS envia a string de user agent do Mobile Safari com um token CriOS/ no lugar de Version/, então a string não tem o token Chrome/. O Firefox para iOS faz o mesmo com FxiOS/. Um parser que procura apenas Chrome/ e Firefox/ acaba caindo no Safari, então verifique CriOS/ e FxiOS/ primeiro. A redução do UA do Chromium não abrange o Chrome para iOS.