# Сколько разных отпечатков даёт одна машина

Документ отвечает на вопрос владельца: «как сделать так, чтобы на одной моей
машине можно было получить много разных качественных отпечатков?»

Ответ получен замерами на его же машине (MacBook Pro 16" 2019, Intel,
macOS 14.5, Intel UHD 630 + AMD Radeon Pro 5500M) и разведкой по открытым
источникам, 5–6 августа 2026. Числа здесь — измеренные, а не выведенные;
где оценка, так и написано.

---

## Короткий ответ

**Много — нельзя.** Не «мы пока не умеем», а физическое ограничение: canvas,
WebGL, аудио, шрифты, ядра и экран приходят от конкретного железа. Развести их
можно двумя способами — подменить (ловится) или переключить настоящую
подсистему (их на этой машине три, и одна из трёх сама себе метка).

Что масштабируется на одном хосте — **не отпечатки, а личности**: IP, часовой
пояс, локаль, куки, история, моторика, геометрия окна. Этим Mantia и занята.

---

## Что замерено

### Два профиля сегодня неотличимы

Прогон двух профилей на одной машине, сравнение поверхностей:

| поверхность | профиль 1 | профиль 2 | совпало |
|---|---|---|---|
| canvas-хеш | `8475515648138962` | то же | **да** |
| WebGL-рендерер | ANGLE Intel UHD 630 | то же | **да** |
| аудио | 44100:1:1 | то же | **да** |
| ядра / память / экран | 6 / 16 / 1536×960 | то же | **да** |
| шрифты (29) | `36cd3e4b02ec` | то же | **да** |
| ClientRects | 202.585938×41 | то же | **да** |
| часовой пояс | America/Los_Angeles | America/New_York | нет |

Различаются только IP, пояс, куки и моторика.

### Что меняют флаги запуска, а что нет

Матрица прогнана настоящим Chrome; сравнивались хеши, а не намерения.

| флаг | что меняет |
|---|---|
| `--use-angle=gl` | **всё WebGL целиком**: рендерер уходит на AMD Radeon Pro 5500M, расширений 35→31, меняются лимиты, precision, пиксели и canvas |
| `--use-angle=swiftshader` | рендерер и canvas, но объявляет программный рендеринг — метка «виртуалка» |
| `--disable-gpu-rasterization` | только canvas; причём одинаково при любом бэкенде, то есть разнообразия не даёт |
| `--force-device-scale-factor` | только `devicePixelRatio`. На Retina это **ложь**: настоящий MBP16 всегда отдаёт 2 |
| `--force-color-profile` | ничего |
| `--font-render-hinting`, `--disable-lcd-text` | ничего (этих ключей в обычном Chrome нет вовсе, они от headless-shell) |
| **версия сборки** (150 против 151) | **ничего**: canvas-хеши побайтово равны |

Шрифты, ClientRects, аудио, ядра, память и экран не двигаются ничем.

### Итог по этой машине

**Два честных отпечатка**: Intel через Metal и AMD через OpenGL. Оба —
настоящая работа разных видеокарт ноутбука, подделывать нечего.

Третий (SwiftShader) существует, но объявляет себя программным рендерингом.

И даже эти два делят шрифты, ClientRects, аудио, экран и ядра. Площадка,
считающая их вместе, свяжет профили независимо от canvas.

---

## Опознать против связать

Владелец заметил верно: таких макбуков проданы миллионы, и отпечаток не
уникален. Это **защита, а не проблема** — уникальный отпечаток хуже общего,
потому что выделяет.

Но есть разница, и она решает всё:

* **опознать** — «кто этот человек среди всех людей мира». Здесь когорта
  спасает: таких машин сотни на любой площадке;
* **связать** — «эти пять аккаунтов ведёт один человек». Здесь когорта не
  помогает **вообще**.

