Skip to content
SnapFrom

What Replaced the User-Agent String

· · 6 min read

Your browser sends a User-Agent string on every request. For most of the web’s history it was a long, messy line that named your browser, its exact version, your operating system and often your device. It was also, in MDN’s words, something that “can be used to identify a particular user agent, and can therefore be used for fingerprinting”[9].

So it was cut down. Chrome now sends a User-Agent that looks like Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36[12] - the device name replaced with K, the Android version pinned, the Chrome version rounded to a major number with zeroes after it. Firefox and Safari trimmed their strings on the same reasoning[9].

The detail did not vanish. It moved to a second channel, one the server switches on, and asking on it has costs the person on the other end never sees.

What still goes out for free

A small set of facts is sent by default, with no request from the server. MDN calls these the low entropy hints, “those that don’t give away much information that might be used to fingerprint a user”[8]. There are four: Save-Data, and three that describe the browser[8].

  • Sec-CH-UA carries “the user-agent’s branding and significant version information”[3] - the marketing major version, not the full build number.
  • Sec-CH-UA-Mobile is a single bit: ?1 for a device that “prefers a mobile experience”, ?0 otherwise[4].
  • Sec-CH-UA-Platform is one string from a fixed list: "Android", "iOS", "Linux", "macOS", "Windows" and a few others[5].

Each is “sent by default, without the server opting in by sending Accept-CH[3]. The specification is explicit that this is deliberate: they “will be sent by default, whether or not the server opted-into receiving the header via an Accept-CH header”[11].

There is one oddity in Sec-CH-UA. It may list brands that do not exist. MDN: “the header may include ‘fake’ brands in any position and with any name”, a feature meant “to prevent servers from rejecting unknown user agents outright”[3]. The spec’s reasoning is that scattering “intentionally incorrect, comma-separated entries with arbitrary ordering” stops sites from hard-coding a check against three exact strings[11]. A real Chrome header reads " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"[3], and the first entry is noise on purpose.

One response header turns the rest on

Everything past that short list requires the server to ask. It asks with Accept-CH, a response header that “specif[ies] which client hint headers should be included by the client in subsequent requests”[1]. Once the browser has seen it, it attaches the named hints to later requests to that origin, and the guidance is to send Accept-CH “for all secure requests” so the browser does not forget[1].

This is not hypothetical. Requesting the front pages of three large sites today, with a plain client, returns these opt-in lists verbatim:

$ curl -sS -D - -o /dev/null https://www.google.com/
accept-ch: Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List,
           Sec-CH-UA-Arch, Sec-CH-UA-Model, Sec-CH-UA-Bitness,
           Sec-CH-UA-WoW64, Sec-CH-UA-Form-Factors, Downlink, RTT,
           Sec-CH-Prefers-Color-Scheme

$ curl -sS -D - -o /dev/null https://www.youtube.com/
accept-ch: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version,
           Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-WoW64,
           Sec-CH-UA-Form-Factors, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version,
           Sec-CH-Viewport-Width, Sec-CH-DPR, Device-Memory
vary: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, ... (the same list)

$ curl -sS -D - -o /dev/null https://www.instagram.com/
accept-ch: Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform-Version,
           Sec-CH-UA-Model, viewport-width, dpr, Sec-CH-Prefers-Color-Scheme

The high-entropy items on those lists are the ones the reduced User-Agent string stopped carrying:

  • Sec-CH-UA-Full-Version-List is Sec-CH-UA “with the full version number instead of the significant version number”[6] - 98.0.4750.0 rather than 98.
  • Sec-CH-UA-Arch and Sec-CH-UA-Bitness give the CPU architecture and whether it is 32- or 64-bit.
  • Sec-CH-UA-Model “indicates the device model on which the browser is running”[7]. MDN’s own example value is "Pixel 3 XL"[7]. That is the specific phone, back in a header, one Accept-CH line after it was removed from the User-Agent string.

MDN describes the whole arrangement plainly: “the server can send an Accept-CH response header detailing the data items they want, and the client can then send the data back via Sec-CH-UA-* headers”[9].

Critical-CH can make your browser start over

Accept-CH has a timing gap. The first request to a site goes out before any Accept-CH has been seen, so it carries only the low-entropy three. A server that needs a high-entropy hint to build the right page does not have it yet.

Critical-CH closes that gap. It names the hints that are “critical”[2], and the browser’s obligation is specific: “user agents receiving a response with Critical-CH must check if the indicated critical headers were sent in the original request. If not, the user agent will retry the request along with the critical headers rather than render the page”[2].

So the sequence becomes: request goes out, server replies with Accept-CH plus Critical-CH, the browser notices a named hint was missing, and it silently re-sends the request with that hint attached before it draws anything[2]. The reader sees one page load. Underneath it there were two requests, and the second one described their device more fully than the first. MDN frames this as making sure client preferences “are always used, even if not included in the first request”[2].

The caching cost

A response that changes based on a hint has to say so, in the Vary header. MDN: hints “that determine which resources are sent in responses should generally also be included in the affected response’s Vary header”, so that “a different resource is cached for every different value of the hint header”[8]. Critical-CH inherits this too - each critical hint “should also be present in the Accept-CH and Vary headers”[2].

