CPU Core Fingerprinting: How hardwareConcurrency Tracks You
How navigator.hardwareConcurrency reveals your CPU core count for fingerprinting, and techniques to control core reporting across all contexts.
Introduction
The navigator.hardwareConcurrency property returns the number of logical CPU cores available to the browser. It was designed to help web applications optimize parallel workloads, letting JavaScript code decide how many Web Workers to spawn or how to partition data processing tasks. But this same property reveals hardware information that contributes to browser fingerprinting. A device with 6 logical cores is far less common than one with 8, and a server with 64 cores stands out immediately. Because CPU core count is stable, easy to query, and varies across hardware configurations, it has become a standard component in fingerprint tracking systems. This article explains how CPU core count contributes to fingerprinting, why simple overrides create problems, and how BotBrowser provides consistent core count reporting through its profile system.
Privacy Impact
CPU core count might seem like a low-value fingerprint signal, but its contribution is significant in context. The value of any fingerprint attribute depends on its distribution in the population. According to data from the AmIUnique project, navigator.hardwareConcurrency has about 4.5 bits of entropy, meaning it can distinguish roughly 23 different groups. While 23 groups does not sound like much, each bit of entropy roughly doubles the identifying power when combined with other signals.
The distribution is highly skewed. Values of 4 and 8 account for the majority of desktop browsers. Values of 2 (older machines, low-end Chromebooks), 6 (certain Intel configurations), 12 (Ryzen 5 processors), 16 (high-end desktops), and anything above 16 are progressively rarer. A machine reporting 24, 32, or 64 cores is almost certainly a server or workstation, which is extremely uncommon in normal browsing populations.
Mobile devices add further differentiation. Most phones report 4 or 8 cores, but specific SoC families (Snapdragon, Exynos, MediaTek, Apple Silicon) pair specific core counts with other identifiable properties. A phone with hardwareConcurrency of 8 combined with a specific user agent pattern narrows the device to a handful of models.
The privacy concern is amplified because hardwareConcurrency is available in all execution contexts: main thread, dedicated workers, shared workers, and service workers. It requires no permissions and cannot be blocked without breaking legitimate web applications that use it for performance optimization.
Technical Background
How hardwareConcurrency Works
The navigator.hardwareConcurrency property returns an unsigned long integer representing the number of logical processors available. "Logical processors" means physical cores multiplied by threads-per-core (e.g., a 4-core CPU with hyperthreading reports 8).
console.log(navigator.hardwareConcurrency); // e.g., 8
The value is also available in worker contexts:
// Inside a Web Worker
console.log(self.navigator.hardwareConcurrency); // same value
Why the Value Varies
Several factors determine what hardwareConcurrency returns:
- Physical hardware. The number of CPU cores and threads is determined by the processor model. Intel i5 processors commonly have 4-6 cores with hyperthreading. AMD Ryzen 7 processors have 8 cores with SMT. Apple M-series chips have varying core counts across efficiency and performance clusters.
- Virtualization. Virtual machines report the number of vCPUs allocated, which may differ from the host machine. A VM with 2 vCPUs reports
hardwareConcurrencyof 2, regardless of the host's actual core count. - OS scheduling. Some operating systems allow limiting the visible processor count. Container environments (Docker, LXC) can restrict CPU visibility.
- Browser implementation. Most browsers report the raw value from the OS. Some have experimented with capping or bucketing the value, but Chromium currently reports the actual count.
Correlation with Other Signals
CPU core count does not exist in isolation. Tracking systems cross-reference it with:
navigator.deviceMemory- A 2-core machine with 8 GB RAM is plausible; a 2-core machine with 0.25 GB RAM suggests a different device class.navigator.platform- A "Linux x86_64" platform with 4 cores narrows to specific hardware. "Linux armv81" with 8 cores suggests a specific ARM server or phone.- User agent. The browser version and OS version constrain which core counts are realistic.
- Performance timing. The actual parallel execution performance (measured through timing side channels) can be compared against the reported core count.
An inconsistency between reported core count and actual parallel performance is a strong signal that the value has been tampered with.
Common Protection Approaches and Their Limitations
Manually overriding hardwareConcurrency via a browser extension or JavaScript injection is the most common approach. This changes the returned value but creates several problems:
- Performance inconsistency. If you report 4 cores but your actual machine has 16, parallel workloads (SharedArrayBuffer, Web Workers) execute faster than a 4-core machine would allow. This timing discrepancy is measurable.
- Prototype detection. JavaScript overrides on
navigator.hardwareConcurrencycan be detected by inspecting the property descriptor, checking the prototype chain, or measuring the getter's execution time. - Worker context gaps. Some extensions only modify the main thread's
navigatorobject without patching worker contexts. A tracking script running in a Web Worker sees the real value.
Randomizing the value creates unrealistic configurations. A random value of 7 or 13 does not correspond to any real CPU architecture. Values must be powers of 2 or specific known configurations to be plausible.
Using a fixed common value (like always reporting 8) is better than randomization but still risky if it contradicts other signals. Reporting 8 cores while the user agent claims an Android phone that ships with a 4-core SoC is inconsistent.
The fundamental requirement is that the reported core count must be internally consistent with all other device properties and must not contradict observable performance characteristics.
BotBrowser's Engine-Level Approach
BotBrowser controls navigator.hardwareConcurrency at the Chromium engine level through its fingerprint profile system. This approach provides several advantages over JavaScript-level modifications.
Profile-Driven Values
Each BotBrowser fingerprint profile specifies a hardwareConcurrency value that matches the profiled device's actual CPU configuration. A Windows 10 desktop profile with an Intel i7 reports 8 or 16 cores. An Android phone profile with a Snapdragon 888 reports 8 cores. The values are drawn from real device configurations, not arbitrary numbers.
All-Context Consistency
Because the control happens at the engine level, every context reports the same value:
- Main thread
navigator.hardwareConcurrency - Dedicated Worker
self.navigator.hardwareConcurrency - Shared Worker
self.navigator.hardwareConcurrency - Service Worker
self.navigator.hardwareConcurrency
There is no gap where one context returns the real value while another returns the profile value.
Cross-Signal Consistency
The hardwareConcurrency value in the profile is part of a complete device identity. It aligns with:
- The user agent string and platform
- The deviceMemory value
- The screen resolution and other hardware signals
- The browser brand and version
This ensures that no cross-reference check reveals an inconsistency.
No JavaScript Artifacts
Because the value is set at the engine level, there are no modified property descriptors, no non-native getters, and no timing differences compared to a native property access. The property behaves exactly as it would on the profiled hardware.
Configuration and Usage
Basic Profile Loading
chrome --bot-profile="/path/to/profile.enc" \
--user-data-dir="$(mktemp -d)"
The profile determines the reported core count automatically.
Verifying the Value
// In browser console or automation script
console.log(navigator.hardwareConcurrency);
// Returns the profile's core count, not your actual hardware
Playwright Integration
const { chromium } = require('playwright');
const browser = await chromium.launch({
executablePath: '/path/to/botbrowser/chrome',
args: [
'--bot-profile=/path/to/profile.enc'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
const cores = await page.evaluate(() => navigator.hardwareConcurrency);
console.log(`Reported cores: ${cores}`);
// Verify worker context matches
const workerCores = await page.evaluate(() => {
return new Promise(resolve => {
const blob = new Blob([
'postMessage(self.navigator.hardwareConcurrency)'
], { type: 'application/javascript' });
const worker = new Worker(URL.createObjectURL(blob));
worker.onmessage = e => resolve(e.data);
});
});
console.log(`Worker cores: ${workerCores}`);
// Both values should be identical
Puppeteer Integration
const puppeteer = require('puppeteer');
const browser = await puppeteer.launch({
executablePath: '/path/to/botbrowser/chrome',
defaultViewport: null,
args: [
'--bot-profile=/path/to/profile.enc'
]
});
const page = await browser.newPage();
await page.goto('https://example.com');
const cores = await page.evaluate(() => navigator.hardwareConcurrency);
console.log(`Reported cores: ${cores}`);
Verification
Value check. Query navigator.hardwareConcurrency and confirm it matches the expected value for your loaded profile. It should not reflect your actual hardware.
Worker consistency. Create a Web Worker and query self.navigator.hardwareConcurrency inside it. The value should match the main thread exactly.
Cross-property consistency. Verify that the core count is plausible for the profile's platform, user agent, and device memory. An 8-core configuration with 8 GB memory and a Windows 10 user agent is realistic. A 64-core configuration with an Android user agent is not.
Cross-session stability. Query the value across multiple sessions with the same profile. It should be identical every time.
Best Practices
- Use a complete profile. Do not try to override just
hardwareConcurrencyin isolation. The value must be consistent with all other device properties. BotBrowser profiles ensure this automatically. - Choose profiles with realistic configurations. Common values (4, 8) blend in better than unusual values (6, 12, 24). Select profiles appropriate for your target use case.
- Test in worker contexts. Always verify that worker-based queries return the same value as the main thread.
- Avoid overriding core count manually. BotBrowser profiles are designed with internally consistent configurations. Manually changing core count without adjusting other properties creates detectable inconsistencies.
FAQ
Q: What are the most common hardwareConcurrency values? A: On desktop, 4 and 8 are by far the most common, together accounting for over 70% of browsers. On mobile, 4 and 8 dominate as well. Values of 2, 6, 10, 12, 16, and higher are progressively rarer.
Q: Can a website measure my actual core count regardless of what hardwareConcurrency reports? A: In theory, a site could attempt to measure parallel execution performance using SharedArrayBuffer and high-resolution timers. In practice, browser security mitigations (reduced timer precision, COOP/COEP requirements) make this difficult. BotBrowser's engine-level control also helps, as the reported value aligns with the profile's expected behavior.
Q: Does hardwareConcurrency change with browser updates? A: No. The value reflects hardware and OS configuration, not browser version. It remains stable across browser updates on the same machine.
Q: Is navigator.hardwareConcurrency available in all browsers? A: Yes. It is supported in all major browsers (Chrome, Firefox, Safari, Edge) and has been standardized as part of the HTML specification.
Q: Why do some VMs report different core counts than the host?
A: Virtual machines report the number of vCPUs allocated by the hypervisor. A host with 16 cores might allocate only 2 or 4 vCPUs to a VM, which is what hardwareConcurrency reports from inside the VM.
Q: Does the core count affect Web Worker performance in BotBrowser? A: BotBrowser controls the reported value, not the actual CPU scheduling. Your real hardware's cores are still used for execution. The profile value only affects what JavaScript sees when querying the property.
Summary
navigator.hardwareConcurrency is a simple property that returns a single number, but its value contributes meaningfully to browser fingerprinting, especially when combined with other hardware signals. BotBrowser controls this value at the Chromium engine level through its profile system, ensuring consistent reporting across all JavaScript execution contexts. Because the value is part of a complete device profile, it aligns with the user agent, platform, memory, and all other identity signals.
For related topics, see What is Browser Fingerprinting, Navigator Property Protection, Screen and Window Protection, and Deterministic Browser Behavior.