Площадке не нужно знать, кто вы. Ей нужно сгруппировать свои же аккаунты:
взять все, сгруппировать по canvas-хешу, внутри группы посмотреть, что ещё
совпадает. Сотня чужих людей с таким же макбуком не имеет с вами общего ничего,
кроме макбука. Ваши пять аккаунтов имеют общего провайдера прокси, тип сети,
время входов, даты создания и манеру работы.

**Совпадение железа — не приговор. Приговор — совпадение железа плюс всё
остальное сразу.**

Отсюда переформулировка задачи: не «сделать отпечатки разными», а **чтобы ни
один признак и ни одно их сочетание не собирало аккаунты в кучку**. Практически:

1. аккаунты **разных** площадок могут делить хост — они в разных базах;
2. прокси важнее canvas: пять аккаунтов с одного дата-центра не спасёт никакой
   отпечаток;
3. время и поведение связывают без отпечатка вовсе.

---

## Почему JS-инъекция не подходит

Технически путь дешёвый: снять требование `worldName` в `cdp/policy.py` и
подменять значения через `Page.addScriptToEvaluateOnNewDocument`. Замер
показывает, что он не работает, и не по неаккуратности:

* в `Worker` инъекция не исполняется вообще — `new Worker()` возвращает
  настоящую машину целиком (замерено: 6 ядер и настоящий ANGLE/Intel против
  подменённых 8 и «Intel Iris» в верхнем фрейме);
* iframe **чужого источника** — а это и есть типовой антифрод-скрипт — не
  покрыт; сравнение его значений с `postMessage` из верхнего фрейма даёт
  противоречие внутри одной страницы;
* подменяется акцессор, а не источник: настоящие пиксели достаются через
  `OffscreenCanvas`, `readPixels`, `toBlob`; `webgl` отдаёт подделку, а
  `webgl2` — правду;
* тайминг работает против: подменённое значение в тысячу раз **дешевле**
  настоящего (не идёт в драйвер), и `performance.now()` это ловит.

Сверх техники есть довод дороже. **Наше правило «JS только через изолированный
мир» и подход «подменим отпечаток инъекцией» взаимно исключают друг друга**:
изолированный мир имеет свой глобальный объект, страница его не видит — в этом
весь смысл. А инъекция ломает `coverage.py`, `coherence.py` и строку в
интерфейсе «от настоящего браузера, не подделываем», то есть единственное, чем
продукт отличается от Octo и AdsPower: ему нечего скрывать от проверки на
перехват.

---

## Форк Chromium: цена и что он на самом деле даёт

Все серьёзные антидетекты (Dolphin, Octo, AdsPower, GoLogin, Multilogin,
Incogniton) правят C++ и пересобирают браузер. Причина не идеологическая: из JS
не чинятся три вещи — `toString`/дескрипторы/прототипы палят обёртку, воркеры и
OOPIF требуют покрыть каждый контекст, а TLS и HTTP/2 вообще вне JS.

**Цена (оценка разведки):** 160–400 человеко-часов входа, 100–350 часов в год
содержания, 150–200 ГБ диска. С 8 сентября 2026 Chrome переходит на
двухнедельные майлстоуны — это **26 ребейзов в год**. У Brave этим занята
штатная команда, 946 файлов патчей. MacBook с 16 ГБ для сборки не годится:
рекомендация 64+ ГБ, практическое правило 4–5 ГиБ на поток.

**И он даёт меньше, чем обещают.** Открытый форк `fingerprint-chromium` в
Chrome 144 **убрал** флаги подмены GPU совсем: строку рендерера подменить
легко, а пиксели по-прежнему рисует настоящая видеокарта, и хеш отрисованной
сцены с лимитами продолжает описывать реальное железо. Планка у конкурентов
ниже, чем принято думать: AdsPower в публичном API предлагает
`device_memory = 6` — значения, которого по спецификации не существует
(бывают степени двойки), и одна строка `navigator.deviceMemory` ловит профиль.

---

## Арифметика решения