That is where it gets expensive. YouTube’s response, above, lists twelve hints in Vary. A shared cache now has to keep a separate copy of that page for every distinct combination of twelve values - architecture, bitness, full version, model, platform version, viewport, DPR, device memory and the rest. MDN’s advice for hints whose values move around is to leave them out of Vary entirely, because including them “effectively makes the resource uncacheable”[8]. The negotiation that tailors the page is in direct tension with the caching that would make it fast.

Only one engine answers

All of this is a Chromium feature. caniuse records Client Hints as implemented in Chrome and Edge and the other Chromium browsers, with Firefox reporting “no support across all tested versions” and Safari “no support across any version tested”[13]. The User-Agent Client Hints API carries MDN’s “not Baseline” label and an “Experimental” flag on top of it[10].

Two consequences follow. A site that sends Accept-CH: Sec-CH-UA-Model is asking a question only Chrome and Edge users answer, and it gets nothing back from anyone else. And the User-Agent reduction that started this - the trim that the hint channel was built to compensate for - was itself mostly Chrome’s move[12]. The same vendor narrowed the passive string and built the active channel that widens it again on request.

Where that leaves the reader

The reduced User-Agent string reads like less exposure, and in the passive sense it is: a site that never sends Accept-CH learns less about you than it used to[11]. Google, YouTube and Instagram all send Accept-CH.

When a site does ask, the mechanics have a few edges worth holding onto. The high-entropy hints are attached automatically from then on, with no per-request consent[1]. Critical-CH can turn a single navigation into two requests, the second keyed to a fuller description of your device, with nothing on the page to indicate it happened[2]. The hints follow the top-level origin: they are “only sent on same-origin requests” unless the page delegates them to a third party[12], which is a real limit, and also a setting the first party controls rather than you.

Using Firefox or Safari opts you out of the Sec-CH-UA-* channel entirely[13]. Chrome sends the low-entropy three to every site and the rest to any site that asks for them[1], and it puts neither exchange in front of the person the headers describe.

