第 1 張,共 34 張

User-Agent evolution

String to Client Hints

merewood@ / @rowan_m

Confidential + Proprietary

Confidential + Proprietary

第 2 張,共 34 張

What are we going to talk about?

  1. User-Agent string → passive fingerprinting surface
  2. Chrome → reducing the information in its user-agent
  3. User-Agent Client Hints → active API for the same data
  4. How do you migrate from string to client hints?

Confidential + Proprietary

第 3 張,共 34 張

User-Agent string

  • Original RFC 1945 example from 1996:

User-Agent: CERN-LineMode/2.15 libwww/2.17b3

Confidential + Proprietary

第 4 張,共 34 張

User-Agent string

  • Original RFC 1945 example from 1996:

User-Agent: CERN-LineMode/2.15 libwww/2.17b3

  • My phone example from today:

Mozilla/5.0 (Linux; Android 11; Pixel 5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.61 Mobile Safari/537.36

Confidential + Proprietary

第 5 張,共 34 張

User-Agent String

  • Passive fingerprinting surface
  • Complicated, inconsistent format

Confidential + Proprietary

第 6 張,共 34 張

60-70%

"…of HTTP user-agent strings can accurately identify hosts in our datasets"

Microsoft research: Host Fingerprinting and Tracking on the Web:Privacy and Security Implications

Confidential + Proprietary

第 7 張,共 34 張

Reducing Chrome's�user-agent string

Confidential + Proprietary

Confidential + Proprietary

第 8 張,共 34 張

Reducing Chrome's user-agent string

  • Current user-agent string format

Mozilla/5.0 (Linux; Android 9; SM-A205U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.1234.56 Mobile Safari/537.36

  • Final reduced user-agent string format

Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.0.0 Mobile Safari/537.36

Full phases detailed at: chromium.org/updates/ua-reduction

Confidential + Proprietary

第 9 張,共 34 張

Early opt-in origin trial

Confidential + Proprietary

Confidential + Proprietary

第 10 張,共 34 張

Privacy Sandbox patterns

  • Passive fingerprinting
    • Active requests
  • Raw data
    • Specific answers

Confidential + Proprietary

Confidential + Proprietary

第 11 張,共 34 張

Client Hints

⬆️ Client (browser) sends initial request

⬇️ Server specifies desired hints in response

⬆️ Client may send hints in subsequent requests

Confidential + Proprietary

第 12 張,共 34 張

⬇️ Server response� Accept-CH: Viewport-Width, Width

↪️ Or a meta tag<meta http-equiv="Accept-CH" content="Viewport-Width, Width" />

⬆️ Client request� Viewport-Width: 460� Width: 230

Confidential + Proprietary

第 13 張,共 34 張

Migrate User-Agent data to Client Hints for a�cleaner, active method of obtaining the same information.

  • User-Agent string
    • User-Agent Client Hints

Confidential + Proprietary

Confidential + Proprietary

第 14 張,共 34 張

User-Agent Client Hints

⬆️ Initial client request� Sec-CH-UA: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"� Sec-CH-UA-Mobile: ?0� Sec-CH-UA-Platform: "Linux"

⬇️ Server response� Accept-CH: Sec-CH-UA-Full-Version

⬆️ Client request� Sec-CH-UA: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"� Sec-CH-UA-Mobile: ?0� Sec-CH-UA-Platform: "Linux"� Sec-CH-UA-Full-Version: "91.0.4469.4"

Confidential + Proprietary

第 15 張,共 34 張

Available hints - Sec-CH-UA-*

  • [UA]: Default list of brand + significant version, e.g. "Google Chrome";v="91"
  • Arch: CPU architecture, e.g. "x86"
  • Bitness: CPU architecture bitness, e.g. "64"
  • Full-Version: Build version, e.g. "91.0.4469.4"
  • Mobile: Boolean indicating a mobile device, e.g. ?0 or ?1
  • Model: Device model, e.g. "Pixel 5"
  • Platform: Operating system, e.g. "Android"
  • Platform-Version: Operating system version, e.g. "11"

Confidential + Proprietary

第 16 張,共 34 張

Confidential + Proprietary

第 17 張,共 34 張

Accept-CH cache

The browser will continue to send the hints requested by the last Accept-CH header until:

  • the session ends
  • storage is cleared
  • another Accept-CH header is received.

Confidential + Proprietary

第 18 張,共 34 張

Client Hints on cross-origin requests

⬇️ Response from example.com� Accept-CH: Sec-CH-UA-Model, DPR� Permissions-Policy: ch-ua-model=(self "https://downloads.example.com"),� ch-dpr (self "img.example.com")

⬆️ Request to downloads.example.com� Sec-CH-UA-Model: "Pixel 5"

⬆️ Requests to img.example.com� DPR: 2

Confidential + Proprietary

第 19 張,共 34 張

Confidential + Proprietary

第 20 張,共 34 張

What if I need all the user-agent data on first load?

Confidential + Proprietary

第 21 張,共 34 張

What if I need all the user-agent data on first load?

😽

Confidential + Proprietary

第 22 張,共 34 張

Things that are not the first request

  • Same-origin navigations
  • JavaScript
  • Content in iframes
  • Images
  • Any subresource
  • Literally anything that is not the very first inbound contact from the browser in that session.

Confidential + Proprietary

第 23 張,共 34 張

If you really need specific user-agent data on first load

⬆️ Initial client request� Sec-CH-UA: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"� Sec-CH-UA-Mobile: ?1� Sec-CH-UA-Platform: "Android"

⬇️ Server response� Accept-CH: Sec-CH-UA-Platform-Version� Critical-CH: Sec-CH-UA-Platform-Version

⬆️ Client retries same request� Sec-CH-UA: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"� Sec-CH-UA-Mobile: ?1� Sec-CH-UA-Platform: "Android"� Sec-CH-UA-Platform-Version: "11"

Confidential + Proprietary

第 24 張,共 34 張

Confidential + Proprietary

第 25 張,共 34 張

Avoiding the round trip with Application-Layer Protocol Settings

ClientHello� + alps

ServerHello�EncryptedExtensions�+ alps=(https://example.com, Sec-CH-UA-Platform-Version)�…�Finished

Finished� GET / HTTP/2.0� Host: example.com� Sec-CH-UA-Platform-Version: 11

HTTP/2.0 200 OK�Vary: Sec-CH-UA-Platform-Version�Accept-CH: Sec-CH-UA-Platform-Version�Critical-CH: Sec-CH-UA-Platform-Version

Confidential + Proprietary

第 26 張,共 34 張

  • Migration strategies�web.dev/migrate-to-ua-ch/

Confidential + Proprietary

Confidential + Proprietary

第 27 張,共 34 張

Confidential + Proprietary

第 28 張,共 34 張

Literally do nothing option

  1. Audit use of User-Agent string in back-end and front-end
  2. If only the base values of brand, major version, and platform are used then these will probably be available in the frozen UA string

Confidential + Proprietary

第 29 張,共 34 張

Front-end option

  1. Check code for usage of navigator.userAgent
    1. Also the deprecated navigator.platform or navigator.appVersion
  2. Wrap with appropriate calls to detect:

if (navigator.userAgentData) {

// use new hints

} else {

// fall back to user-agent string parsing

}

Confidential + Proprietary

第 30 張,共 34 張

Server-side static header option

  1. Audit use of User-Agent string
  2. Set headers for required hints across the whole site
    1. Or no headers if the defaults are enough
  3. Switch from checking User-Agent header to relevant Sec-CH-UA headers

Confidential + Proprietary

第 31 張,共 34 張

Hints needed on first request option

  1. Audit use of user-agent data
    1. Are the non-default values really needed on first request? What's the impact of them not being there?
  2. Can that data be requested client-side? Use navigator.userAgentData
  3. Is that first request to a sub-resource? Use Feature Policy / Permission Policy
  4. Really needed on first request? Use Critical-CH

Confidential + Proprietary

第 32 張,共 34 張

Dynamic requirements option

  1. Audit use of user-agent data
  2. Are there specific user journeys that require richer data?
  3. Can these be handled client-side with navigator.userAgentData?
  4. Can headers be configured per route?

Confidential + Proprietary

第 33 張,共 34 張

Using third-party libraries option

  1. Do you use a device detection or similar user-agent parsing library?
  2. Add a feature request for them to support User-Agent Client Hints
    1. TODO: reasonable template for these as we want to avoid… (see next slide)

Confidential + Proprietary

第 34 張,共 34 張

Retrofill option - please don't

  • Audit use of user-agent data
  • Is there legacy / unknown code using user-agent data?
  • Request all Client Hints and rebuild the user-agent string server-side
  • Request all data from navigator.userAgentData and overwrite navigator.userAgent

Confidential + Proprietary