| путь | даёт | стоит |
|---|---|---|
| второе физическое устройство | **+1 полный, идеально согласованный** отпечаток | $400–800 разово, поддержка нулевая |
| форк + корпус | сколько угодно, ограничено корпусом | 160–400 ч входа, 100–350 ч/год **навсегда** |

Нужно 5 отпечатков — железо $3 000 один раз. Нужно 50 — $30 000 один раз, всё
ещё дешевле одного года форка. Нужно 500 — тогда форк начинает выигрывать.

**Пока речь про десятки — покупать железо, а не писать код.**

Оговорка: облачные VM не годятся. Там SwiftShader или паравиртуальный GPU, то
есть готовая метка «виртуалка». Нужен bare-metal с настоящей видеокартой.

---

## Порядок действий, если понадобится больше

1. **Показать ёмкость хоста.** `fingerprint_check.py` уже снимает canvas- и
   аудио-хеши именно для сравнения профилей между собой, но человеку не
   говорит: «профилей на этом устройстве семь, различимых отпечатков один».
   Продукт обязан сказать это **до** запуска, а не после блокировки. Полдня.
2. **Пер-профильная геометрия окна.** `api/engine.py:_window_for` даёт всему
   флоту один размер и позицию, а у живых людей `innerWidth/innerHeight`
   разбросаны сильнее всего. Единственная бесплатная ось, ещё не использованная.
3. **Ответить числом:** сколько аккаунтов ОДНОЙ площадки нужно одновременно.
   До трёх — хватит пунктов 1–2. Пять–тридцать — покупать устройства.
   Больше пятидесяти — тогда корпус, и только потом форк.

Чего не делать: не снимать запреты с флагов без замера на Windows (боевая
платформа, там другие бэкенды ANGLE); не подменять отпечаток инъекцией; не
гнаться за уникальностью — цель несвязываемость, а не редкость.


## Поправка: классов больше, чем считалось

Замер выше давал **два** отпечатка с машины. Он верен для того, что мерили:
разные сборки Chromium и разные каталоги профиля действительно не меняют
canvas. Но мерили не всё.

Настоящую отрисовку меняют **флаги запуска**, и это не подделка: страница
правда рисуется другим железом или другим растеризатором, поэтому строка
рендерера и пиксели совпадают между собой. Противоречия, по которому в
[competitors.md](competitors.md) ловятся все восемь чужих профилей, здесь нет
по построению.

Рычагов два, и они независимы, то есть перемножаются:

* какой видеокартой рисовать — `--force_low_power_gpu` против
  `--force_high_performance_gpu`;
* чем растеризовать 2D-холст — видеокартой или процессором
  (`--disable-accelerated-2d-canvas`).

Замерено сквозным прогоном БОЕВОГО зонда на четырёх настоящих профилях Etsy из
базы продукта, счёт вёлся нашим же `combo_hash`:

| Профиль | Класс | `combo_hash` | Рендерер |
|---|---|---|---|
| 1 Etsy | `low_power` | `10cfb11a…` | ANGLE (Intel, Metal, UHD 630) |
| 2 Etsy | `high_perf` | `7ccf5c4e…` | ANGLE (AMD, Metal, Radeon Pro) |
| 3 Etsy | `low_power_cpu2d` | `835e2137…` | ANGLE (Intel, Metal, UHD 630) |
| 4 Etsy | `high_perf_cpu2d` | `980cc226…` | ANGLE (AMD, Metal, Radeon Pro) |

**Четыре разных устройства из четырёх.**

**Что НЕ сработало.** Отдельным замером проверены ещё семь флагов: цветовой
профиль (`srgb`, `display-p3`, `generic-rgb`), хинтинг шрифтов, субпиксельное
позиционирование, отключение LCD-текста — на macOS ни один не изменил ни
холст, ни WebGL. Это флаги для других платформ. Плотность экрана меняет только
ширину прямоугольников, но не холст.

