navigator.getBattery() без разрешения выдавал заряд и статус зарядки. Почему Firefox убрал API, WebKit его так и не внедрил, и чему это учит.
Несколько лет подряд любой сайт мог задать вашему ноутбуку или телефону довольно личный вопрос — «сколько у тебя осталось заряда и заряжаешься ли ты сейчас?» — и мгновенно получить ответ, без единого запроса разрешения. Battery Status API создавался ради вполне разумной цели: позволить странице приглушить анимации или отложить фоновую синхронизацию, когда устройство пользователя вот-вот разрядится. Исследователи показали, что его можно читать и как краткоживущий сигнал слежки, и в течение пары лет один крупный браузер убрал его, а другой так и не реализовал вовсе. Это один из самых наглядных реальных примеров того, как задуманный с благими намерениями Web API откатывают назад, когда становится ясна его цена для приватности.
Ключевые выводы
navigator.getBattery()без запроса разрешения отдавал любому скриптуlevel,charging,chargingTimeиdischargingTime— редкое сочетание для API, связанного с оборудованием.- Исследование 2015 года «The Leaking Battery» (Olejnik, Acar, Castelluccia и Diaz) показало, что эти показания позволяют повторно опознать браузер на разных сайтах в течение короткого окна, а там, где браузер отдавал их с полной точностью, скрипт мог вычислить и реальную ёмкость батареи — куда более долгоживущий сигнал.
- Firefox убрал доступ веб-контента к API в версии 52 (март 2017), сославшись на риск для приватности; Safari/WebKit так и не реализовал его вовсе, сочтя эту поверхность фингерпринтинга неоправданной.
- Браузеры на Chromium по-прежнему поддерживают API — Chrome, Edge, Opera и Samsung Internet реализуют
navigator.getBattery(). - Проверка отпечатка BrowserInsight показывает, какие из подобных менее заметных сигналов раскрывает именно ваш браузер.
Что раскрывал navigator.getBattery()
API добавлял в navigator единственный метод, возвращающий промис. Там, где он поддерживался, чтение занимало одну строку и не требовало никакого взаимодействия с пользователем:
navigator.getBattery().then((battery) => {
console.log(battery.level); // от 0 до 1, например 0.73
console.log(battery.charging); // true / false
console.log(battery.chargingTime); // секунд до полной зарядки, или Infinity
console.log(battery.dischargingTime);// секунд до разрядки, или Infinity
});
Работали четыре значения: дробный уровень заряда level, булево состояние charging и две оценки времени, которые браузер пересчитывал по мере наблюдения за поведением батареи. API также генерировал события levelchange, chargingchange, chargingtimechange и dischargingtimechange, так что скрипту даже не требовался опрос — достаточно было просто подписаться и наблюдать в реальном времени, как у посетителя садится батарея.
Правила доступа были предельно мягкими. API доступен только в защищённом контексте (то есть требует HTTPS) и не выдаётся в Web Workers, но внутри обычной HTTPS-страницы не было ни запроса разрешения, ни требования жеста пользователя, ни какого-либо признака в интерфейсе, что страница что-то прочитала. Главное же — сторонний аналитический или рекламный скрипт, подключённый прямо на страницу (а это самый обычный способ), получал ровно тот же доступ, что и собственный код сайта. (В нынешней спецификации появилась политика разрешений battery со списком по умолчанию ["self"], которая хотя бы закрывает доступ кросс-доменным iframe, пока встраивающая страница явно его не откроет.)
Исследование «The Leaking Battery»
Проблема приватности была не в том, что какое-то одно значение сильно идентифицирует человека — заряд 73% в любой момент есть у множества людей. Проблема в том, что все четыре значения вместе, непрерывно обновляясь, формировали кратковременный, но точный вектор состояния, который два разных сайта (или два разных трекера на одном сайте) могли сопоставить и сравнить — даже если пользователь блокировал куки, открывал приватное окно или переключался между вкладками. В окне от нескольких секунд до пары минут совпадающего level вместе с близкими оценками chargingTime/dischargingTime хватало, чтобы связать два формально независимых визита с одним и тем же устройством.
Наибольшее внимание привлекло то, округлялись ли эти числа вообще, — а они не округлялись. В Firefox под Linux значение level отдавалось скриптам с полной точностью double, почти без изменений транслируясь из демона управления питанием операционной системы: вместо аккуратного 0.73 страница видела длинную дробь. Это важно потому, что level — это отношение текущего заряда к полной ёмкости батареи: имея достаточно знаков, скрипт может пройти от этого отношения обратно к самой ёмкости — свойству физического оборудования, а не текущего заряда, которое почти не меняется от одной сессии просмотра к другой.
Исследователи отметили, что бьёт это сильнее всего как раз по тем, кто заметит это в последнюю очередь. Ёмкость батареи деградирует со временем, отходя от заводского значения по-своему у каждого конкретного аккумулятора, поэтому старая и много поработавшая батарея даёт более необычное — а значит, и более опознаваемое — число, чем новая. Их предложение состояло не в том, чтобы убрать API, а в том, чтобы огрубить показания: для реальных задач это не стоит ничего — странице, которая гасит анимации при 20% заряда, не нужны лишние знаки после запятой.
Почему Firefox убрал API, а WebKit так и не реализовал его
Firefox был одним из первых, кто это реализовал: префиксный navigator.mozBattery работал по умолчанию начиная с Firefox 11 в 2012 году, а промис-версия navigator.getBattery() появилась в Firefox 43 — Chrome выпустил getBattery() годом раньше, в Chrome 38. Первой реакцией Mozilla на исследование стала именно та точечная правка, о которой просили авторы: срезать точность показаний. Более широкое решение пришло позже. Firefox 52, вышедший в марте 2017 года, вообще убрал у веб-контента возможность вызывать navigator.getBattery(), посчитав, что цена для приватности больше не оправдывает ту небольшую пользу, которую API давал большинству сайтов. Для собственного привилегированного кода Firefox API сохранил — поэтому изменение и описывают как отзыв доступа у веб-контента, а не как полное удаление функции.
Движок WebKit от Apple с самого начала пошёл по более осторожному пути: он никогда не предоставлял navigator.getBattery() в Safari — в русле общей позиции WebKit отказываться от API, если команда считает, что риск фингерпринтинга перевешивает пользу.
Браузеры на Chromium — исключение. Таблица совместимости на caniuse.com по-прежнему показывает поддержку API в Chrome, Edge, Opera и Samsung Internet — это около 78% мирового использования браузеров по данным caniuse на момент написания, — тогда как Firefox и все варианты Safari, включая Safari на iOS, сообщают об отсутствии поддержки. Это трёхстороннее расхождение (Chromium: да, Firefox: убрали, WebKit: никогда не было) означает, что само наличие navigator.getBattery на странице — это грубый, трудно подделываемый сигнал движка, из той же категории, что и специфичная для Chromium доступность Network Information API.
Урок для новых Web API
Откат Battery Status API — полезный кейс именно потому, что он произошёл публично, на довольно коротком промежутке времени, и по причине, никак не связанной со злым умыслом разработчиков — API вышел ровно так, как был описан в спецификации, и риск стал очевиден лишь после того, как независимые исследователи внимательно изучили, на что способно непрерывное, не требующее разрешения чтение числового датчика при сопоставлении между источниками. В текущем рабочем черновике W3C этот урок теперь закреплён напрямую: там прямо сказано, что пользовательский агент «не должен раскрывать высокоточные показания статуса батареи, поскольку это может создать новый вектор фингерпринтинга», а также «может искажать раскрываемое значение», чтобы скрипт не мог быть уверен, видит ли он реальные данные оборудования вообще.
Это и есть закономерность, которую стоит обобщать: любой API, сообщающий непрерывно обновляющееся вещественное значение с оборудования — батарея, датчики, счётчики производительности, — должен отвечать не только на вопрос «идентифицирует ли одно это показание пользователя», но и на вопрос «позволит ли наблюдение за изменением этого значения во времени двум разным скриптам узнать один и тот же браузер». Округление по «корзинам» — та же мера, которую разработчики Network Information API применили к downlink и rtt, — теперь по умолчанию ожидается для API этого класса, а не добавляется задним числом.
Что это значит для вас сегодня
Если вы пользуетесь Firefox или Safari, этот конкретный сигнал уже закрыт — ни один из браузеров его не раскрывает, настраивать ничего не нужно. Если вы на браузере на базе Chromium, navigator.getBattery() по-прежнему может вызвать любая посещаемая вами страница, хотя его вклад в общий отпечаток браузера намного меньше, чем у canvas или WebGL, поскольку уровень заряда и статус зарядки постоянно меняются, а не остаются стабильными от сессии к сессии. Здесь подойдут те же общие меры защиты, что и для других второстепенных сигналов: расширение, блокирующее скрипты или контент, или браузер с более строгими настройками приватности по умолчанию.
Есть и обратное применение — именно поэтому проверка отпечатка BrowserInsight читает батарейный API там, где он есть, а не игнорирует его. Четыре значения обязаны быть согласованы между собой: устройство, которое одновременно заявляет, что не заряжается и заряжено не полностью, но при этом сообщает «ноль секунд до полной зарядки» и «бесконечность до разрядки», описывает батарею, которой физически не бывает. Реальное железо такой комбинации не даёт — а подставной, скриптованный профиль браузера даёт. Так что те же самые показания, что когда-то сделали API риском для слежки, сегодня работают дешёвой проверкой на согласованность: браузер, который врёт о себе, обычно попадается не на одном значении, а на противоречиях в отпечатке.
Часто задаваемые вопросы
Battery Status API всё ещё доступен в каком-нибудь браузере?
Да — Chrome, Edge, Opera и Samsung Internet по-прежнему реализуют navigator.getBattery(). Firefox убрал доступ к нему в версии 52 (2017), а Safari/WebKit так его и не реализовал.
Может ли сайт узнать точный уровень заряда моей батареи?
В браузерах, где API ещё поддерживается, — да: level сообщает заряд как дробь от 0 до 1 без единого запроса разрешения. В Firefox и Safari этого API попросту нет, вызывать нечего.
Использовался ли Battery Status API как реальный инструмент слежки на практике?
Свидетельств широкого злоупотребления им как основным трекером до реакции браузеров нет — толчком стало академическое исследование, показавшее риск, а не задокументированная массовая эксплуатация. Браузеры среагировали на опережение, а не постфактум, что тоже делает этот случай хорошим примером для изучения.
Блокировка JavaScript закрывает этот сигнал?
Да. Поскольку вызов navigator.getBattery() требует выполнения скрипта, отключение JavaScript или расширение, блокирующее скрипты, не даёт странице прочитать его — в тех браузерах, где API всё ещё присутствует.
Заключение
Battery Status API — небольшой, почти забытый эпизод истории веб-платформы, но он ясно иллюстрирует более общую мысль: API без запроса разрешения не обязан раскрывать ваше имя или местоположение, чтобы стать проблемой приватности, — непрерывно обновляющийся числовой датчик, читаемый из достаточного числа источников, справится с этим не хуже. Удаление в Firefox и отказ WebKit от реализации — ранние, конкретные примеры того, как браузеры стали относиться к поверхности фингерпринтинга как к полноценному критерию дизайна, а не к второстепенному нюансу, — позиция, которая теперь прямо видна в том, как с самого начала специфицируются новые API вроде Network Information. Запустите проверку отпечатка BrowserInsight, чтобы увидеть, какие подобные малозаметные сигналы раскрывает ваш собственный браузер.
Рекомендуем почитать:


