The Client Hints API automatically sends your device model, operating system, CPU architecture, and browser version to every website via HTTP headers, enabling fingerprinting without JavaScript. Originally designed for content optimization, websites now use these headers to fingerprint and track users across sessions with high accuracy. Disable Client Hints through browser preferences (if available), use privacy extensions to block high-entropy hints, or use a privacy-focused browser like Firefox, which offers better Client Hints protection than Chrome.
What Are Client Hints?
Client Hints are HTTP headers that browsers send with each request, providing servers with information about the client device and browser configuration. Unlike traditional fingerprinting techniques that require JavaScript execution, Client Hints transmit this data automatically at the network level.
The API emerged from Google’s broader Privacy Sandbox initiative, positioned as a privacy-respecting alternative to cross-site tracking. However, the implementation has raised substantial concerns among privacy researchers and developers.
Available Client Hints Headers
Chrome exposes numerous hints that websites can request:
Sec-CH-UA: "Chrome"; v="120", "Not(A:Brand"; v="8"
Sec-CH-UA-Mobile: ?0
Sec-CH-UA-Platform: "Windows"
Sec-CH-UA-Platform-Version: "15.0.0"
Sec-CH-UA-Arch: "x86"
Sec-CH-UA-Bitness: "64"
Sec-CH-UA-Model: "Galaxy S21"
Sec-CH-UA-WOW64: ?0
Sec-CH-Prefers-Color-Scheme: "dark"
Sec-CH-Prefers-Reduced-Motion: "no-preference"
Viewport-Width: 1920
Device-Memory: 8
Downlink: 10.85
ECT: 4g
RTT: 50
Each header reveals specific information about your device and preferences. The Sec-CH-UA-* headers expose detailed browser and platform information, while Device-Memory reveals available RAM, and network-related hints like Downlink and ECT characterize your connection quality.
How Websites Use Client Hints for Fingerprinting
When you visit a site that implements Client Hints fingerprinting, the server responds with an Accept-CH header requesting specific hints. Chrome then includes these headers in subsequent requests to that origin.
Here’s a practical example of how a site might implement hint-based fingerprinting:
// Server sends this response header to request Client Hints
Accept-CH: Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform,
Sec-CH-UA-Model, Device-Memory, Downlink
// JavaScript can also access hints via the Permissions API
async function getClientHints() {
if (!window.clientHintsInfrastructure) return;
const hints = await document.clientHintsInfrastructure.getHintsDeprecation();
console.log('Available hints:', hints);
}
The fingerprinting potential comes from combining multiple hints. While individual hints might seem innocuous, the combination creates a highly unique identifier:
| Hint | Information Revealed |
|---|---|
| Sec-CH-UA | Browser brand and version |
| Sec-CH-UA-Model | Device model |
| Sec-CH-UA-Platform | Operating system |
| Device-Memory | RAM amount |
| Sec-CH-UA-Arch | CPU architecture |
A user with a specific Chrome version, on a particular device model, running Windows 11 with 16GB RAM becomes significantly more identifiable than someone using generic values.
Practical Fingerprinting Example
Consider this scenario: a website wants to build a fingerprint:
# Server-side example (Python/Flask)
@app.route('/track')
def track_fingerprint():
user_hints = {
'ua': request.headers.get('Sec-CH-UA'),
'mobile': request.headers.get('Sec-CH-UA-Mobile'),
'platform': request.headers.get('Sec-CH-UA-Platform'),
'model': request.headers.get('Sec-CH-UA-Model'),
'memory': request.headers.get('Device-Memory'),
'arch': request.headers.get('Sec-CH-UA-Arch'),
}
# Create fingerprint hash from combined hints
fingerprint = hashlib.sha256(
'|'.join(str(v) for v in user_hints.values() if v)
).hexdigest()
# Store or correlate with existing user data
return jsonify({'fingerprint': fingerprint})
This approach works even when users clear cookies, employ VPN services, or use private browsing modes. The fingerprint derives entirely from headers that Chrome sends automatically.
Browser Support and Availability
Client Hints have expanded significantly across Chromium-based browsers:
- Chrome/Edge: Full support, actively deploying new hints
- Opera: Inherits Chromium implementation
- Brave: Blocks many hints by default
- Firefox/Safari: Limited or no support
This uneven support creates additional fingerprinting opportunities. Sites can detect which hints a browser provides (or doesn’t provide), using the presence or absence of specific hints as another distinguishing factor.
Defending Against Client Hints Tracking
Several strategies can reduce Client Hints fingerprinting:
Browser Configuration
Firefox provides the most protection by not implementing Client Hints. For Chrome users, the built-in privacy settings offer limited control:
- Navigate to
chrome://settings/cookiesand disable “Send Do Not Track” (ironically, this reduces your fingerprint surface) - Use browser extensions that modify request headers
Extension-Based Blocking
Extensions like uBlock Origin can block or modify Client Hints headers:
! Block Device-Memory header
||example.com^$header=Device-Memory
! Block all Client Hints infrastructure
||example.com^$header=Sec-CH-UA
||example.com^$header=Sec-CH-UA-Mobile
||example.com^$header=Sec-CH-UA-Platform
Network-Level Solutions
For advanced users, proxy services and privacy-focused DNS providers can filter or normalize Client Hints before they reach their destination.
The Tor Browser Approach
Tor Browser provides the most protection by standardizing Client Hints across all users. Every Tor Browser instance reports identical hint values, eliminating the ability to distinguish between users based on these headers.
What Developers Need to Know
If you’re building web applications, consider the privacy implications before implementing Client Hints:
- Minimal necessary hints: Only request hints actually needed for content optimization
- Graceful degradation: Design systems to work when hints aren’t available
- User consent: Consider whether you genuinely need device fingerprinting versus less invasive analytics
The Broader Privacy Sandbox Context
Client Hints exist within Google’s larger Privacy Sandbox framework, which includes APIs like Topics, Attribution Reporting, and FLEDGE. While marketed as privacy improvements, these APIs create new tracking mechanisms that operate without traditional cookies.
The fundamental tension: Chrome positions Client Hints as a privacy-respecting alternative to fingerprinting, while the API itself enables fingerprinting at scale.
Related Articles
- Chrome Privacy Sandbox Explained What It Means For Tracking
- Attribution Reporting Api How Chrome Replaced Cookies For Ad
- Topics Api Chrome Replacement For Cookies How It Tracks You
- Audio Context Fingerprinting How Websites Use Sound Api Trac
- Battery Api Fingerprinting How Battery Status Tracks You Exp
Built by theluckystrike — More at zovo.one
Fingerprinting Entropy Analysis
To understand the tracking risk, calculate the entropy (uniqueness) of your fingerprint:
import hashlib
import math
def calculate_fingerprint_entropy(hints: dict) -> float:
"""Calculate entropy (bits of information) in your Client Hints fingerprint"""
# Estimate possible values for each hint
hint_distributions = {
'Sec-CH-UA': 50, # ~50 distinct browser/version combos
'Sec-CH-UA-Mobile': 2, # binary: mobile or not
'Sec-CH-UA-Platform': 5, # Windows, Mac, Linux, Android, iOS
'Sec-CH-UA-Platform-Version': 100, # many OS versions
'Sec-CH-UA-Arch': 3, # x86, ARM, other
'Sec-CH-UA-Bitness': 2, # 32-bit or 64-bit
'Sec-CH-UA-Model': 1000, # thousands of device models
'Device-Memory': 8, # 2GB, 4GB, 8GB, 16GB, etc
}
# Calculate total entropy
total_entropy = 0
for hint, possible_values in hint_distributions.items():
if hints.get(hint):
# Entropy = log2(possible_values)
entropy = math.log2(possible_values)
total_entropy += entropy
print(f"{hint}: {entropy:.1f} bits")
print(f"\nTotal fingerprint entropy: {total_entropy:.1f} bits")
print(f"Uniqueness: 1 in {2**total_entropy:,.0f} browsers")
return total_entropy
# Example
my_hints = {
'Sec-CH-UA': '"Chrome"; v="120", "Not(A:Brand"; v="8"',
'Sec-CH-UA-Platform': 'Linux',
'Sec-CH-UA-Arch': 'x86',
'Device-Memory': 16,
'Sec-CH-UA-Model': 'Custom Desktop'
}
entropy = calculate_fingerprint_entropy(my_hints)
With 25-30 bits of entropy, your fingerprint uniquely identifies you among thousands of users. Modern browsers combined with Client Hints easily exceed this threshold.
Practical Fingerprinting Implementation
A real website using Client Hints for tracking would implement:
// Server-side tracking implementation (Python/Node.js)
class ClientFingerprintTracker {
constructor(database) {
this.db = database;
}
extract_hints_from_headers(request) {
// Extract from HTTP headers
return {
ua: request.headers.get('Sec-CH-UA'),
mobile: request.headers.get('Sec-CH-UA-Mobile'),
platform: request.headers.get('Sec-CH-UA-Platform'),
model: request.headers.get('Sec-CH-UA-Model'),
memory: request.headers.get('Device-Memory'),
arch: request.headers.get('Sec-CH-UA-Arch'),
color_scheme: request.headers.get('Sec-CH-Prefers-Color-Scheme'),
viewport_width: request.headers.get('Viewport-Width'),
network_type: request.headers.get('ECT')
};
}
create_fingerprint(hints) {
// Combine hints into a unique identifier
import hashlib
combined = '|'.join(str(v) for v in hints.values())
fingerprint_hash = hashlib.sha256(combined.encode()).hexdigest()
return fingerprint_hash
def track_user(self, request, user_id=None):
hints = self.extract_hints_from_headers(request)
fingerprint = self.create_fingerprint(hints)
# Store or update fingerprint record
self.db.upsert('fingerprints', {
'fingerprint_hash': fingerprint,
'user_id': user_id,
'hints': hints,
'last_seen': datetime.now(),
'visit_count': self.db.get_visit_count(fingerprint) + 1
})
# Correlate with other tracking data
# (cookies, pixel tracking, analytics tags)
return fingerprint
This creates a persistent identifier that survives:
- Cookie deletion
- Private browsing sessions
- Browser cache clearing
- VPN changes (but NOT IP address changes)
Cross-Site Tracking with Client Hints
The dangerous part: Hints are sent to EVERY website you visit:
User visits site1.com
→ site1.com receives Client Hints header
→ site1.com records fingerprint
User visits site2.com (different website)
→ site2.com receives SAME Client Hints header
→ site2.com records fingerprint
site1.com + site2.com use data brokers to share fingerprints
→ User is now tracked across multiple websites
→ No cookies needed; no consent required
This is fundamentally different from cookie tracking because:
- Users expect cookies can be deleted
- Hints are “just device information” users don’t think about
- Technical prevention requires browser-level changes, not user action
Protection Mechanisms by Browser
Firefox (Best Protection)
Firefox doesn’t implement Client Hints, providing strongest protection:
// Firefox ignores Accept-CH headers
// Fingerprinting attempts return generic values
navigator.userAgent
// "Mozilla/5.0 (X11; Linux x86_64; rv:121.0) Gecko/20100101 Firefox/121.0"
// No detailed hints available
Recommendation: Use Firefox for high-privacy browsing.
Brave (Good Protection)
Brave blocks high-entropy hints by default:
Blocked by default:
- Sec-CH-UA-Model (device model)
- Sec-CH-UA-Full-Version-List
- Device-Memory
- Sec-CH-UA-Platform-Version
Allowed (low-entropy):
- Sec-CH-UA (browser, already in User-Agent)
- Sec-CH-UA-Mobile (binary value)
- Sec-CH-UA-Platform (OS, already in User-Agent)
Recommendation: Use Brave for Chrome-based browsing with privacy.
Chrome (Limited Protection)
Chrome’s privacy controls are minimal:
chrome://settings/privacy
→ "Send Do Not Track" - Adds header but doesn't block hints
→ No Client Hints specific controls
Solution: Use extensions to block or modify hints
Recommendation: If using Chrome, install uBlock Origin with custom rules.
Browser Extension Blocking Rules
uBlock Origin Rules
! Block all Client Hints
||example.com^$header=Sec-CH-UA
||example.com^$header=Sec-CH-UA-Mobile
||example.com^$header=Sec-CH-UA-Platform
||example.com^$header=Sec-CH-UA-Platform-Version
||example.com^$header=Sec-CH-UA-Arch
||example.com^$header=Sec-CH-UA-Bitness
||example.com^$header=Sec-CH-UA-Model
||example.com^$header=Device-Memory
||example.com^$header=Downlink
||example.com^$header=ECT
||example.com^$header=RTT
Note: uBlock Origin’s header-blocking feature has limitations. Full blocking requires more advanced solutions.
Header Modification with LibreWolf
LibreWolf (Firefox hardened fork) removes Client Hints entirely:
about:config settings:
network.http.http2.enabled = true
network.http.http3.enabled = false
# Client Hints already disabled in Firefox
Advanced Fingerprinting via Hints Combination
Sophisticated trackers combine Client Hints with other signals:
def create_advanced_fingerprint(request):
"""Combine Client Hints with other signals"""
hints_fingerprint = hashlib.sha256(
f"{request.headers.get('Sec-CH-UA')}" +
f"{request.headers.get('Device-Memory')}" +
f"{request.headers.get('Sec-CH-UA-Model')}"
).hexdigest()
# Layer 1: Client Hints
fp_layer1 = hints_fingerprint
# Layer 2: HTTP Accept headers (font types, image formats)
fp_layer2 = hashlib.sha256(
request.headers.get('Accept')
).hexdigest()
# Layer 3: TLS fingerprint (cipher suite preferences)
fp_layer3 = request.tls_fingerprint
# Layer 4: IP geolocation
fp_layer4 = request.client_ip
# Combine all layers
complete_fingerprint = hashlib.sha256(
f"{fp_layer1}{fp_layer2}{fp_layer3}{fp_layer4}"
).hexdigest()
return complete_fingerprint
This multi-layer approach makes fingerprints persistent even if one signal changes.
Privacy Sandbox and Future Tracking APIs
Client Hints are part of Google’s larger Privacy Sandbox initiative, which includes:
- FLEDGE (Federated Learning of Cohorts): Group users into interest cohorts
- Topics API: Website shares inferred topics about user interests
- Attribution Reporting: Tracks conversions without cross-site cookies
- Aggregate Reporting: Anonymous aggregate statistics
All these APIs face similar concerns: they provide tracking capabilities without user consent or ability to opt-out technically.
Recommendations by Threat Model
| Threat Model | Browser | Extensions |
|---|---|---|
| Casual user | Chrome | uBlock Origin |
| Privacy-conscious | Brave | Default protections |
| Privacy-researcher | Firefox + LibreWolf | Minimal extensions |
| Journalist/Activist | Tor Browser | Already hardened |
For maximum protection, Tor Browser standardizes all Client Hints to identical values, eliminating fingerprinting differences.