**Класс считается по месту профиля ВНУТРИ ПЛОЩАДКИ, а не по сквозному номеру.**
Это не тонкость: на живых данных сквозной номер дал столкновение — профили 23 и
27, оба Etsy, при четырёх классах получили один и тот же. Два аккаунта одной
площадки стали близнецами, то есть ровно то, ради чего всё затевалось,
сломалось бы молча.

**Про `--use-angle=gl`.** Он даёт ещё два класса, и замер это подтвердил: шесть
сочетаний — шесть разных `combo_hash`. Флаг оставлен в чёрном списке
`cdp/flags.py`. Половину его обоснования («подмена бэкенда меняет
`WEBGL_debug_renderer_info`») замер снимает: подмены нет, меняются согласованно
и строка, и пиксели. Вторая половина остаётся: Chromium на macOS с M115 рисует
через Metal, OpenGL в живой популяции редок, а редкость палевна сама по себе.
Отменять запрет имеет смысл тогда, когда корпус скажет долю таких машин.

**Три оговорки, без которых число врёт.**

Первое: зависит от железа. Одна видеокарта — рычаг остаётся один, классов два.
Притворяться нельзя: флаг дискретной карты там, где её нет, ничего не изменит,
а профиль будет числиться «другим» и уйдёт близнецом.

Второе: **четыре — это не сто.** Пятый профиль одной площадки повторит первый.
Продукт обязан это показывать, а не прятать; величину отдаёт
`gpu_classes.honest_capacity`.

Третье: класс меняет отрисовку, но НЕ меняет шрифты, звук, экран и часовой
пояс. Профили остаются похожи по этим осям, и это правда про одну машину.

Отсюда честная формула уровня 1: **на одну площадку столько профилей, сколько
у машины классов** — на этом MacBook четыре. Для сотни нужен уровень 2, то есть
форк, и порядок работ от этого не меняется.


## Где кончается путь без форка

Это главный итог замеров, и он отвечает на вопрос «зачем вообще форк, если
флаги работают».

**Отрисовку звука не меняет НИ ОДИН флаг.** Проверено: `--audio-buffer-size`
128 и 2048, `--disable-audio-output`, обе видеокарты, обе плотности экрана —
во всех семи вариантах `audioRender` дал ровно `124.04347658`.

Причина не в том, что мы не нашли нужный флаг. `OfflineAudioContext` считает
арифметику с плавающей точкой в памяти: результат зависит от процессора и
сборки Chromium, а не от того, чем и как рисуется страница. Менять там нечего,
пока не меняешь сам код.

Отсюда точная граница:

| Поверхность | Меняется флагами | Чем |
|---|---|---|
| canvas (текст и геометрия) | **да** | видеокарта, растеризатор 2D |
| отрисовка WebGL | **да** | видеокарта |
| строка рендерера | **да** | видеокарта |
| ClientRects | **да** | плотность экрана |
| **отрисовка звука** | **НЕТ** | ничем |
| шрифты | нет | список берётся у системы |
| экран, часовой пояс, язык | да, но это уже делается | |

**Что это значит на практике.** Профили одной машины различаются по холсту и
WebGL, но остаются связаны по звуку. Одного `GROUP BY` по `audioRender` хватит,
чтобы собрать их в кучу — если площадка его считает.

Ровно на этом мы поймали Dolphin: у всех восьми их профилей отрисовка звука
совпала с настоящей машиной ([competitors.md](competitors.md)). Разница между
нами и ими в том, что **у них переключатель есть**, просто выключен по
умолчанию, а у нас его нет и без форка не будет.

**Поэтому форк нужен, и нужен ровно для одного** — зашумить то, что рождается
из вычислений, а не из отрисовки: звук в первую очередь. Всё остальное уже
получается флагами, честно и без подделки.

Порядок работ от этого не меняется: сначала корпус (он скажет, какой шум
правдоподобен), потом форк. Но цель форка теперь названа точно, а не «чтобы
как у всех».
