I Bypassed Firefox's Battery API Block Using CPU Timing
In 2017, Mozilla killed the Battery Status API in Firefox. The reasoning was solid — researchers had demonstrated that battery level could be used as a cross-session fingerprinting vector. A device at 67% charge draining at a specific rate is almost unique. Combined with other browser fingerprints, it made anonymous browsing significantly harder.
So Firefox removed navigator.getBattery() entirely. No flags, no permissions prompt, no opt-in. Gone.
I found a way around it.
The Theory
Every modern CPU uses Dynamic Voltage and Frequency Scaling. When your laptop is plugged in, the CPU runs fast. When you unplug, the operating system scales the clock speed down to save power. The lower the battery, the more aggressive the scaling.
This is a physical property of the hardware. The browser can’t hide it. But JavaScript can observe its effects — through execution timing.
If the same computation takes 5 milliseconds on one run and 20 milliseconds a few seconds later, the CPU slowed down. Something told it to conserve energy. That something is the power governor reacting to battery state.
What I Built
I wrote a probe that runs a fixed computational workload multiple times over a short window and measures the execution time of each run. No special APIs. No permissions. Just arithmetic and a timer.
The probe collects a series of timing samples, filters out noise caused by browser scheduling and garbage collection, then analyzes the shape of the resulting curve.
A flat curve — consistent timing across all samples — means the CPU is running at a stable frequency. That’s AC power.
A curve that starts fast and degrades over time means the CPU boosted briefly, then the power governor intervened and reduced clock speed. That’s battery.
A consistently slow flat curve means the OS isn’t even trying to boost — power saver mode. Low battery.
Real Results
I tested this on my own machine running Firefox on Linux. The Battery API returns nothing — navigator.getBattery doesn’t exist.
Plugged in, my probe returned samples in the 4-5 millisecond range. Flat. Stable. The classification: AC power, 90% confidence.
Thirty seconds later, unplugged, the same probe returned samples that started around 6 milliseconds and climbed to over 22 milliseconds within seconds. The classification flipped to battery, 73% confidence.
Same machine. Same browser. Same code. The only variable was whether a cable was plugged into the wall.
Why This Matters
Mozilla removed the Battery API because battery state is a fingerprinting vector. But they removed the API, not the information. The information still leaks through CPU timing — a channel that can’t be closed without crippling JavaScript execution entirely.
This has real implications for user privacy:
Cross-site correlation. Two unrelated sites running similar timing measurements simultaneously would observe the same CPU behavior — because they share the same physical hardware. If both sites see the same performance degradation curve at the same moment, they can infer it’s the same device, even without cookies or shared storage.
Session linking. A user clears cookies and revisits a site. Static fingerprints like screen size and GPU match thousands of devices. But a specific CPU throttle pattern at a specific moment in time is far more unique. It adds a temporal dimension to fingerprinting that persists through storage wipes.
Behavioral profiling. Charging patterns reveal daily routines. Always plugged in during business hours? Office worker. Battery drains predictably at night? Personal device. These inferences build a behavioral profile without any explicit tracking consent.
Can It Be Fixed?
Not easily.
The side channel exists because JavaScript can measure time and perform computation. You can’t remove either without breaking the web. Reducing timer resolution — Firefox already clamps performance.now() to mitigate Spectre-class attacks — makes the measurement noisier but doesn’t eliminate it. I achieved reliable classification with Firefox’s reduced-resolution timer.
The fundamental issue is that battery state affects CPU frequency, and CPU frequency affects observable execution time. That causal chain runs through the hardware, below the browser’s control surface.
The only real defense is the same as it’s always been: if you need anonymity, use a desktop machine with no battery, or disable JavaScript entirely.
Context
I built this as part of a legitimate analytics platform for my own portfolio. The probe runs with user consent, behind a cookie banner, for the purpose of understanding what hardware my visitors use. The battery inference was born from the practical problem of Firefox returning no data for a field I wanted to populate.
I’m documenting it here not as an exploit guide, but because I believe the security community should be aware that the Battery API removal did not eliminate the underlying information leak. The data Mozilla tried to protect is still accessible — just through a different window.
The specific implementation details are intentionally omitted.