Как тест приватности не хранит никаких данных
Тест приватности — это инструмент, который показывает вам, что сайты могут узнать о вашем браузере: сигналы canvas и GPU, шрифты, экран, часовой пояс, язык, сетевые подсказки и тихие идентификаторы, позволяющие сайту узнать вас при возвращении, — и это создаёт проблему доверия: инструмент, демонстрирующий техники слежки, судят и по его собственным практикам обращения с данными, которые не могут быть страницей политики, добавленной задним числом. Правило проектирования нашего теста отпечатка браузера умещается в одно предложение: если браузер может это прочитать, сервер этого никогда не видит. Эта статья объясняет, чего стоит это правило, где оно сгибается и где останавливается. Если сначала вам нужен перечень сигналов, начните с что сайты знают о вас; здесь речь о сантехнике под тестом, а не о самих сигналах.
Сначала клиентская сторона
Каждый сигнал, который предлагает сам браузер, читается и отображается локально, на вашей машине, кодом страницы в вашей вкладке. Рендеринг canvas, строки рендерера WebGL, свойства аудиоконтекста, пробы установленных шрифтов, геометрия экрана и окна, глубина цвета, поддержка касаний, оценки хранилища, медиазапросы о контрасте и движении — всё это вычисляется там, где обнаруживается, и рисуется на странице, которую вы видите. Код страницы — единственное, что когда-либо держит полную картину. Ни один скрипт не собирает панель в полезную нагрузку, ни один маяк не уносит её домой и ни одно аналитическое событие её не суммирует, потому что в тот момент, когда полный отпечаток покидает браузер, тест становится тем, о чём он предупреждал.
Этот порядок — сначала локально, сервер в последнюю очередь, и сервер только для тех немногих задач, которые браузер действительно не может сделать сам, — не свойство архитектуры; это и есть архитектура. Большинство инструментов приватности построены наоборот: собрать всё централизованно, а потом пообещать удалить, анонимизировать или хранить недолго. Эти обещания могут выполняться, но это обещания, и читатель не может проверить удаление. Конструкция, которая вообще не передаёт данные, заменяет обещание фактом о пути кода, а факты о путях кода можно проверить во вкладке сети — именно тот стандарт, к которому инструмент приватности должен приглашать.
Цена реальна и принята замыслом. Нет ничего меж-устройственного: ни аккаунта, который следует за вами, ни истории, которая синхронизируется, ни сравнения с вашим отпечатком на другом ноутбуке, потому что всё это требует, чтобы сервер хранил что-то по каждому человеку. Сравнения ограничены тем, что ваш собственный браузер помнит локально, и анонимными дневными счётчиками, описанными ниже. Когда функция потребовала бы полного отпечатка на сервере, ответ — не более осторожная политика хранения, а нет. Ограничение и есть продукт.
Три маленьких эндпоинта, три разные задачи
Три задачи действительно нельзя выполнить внутри страницы, и каждая получает свой маленький эндпоинт со своей узкой задачей. Первый, /api/check/whoami, возвращает только то, что любой сервер уже видит в запросе, который принёс страницу: ваш сетевой адрес таким, каким он пришёл, заголовки, которые ваш браузер отправил вместе с ним, и грубую географию, привязанную к этому адресу. Показ вам этих значений не добавляет нового раскрытия, потому что каждый сервер, который вы посещаете, получает их, показывает он их или нет; эндпоинт просто превращает одностороннее раскрытие в видимое и не хранит ничего сверх ответа.
Второй, /api/check/etag, существует только для демонстрации распознавания на основе кэша, объяснённого в следующем разделе: он выдаёт случайный идентификатор кэша и не хранит вообще ничего — ни таблицы выданных значений, ни записи о том, кто спрашивал. Третий — эндпоинт счётчиков: он принимает горсть грубых значений из завершённого запуска и возвращает текущие итоги, чтобы страница могла сказать, насколько обычна ваша конфигурация, при этом никто не хранит ваш визит. Каждый эндпоинт ограничен по скорости, как и другие проверяющие инструменты сайта, потому что маленькое и честное — не то же самое, что неограниченное, а демонстрационный эндпоинт, который можно долбить, — это поверхность злоупотребления с добрыми намерениями.
Страница также прямо называет внешние службы, к которым она обращается напрямую из вашего браузера, потому что эти службы тоже видят ваш адрес, и честность о них — часть замысла. Тест читает эндпоинты трассировки Cloudflare, запрашивает службу DNS-эха, чтобы показать, какой резолвер отвечает за вас, и открывает соединения к двум публичным STUN-серверам, чтобы показать, что может узнать запрос в стиле WebRTC. Каждая названа на самой странице, рядом с результатом, который она даёт, — та же дисциплина раскрытия, что мы применяем к проверкам утечек в утечки VPN и проверки маскировки. Тест приватности, который тихо звонил бы третьим сторонам, провалил бы собственный урок.
Трюк с ETag, объяснённый честно
ETag — обычная часть HTTP-кэширования: сервер прикрепляет к ответу валидатор, браузер сохраняет его вместе с кэшированной копией, а при следующем визите браузер отправляет валидатор обратно, чтобы сервер мог ответить «не изменено» и сэкономить передачу. Трюк в том, что валидатор не обязан описывать содержимое. Сервер может выдать уникальный валидатор каждому посетителю, и браузер будет верно возвращать это уникальное значение при каждой повторной проверке — в этот момент сервер узнаёт браузер ровно с той же надёжностью, что и с cookie, ни разу её не установив. Проверка кэша работает как идентификатор, потому что браузер правильно делает свою работу.
Вот почему очистка cookie её не очищает: cookie и кэш — отдельные хранилища, опустошаемые отдельными средствами управления по отдельным расписаниям. И вот почему приватное окно или окно инкогнито ломает трюк — приватное окно работает со своим отдельным кэшем, который выбрасывается при закрытии окна, так что валидатор умирает вместе с сессией. Наш эндпоинт демонстрирует путь туда и обратно и ничего не хранит: распознавание доказывает сам ответ 304, сервер, видящий, как его собственное случайное значение возвращается, а не какая-либо запись, хранящаяся между визитами. Демонстрация заканчивается в момент закрытия страницы — ровно то свойство, которого не предлагают настоящие трекеры, использующие эту технику.
Есть одна честная тонкость, которую стоит назвать: краевой кэш. Если бы ответы проходили через общий кэш CDN, идентификатор первого посетителя мог бы выдаваться всем, и демонстрация показала бы толпу, делящую один ID, вместо того, чтобы каждый браузер держал свой. Поэтому этот эндпоинт полностью отказывается от краевого кэширования — каждый запрос достигает источника, каждый посетитель получает свежее случайное значение, и ни один общий кэш никогда не держит ни одного. Это маленькая строка конфигурации, но это разница между демонстрацией, доказывающей механизм, и демонстрацией, случайно доказывающей обратное.
Счётчики вместо журналов
Один вопрос действительно нуждается в сервере: «насколько я необычен?» Страница, которая может сказать ваш GPU появился в N из M запусков сегодня, учит гораздо большему, чем страница, которая может только перечислить сигналы, потому что редкость и делает отпечаток идентифицирующим. Но отвечать на него очевидным способом — записывать каждый визит, а потом считать — значит построить ровно ту базу данных, о которой предупреждает инструмент. Поэтому тест хранит счётчики вместо журналов: одно число на значение в день. Операционная система, семейство браузера, строка GPU, часовой пояс, первый язык, класс размера экрана, первые восемь символов идентификаторов отпечатка, результат каждой отдельной проверки и интервал длительности запуска — каждое вносит черту в дневной итог, и больше ничего не хранится.
Границы важнее механизма. Полный отпечаток никогда не отправляется, не говоря уже о хранении; ваш адрес никогда не отправляется счётчику вообще. Счётчик нельзя опросить в обратную сторону до человека, потому что нет строки по посетителю, которую можно найти, — агрегация происходит в момент подсчёта, а не позже как шаг отчётности по сохранённым записям. По той же причине итоги сбрасываются по дням: счётчик, который накапливался бы вечно и был нарезан достаточно тонко, через накопление тихо стал бы базой данных отпечатков, и замысел отвергает этот дрейф заранее, а не извиняется за него потом.
Ещё два выбора защищают людей, которые никогда не прочитают эту статью. Браузеры, отправляющие сигнал Global Privacy Control, вообще никогда не считаются — сигнал означает «не отслеживай меня», и самое уважительное его прочтение — полностью исключить эти запуски, а не считать их осторожно. Все остальные получают видимую возможность отказа на странице, соблюдаемую до того, как произойдёт какой-либо подсчёт. А запуск, который не завершается за 45 секунд, считается один раз как «застрявший» в своём собственном интервале: медлительность и поломки остаются видимыми для нас как операторов в виде одного агрегированного числа, без следования за каким-либо отдельным посетителем через неудачный запуск.
Что мы решили не строить
Самая поучительная часть замысла — список оставленных вещей. Нет пробы на уровне соединения: ни снятия TLS-отпечатка, ни чтения вашего рукопожатия в стиле JA3. Честная причина архитектурна — TLS для этого сайта завершается на CDN, стоящей перед ним, поэтому чтение рукопожатия, взятое на нашем источнике, описывало бы переговоры и настройки CDN куда больше, чем вашего браузера. Публиковать его как «ваш TLS-отпечаток» было бы театром: правдоподобно выглядящее значение с чужим именем. Инструмент приватности, который печатает одно уверенное неверное чтение, учит своих пользователей доверять уверенным неверным чтениям везде, что хуже пустой панели.
Та же дисциплина сформировала остальные пропуски. Счётчики грубы, потому что тонкие счётчики становятся базой данных отпечатков через накопление, по одному невинному измерению за раз. Память о последнем визите — «с вашего последнего запуска эти значения изменились» — живёт только в хранилище вашего собственного браузера, на вашей машине, и никогда не передаётся; в момент, когда она синхронизировалась бы, она стала бы историей, а история — это запись. Функции, которые были бы действительно полезны, были вырезаны по той же причине, по которой эндпоинты остались маленькими: полезность — не защита, когда цена в данных — весь смысл инструмента.
Наконец, остаточная честная заметка, потому что ни одна статья о замысле не должна заканчиваться собственными комплиментами. Этот замысел минимизирует данные; он не может сделать сами чтения менее идентифицирующими. Ваш GPU, ваши шрифты, ваш экран и ваш часовой пояс сочетаются в нечто почти уникальное, хранит их кто-то или нет, — это и есть вывод, который инструмент существует, чтобы вам показать. Хранение никаких данных защищает вас от нас. Оно не защищает вас от самой техники, и притворяться иначе было бы единственной по-настоящему нечестной функцией, которую мы могли бы выпустить.