Sources

  1. Accept-CH header - MDN Web Docs, Mozilla, accessed

    Supports: The Accept-CH response header 'may be set by a server to specify which client hint headers should be included by the client in subsequent requests'. 'To ensure client hints are sent reliably, the Accept-CH header should be persisted for all secure requests.' The example pairs it with Vary: 'The Vary header indicates which values were used to vary the response based on the accepted client hints.' 'This feature is available only in secure contexts (HTTPS), in some or all supporting browsers.'

  2. Critical-CH header - MDN Web Docs, Mozilla, accessed

    Supports: The Critical-CH response header 'is used along with Accept-CH to identify the accepted client hints that are critical'. 'User agents receiving a response with Critical-CH must check if the indicated critical headers were sent in the original request. If not, the user agent will retry the request along with the critical headers rather than render the page.' 'This approach ensures that client preferences set using critical client hints are always used, even if not included in the first request, or following server configuration changes.' 'Each header listed in the Critical-CH header should also be present in the Accept-CH and Vary headers.' The worked example shows an initial response carrying Accept-CH / Vary / Critical-CH for Sec-CH-Prefers-Reduced-Motion, followed by an automatic retry request that now includes 'Sec-CH-Prefers-Reduced-Motion: "reduce"'.

  3. Sec-CH-UA header - MDN Web Docs, Mozilla, accessed

    Supports: The Sec-CH-UA request header 'is a user agent client hint which provides the user-agent's branding and significant version information'. 'Sec-CH-UA is a low entropy hint. Unless blocked by a user agent permission policy, it is sent by default, without the server opting in by sending Accept-CH.' The significant version is 'the "marketing" version identifier that is used to distinguish between major releases of the brand'. On the fictitious brands: 'The header may include "fake" brands in any position and with any name. This is a feature designed to prevent servers from rejecting unknown user agents outright, forcing user agents to lie about their brand identity.' Example value: 'Sec-CH-UA: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"'. Secure context only.

  4. Sec-CH-UA-Mobile header - MDN Web Docs, Mozilla, accessed

    Supports: The Sec-CH-UA-Mobile request header 'is a user agent client hint which indicates whether the browser is on a mobile device'. 'Sec-CH-UA-Mobile is a low entropy hint. Unless blocked by a user agent permission policy, it is sent by default, without the server opting in by sending Accept-CH.' It is a boolean: '?1 indicates that the user-agent prefers a mobile experience (true). ?0 indicates that user-agent does not prefer a mobile experience (false).' A desktop browser sends 'Sec-CH-UA-Mobile: ?0'.

  5. Sec-CH-UA-Platform header - MDN Web Docs, Mozilla, accessed

    Supports: The Sec-CH-UA-Platform request header 'provides the platform or operating system on which the user agent is running'. 'Sec-CH-UA-Platform is a low entropy hint. Unless blocked by a user agent permission policy, it is sent by default (without the server opting in by sending Accept-CH).' The value is 'One of the following strings: "Android", "Chrome OS", "Chromium OS", "iOS", "Linux", "macOS", "Windows", or "Unknown".' Example: 'Sec-CH-UA-Platform: "macOS"'.

  6. Sec-CH-UA-Full-Version-List header - MDN Web Docs, Mozilla, accessed

    Supports: The Sec-CH-UA-Full-Version-List request header 'is a user agent client hint which provides the user-agent's branding and full version information'. 'This is similar to Sec-CH-UA, but includes the full version number instead of the significant version number for each brand.' 'A server requests the Sec-CH-UA-Full-Version-List header by including the Accept-CH in a response to any request from the client, using the name of the desired header as a token.' Example value: 'Sec-CH-UA-Full-Version-List: " Not A;Brand";v="99.0.0.0", "Chromium";v="98.0.4750.0", "Google Chrome";v="98.0.4750.0"'.

  7. Sec-CH-UA-Model header - MDN Web Docs, Mozilla, accessed

    Supports: The Sec-CH-UA-Model request header 'is a user agent client hint which indicates the device model on which the browser is running'. It is requested by the server through Accept-CH. MDN marks it 'Limited availability' with the note 'This feature is not Baseline because it does not work in some of the most widely-used browsers.' The directive is 'A string containing the device version. For example "Pixel 3".' Example header on a mobile phone: 'Sec-CH-UA-Model: "Pixel 3 XL"'.

  8. HTTP Client Hints - MDN Web Docs, Mozilla, accessed

    Supports: Client hints are 'a set of HTTP request header fields that a server can proactively request from a client to get information about the device, network, user, and user-agent-specific preferences'. 'The low entropy hints are those that don't give away much information that might be used to fingerprint a user. They may be sent by default on every client request, irrespective of the server Accept-CH response header, depending on the permission policy.' The low entropy set is 'Save-Data', 'Sec-CH-UA', 'Sec-CH-UA-Mobile' and 'Sec-CH-UA-Platform'. On caching: 'Client hints that determine which resources are sent in responses should generally also be included in the affected response's Vary header. This ensures that a different resource is cached for every different value of the hint header.' And: 'You may prefer to omit specifying Vary or use some other strategy for client hint headers where the value changes a lot, as this effectively makes the resource uncacheable.'

  9. User-Agent header - MDN Web Docs, Mozilla, accessed

    Supports: 'The information exposed in the User-Agent header has historically raised privacy concerns - it can be used to identify a particular user agent, and can therefore be used for fingerprinting.' 'To mitigate such concerns, supporting browsers provide a reduced set of information in their User-Agent header, and in related API features such as Navigator.userAgent, Navigator.appVersion, and Navigator.platform.' 'Servers that need more information can request it via User-Agent client hints. After the initial connection, the server can send an Accept-CH response header detailing the data items they want, and the client can then send the data back via Sec-CH-UA-* headers.'

  10. User-Agent Client Hints API - MDN Web Docs, Mozilla, accessed

    Supports: The API 'extends Client Hints to provide a way of exposing browser and platform information via User-Agent response and request headers, and a JavaScript API'. MDN marks it 'Limited availability' with 'This feature is not Baseline because it does not work in some of the most widely-used browsers', and additionally flags it 'Experimental'.

  11. User-Agent Client Hints (Draft Community Group Report) - Web Incubator Community Group (WICG), accessed

    Supports: The specification's stated motivation is that the User-Agent header's 'value exposes far more information about the user's device than seems appropriate as a default', and its aim is to reduce 'the passive fingerprinting surface area exposed to the network'. Sec-CH-UA, Sec-CH-UA-Mobile and Sec-CH-UA-Platform are designated low-entropy and 'will be sent by default, whether or not the server opted-into receiving the header via an Accept-CH header'. Higher-entropy hints are restricted to same-origin contexts unless 'delegated from top-level pages via Permissions Policy', which reduces 'the likelihood that user agent information will be delivered along with subresource requests'. On GREASE: 'by randomly including additional, intentionally incorrect, comma-separated entries with arbitrary ordering, they would reduce the chance that we ossify on a few required strings.'

  12. User-Agent Client Hints - Chrome for Developers, Google, accessed

    Supports: Chrome's rationale: 'The User-Agent string could contain enough information to allow individual users to be uniquely identified', and parsing it 'results in unnecessary complexity, which is often the cause for bugs and site compatibility issues'. By default the browser returns Sec-CH-UA (brand and significant version), Sec-CH-UA-Mobile (boolean) and Sec-CH-UA-Platform (operating system); servers request more via an Accept-CH response header such as 'Accept-CH: Sec-CH-UA-Full-Version-List'. 'Client Hints will only be sent on same-origin requests' without explicit delegation via a Permissions-Policy header. The reduced User-Agent string is illustrated as 'Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36'.

  13. HTTP Client Hints - browser support - caniuse.com, accessed

    Supports: Client Hints are implemented in Chrome and Edge (from version 46 / 79 onwards) and other Chromium-based browsers. Firefox reports no support across all tested versions, and Safari reports no support across any version tested, including current releases and Technology Preview. 'Chromium-based browsers (Chrome, Edge, Opera) have implemented this capability, while Mozilla and WebKit engines have not.'