# Что устроено у соседей

Разбор семи установленных антидетект-браузеров, август 2026. Документ отвечает
на один вопрос: **что из этого брать себе, а что нет.**

Метод: у каждого продукта взят его собственный Chromium, снят `strings` с ядра
и вычтен `strings` чистой сборки Chrome for Testing 151.0.7922.76. Разница —
их патч. Дальше в разнице ищутся пути своих исходников, ключи настроек и
сообщения-правила вида «noiseScale must be in range 0..3». Способ работает,
потому что все шестеро оставляют в бинаре пути `../../<своё дерево>/*.cc` —
патч называет себя сам.

Оговорка: часть выводов — из строк бинаря, а не из наблюдаемого поведения.
Где утверждение проверено замером, это сказано отдельно.

## Кто чем оказался

| Продукт | Оболочка | Свой форк | База Chromium | Своё дерево |
|---|---|---|---|---|
| Dolphin Anty 2026.70.214 | Electron | Anty.app, 428 МБ | 151.0.7922.72 | `components/zubre/overrides/` — 9 файлов |
| Vision 3.7.11 | Rust + Tauri | Vision.app, 366 МБ | 151.0.7922.72 | `components/vision_fingerprinting_helper/` |
| Octo Browser 3.0.7 | Python + React | Octium.app, 354 МБ | 151.0.7922.47 | целое дерево `0cto/` поверх `src/` |
| Undetectable 2.49.1 | Qt 6 | Undetected.app, 385 МБ | 151.0.7922.47 | `undetectable/core/` |
| AdsPower Global 8.7.23 | Electron | SunBrowser 131 и 150, плюс FlowerBrowser (Firefox 132) | 131 и 150 | ключи `--protected-*` |
| 0detect Browser 3.32.10 | Electron | есть | — | `aqum/` |
| Incogniton | — | IncognitonBrowser | **99.0.4844.51** | своего нет |

Три вывода из одной таблицы.

**Все всерьёз работающие — это форк Chromium с патчем на C++.** Ни у кого
инъекция JavaScript не является основным способом. У Undetectable JS есть, но
он крошечный, не обфусцированный и занимается не подделкой, а удалением лишнего
ради согласованности. У 0detect в 171 924 строках разницы **ноль** вхождений
`defineProperty`. Это прямое подтверждение того, что записано в
[fingerprint-diversity.md](fingerprint-diversity.md): инъекция заметнее
исходного значения.

**Incogniton — контрпример.** Chromium 99 — это 2022 год. Такую сборку площадка
узнаёт по одной версии, ничего не измеряя.

Поправка, купленная замером: сначала здесь стояло «своего патча нет вовсе» —
на основании того, что в бинаре нет путей вида `../../<своё дерево>/*.cc`.
Прямой замер это опроверг. Сборка запущена на лабораторный стенд и сверена с
чистым Chrome на той же машине: ядра 12 против настоящих 6, память 8 против
16, хеш canvas другой, ClientRects `202.585938` против `202.593750`. Шрифты и
звук при этом совпали. То есть подделка есть, просто следов исходников в
бинаре не осталось. **Отсутствие путей не означает отсутствия патча.**

Там же видно, чем ловится он сам: `navigator.userAgentData.brands` — пустой
список при UA, заявляющем Chrome 99. Такого сочетания не бывает ни у одной
настоящей машины.

**Dolphin и Vision собраны из одного тега** — 151.0.7922.72. Совпадение
говорит о цене занятия: форк надо ребейзить на каждый релиз, и все сидят на
свежем. Кроме Incogniton, который не сидит.

## Главное: у двоих работает ровно наш корпус

Это самая ценная находка разбора, и она проверена не по строкам, а глазами.

**Vision.** В `~/Library/Application Support/Vision/cache/` лежит 21 файл по
130 КБ. Внутри — JSON вида `md5 → data:image/png;base64,...`. Клиент качает их
с `/api/v1/fingerprints/cache/v2`, заголовок `X-CRC`, в клиенте есть
`crates/tauri-plugin-sys/src/canvas_cache.rs`. В журнале запуска профиля прямо
написано: «starting canvas job… canvas job finished».

**Octo.** С другой стороны той же схемы: в ядре сидит
`OctoCanvasLogger::GetDataURLForCanvas` и настройки `css.logging.whitelist`,
`css.logging.blacklist`, `css.logging.min_canvas_width`, `css.logging.api ∈
{toDataUrl, getImageData}`. То есть их браузер **подсматривает, что рисуют на
живых сайтах их же клиенты**, и отправляет к себе. В докстроке их же модуля
`canvas_cache.pyc` сказано, что сервер хранит canvas-скрипты и холсты
настоящих пользователей как непрозрачные блобы.

Смысл у обоих один: не шуметь поверх своей картинки, а **подставить настоящую
картинку живого человека**, подобранную по хешу рисующего скрипта.

Это подтверждает порядок работ, выбранный в
[fingerprint-corpus.md](fingerprint-corpus.md): сначала корпус, потом форк.
Два независимых коммерческих продукта пришли к той же схеме. Разница в
источнике: они снимают с браузеров своих клиентов на чужих сайтах, мы — со
своих шести сайтов, по человеческому клику, без личных данных.

## Замер: восемь их профилей против настоящей машины

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

| Профиль | Рендерер, как он себя называет | canvas | отрисовка WebGL | отрисовка звука |
|---|---|---|---|---|
| чистый Chrome (правда) | Intel UHD | `7177c1d2…` | `f497b91e…` | `124.04347658` |
| антидетект 1 | **Apple M4 Pro** | `7177c1d2…` | `f497b91e…` | `124.04347658` |
| антидетект 2 | **Apple M1 Pro** | `7177c1d2…` | `f497b91e…` | `124.04347658` |
| антидетект 3 | **Intel Iris Xe**, ОС Windows | `7177c1d2…` | `f497b91e…` | `124.04347658` |
| антидетект 4 | **Apple M1 Max** | `7177c1d2…` | `f497b91e…` | `124.04347658` |
| антидетект 5 | **Intel Iris OpenGL** | `7177c1d2…` | `f497b91e…` | `124.04347658` |
| антидетект 6 | Intel UHD | `7177c1d2…` | `f497b91e…` | `124.04347658` |
| антидетект 7 (Firefox) | Generic Renderer | `53691e71…` | `863a0667…` | `38.22506343` |
| антидетект 8 (шум включён) | **Apple M5** | `1602d73d…` | `f497b91e…` | `124.03231224` |

**Шесть профилей из восьми побайтно совпали с настоящей машиной хозяина** по
всем четырём поверхностям, которые нельзя подделать строкой.

Отсюда три вывода, и каждый проверяем.

**Первый. Подпись меняют, картинку — нет.** Профиль сообщает
`UNMASKED_RENDERER = «ANGLE (Apple, Apple M1 Max)»`, а хеш реально отрисованной
сцены совпадает с Intel UHD этой машины. Противоречие ловится одной проверкой
внутри одной страницы, без всякой библиотеки отпечатков: нарисуй сцену и
сверь с тем, что обещала подпись. Мы должны уметь и то, и другое — и не
допускать такого у себя.

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

**Третий, и он оказался точнее, чем выглядел.** Первое объяснение было
«хозяин не заплатил, поэтому отпечатки плохие». Проверено — дело не в оплате.

В базе Undetectable (`Profiles/storage`, таблица `profiles`) у всех трёх
созданных профилей записано ровно это:

| Настройка | Что делает | Значение |
|---|---|---|
| `isCanvas` | подмена canvas | **False** |
| `isAudioNoize` | шум звука | **False** |
| `isWebGLImage` | подмена отрисовки WebGL | **False** |
| `rectsEnabled` | шум прямоугольников | **False** |
| `isWebGL` | **строка** рендерера | True |
| `isFonts` | список шрифтов | True |
| `isWebGPU` | сведения об адаптере | True |

Профили опознаются однозначно: в поле `webGL` лежат ровно те строки, что мы
замерили, — «ANGLE (Apple, ANGLE Metal Renderer: Apple M4 Pro…)» и другие.

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

Про Dolphin и AdsPower так же уверенно сказать нельзя: у них настройки на диске
не лежат, конфиг приезжает с их сервера при запуске (это же видно в бинаре).
Замеренный результат у них тот же самый, но причина не подтверждена.

Наш вывод: **безопасный режим обязан быть режимом по умолчанию.** Галочка, о
которой человек не знает, защитой не является.

### Второй замер: а что если шум ВКЛЮЧИТЬ

Первый замер снят с настройками по умолчанию. Естественное возражение — «просто
не включили». Проверено: в Dolphin у двух профилей вручную выставлено
`Canvas: Noise` и `WebGL: Noise`, каждый запущен, замер снят тем же стендом.

| | настоящая машина | профиль 1 | профиль 2 |
|---|---|---|---|
| canvas.text | `7177c1d2…` | `9f5e4674…` | `2d305eea…` |
| canvas.geom | `2190b8cf…` | `bed56ce0…` | `1fa564d0…` |
| отрисовка WebGL | `f497b91e…` | `7233f2ea…` | `7730bacf…` |
| **отрисовка звука** | `124.04347658` | `124.04347658` | `124.04347658` |

**Что у них работает хорошо, и это стоит перенять.**

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

Шум **различает профили**: два профиля на одной машине дали разные canvas и
разную отрисовку WebGL. То есть посев берётся от профиля, а не от машины.

Наш детектор подделки **не нашёл ничего**: `struct.fn` и `frame.fn` — строки из
нулей, `gpu.def.native` — истина. Их патч на C++ из JavaScript невидим. Это
прямое подтверждение того, почему инъекция в прототипы не годится: она бы
здесь засветилась, а патч ядра не светится.

**Что не работает, и это дыра.**

Отрисовка звука у обоих профилей осталась **побайтно той же, что у настоящей
машины**. Причина найдена в их же интерфейсе: переключатель `Audio` существует,
но лежит в свёрнутом разделе «Additional hardware parameters» и по умолчанию
стоит `Real`, тогда как `Canvas` и `WebGL` — в первом, развёрнутом разделе.
Человек, включивший шум там, где его видно, остаётся связан по звуку.

Одного `GROUP BY` по `audioRender` достаточно, чтобы собрать все профили этой
машины в одну кучу — при включённом шуме на canvas и WebGL.

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

Побочно подтвердилось из бинаря: `Ports: Protect` с зашитым списком
`3389,5900,5800,7070,6568,5938,63333,5901,5902,5903,…` стоит по умолчанию —
это ровно тот чёрный список RDP/VNC/TeamViewer, что мы нашли строками.

### Побочная находка: ClientRects выдаёт плотность экрана

Сначала показалось тревожным: `ClientRects` у всех восьми чужих профилей давал
`202.585938`, а наша сборка Chrome for Testing — `202.593750`. То есть наш
профиль отличался бы от всех.

Разрешилось замером, а не рассуждением. Дело не в сборке и не в их патче:
**та же наша сборка, запущенная с `--force-device-scale-factor=2`, даёт ровно
`202.585938`.** Сборки 150 и 151 между собой не расходятся вовсе. Тревога
ложная.

Но отсюда следует правило связности, которого у нас не было:

> ширина текстового прямоугольника зависит от НАСТОЯЩЕЙ плотности экрана и
> считается до того, как страница увидит `devicePixelRatio`.

Значит профиль, который заявляет `dpr 1`, но рисуется на ретиновом мониторе,
выдаёт себя дробной частью `getBoundingClientRect` — и наоборот. Подделать
`devicePixelRatio` строкой мало: надо, чтобы совпала раскладка. У шести
замеренных профилей `dpr` заявлен 2 и раскладка ретиновая, то есть здесь они
согласованы; но проверка бесплатная и работает против любого, кто менял
плотность.

## Что они пишут о себе сами (их документация)

Прочитано в браузере, на их же сайте. Два факта, которые меняют выводы.

**Про звук — умолчание названо ими самими.** Раздел «Аудио» в документации
Dolphin: «Параметр отвечает за AudioContext, который формируется на основе
подключенных и используемых аудио устройств… **1) Реальный** — берётся с
устройства. **2) Шум** — автоматически подменяет параметры».

То есть шум у них есть, а по умолчанию стоит «Реальный». Это объясняет наш
замер восьми профилей: звук совпал с настоящей машиной не потому, что они не
умеют, а потому, что переключатель лежит в свёрнутом разделе и его никто не
трогал. Устройство интерфейса, а не устройство алгоритма.

**Про флаги — они продают то же, что мы нашли замером.** В их списке из 25
параметров профиля есть **«Аргументы запуска»**, и в самом окне у него значок
«Starter and higher», то есть это платная возможность.

Мы пришли к тому же рычагу с другой стороны: замером выяснили, какие флаги
меняют НАСТОЯЩУЮ отрисовку (`docs/fingerprint-diversity.md`), и раздаём их
профилям автоматически по площадке. Разница в подаче: у них человек должен сам
знать, что вписать, и заплатить за возможность вписать; у нас класс считается
сам и входит в базовое поведение.

**Умолчания у всех РАЗНЫЕ, и это меняет вывод.** Сначала казалось, что рынок
целиком отгружает небезопасное умолчание. Прочитано в их документации —
неправда, разброс полный:

| Продукт | canvas | звук | WebGL-картинка | ClientRects |
|---|---|---|---|---|
| AdsPower | **шум** | **шум** | **шум** | **шум** |
| Dolphin | Real | Real | Real | Real |
| Undetectable | выкл | выкл | выкл | выкл |
| Octo (их же пример) | выкл | **шум** | **шум** | выкл |

Источники дословные. AdsPower, Local API: «canvas — 1: Add noise (Default
setting)», «audio — 1: Add noise (Default setting)», «client_rects — 1: Add
corresponding noise, generate different ClientRects for each browser on the
same computer». Dolphin, документация: «Аудио… 1) Реальный — берётся с
устройства». Undetectable — из базы созданных профилей: `isCanvas=False`,
`isAudioNoize=False`.

**И это сходится с нашим замером восьми профилей.** Отличались ровно два, и
один из них — AdsPower на Chrome 149: у него сменился и холст, и звук
(`124.03231224` против `124.04347658` у машины). Профиль честно отвязался.
У Dolphin и Undetectable всё совпало с машиной — ровно как обещают их
умолчания.

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

**Закрытые списки значений — забрать как проверку.** У обоих, кто их публикует,
`hardwareConcurrency` и `deviceMemory` принимают не любое число:

* Octo: cpu ∈ {2,4,6,8,10,11,12,14,16,20,24}, ram ∈ {2,4,8,12,16,24,32,64};
* AdsPower: cpu ∈ {2,4,6,8,16}, ram ∈ {2,4,6,8}.

Профиль с `hardwareConcurrency = 7` невозможен — такого не бывает ни у одной
живой машины. Это готовое правило связности, и оно стоит нам одной строки.

**Полный список их параметров** — для сверки с нашим покрытием: ОС, User-agent,
IP, WebRTC, WebGL, WebGL инфо, Canvas, WebGPU, Client rects, часовой пояс,
язык, геолокация, процессор и память, MAC-адрес, имя устройства, шрифты,
**аудио**, экран, медиа, порты, Do not track, **аргументы запуска**, скрыть
название в браузере, подмена видеопотока и cookies, подмена имени и значка.

## Два способа, и их надо различать

Самую внятную формулировку даёт 0detect, но так устроены все:

* **всё перечислимое** — `navigator`, экран, параметры WebGL, WebGPU,
  `audioContext`, медиаустройства, голоса, батарея, `connection`, шрифты —
  **подставляется** значениями из корпуса настоящих машин;
* **всё, что рождается из пикселей и раскладки** — canvas, отрисовка WebGL,
  `ClientRects`, отрисовка звука — **зашумляется**, с посевом на профиль
  (`*_noise_value`) и версией алгоритма (`noise_version`).

Логика правильная: шуметь имеет смысл там, где значение и так непрерывно и
уникально. Перечислимое поле от шума станет **невозможным** — не бывает
`MAX_TEXTURE_SIZE = 16383`.

Vision это тоже знает: у него `noiseScale` ограничен диапазоном 0..3, и есть
режимы `off / real / noise` на каждую поверхность отдельно.

## Где у них корпус

У **всех** настоящая библиотека отпечатков живёт на сервере и приезжает
вместе с профилем. Локально:

* Vision — только кэш холстов (см. выше);
* AdsPower — 34 пары `{UNMASKED_VENDOR, UNMASKED_RENDERER}` в `main.min.js`
  и больше ничего: ни расширений, ни лимитов, ни precision;
* 0detect — ничего, корпус приходит ручкой `GET /dictionary`;
* Undetectable — ничего, отпечаток берётся из облачной библиотеки готовых
  «конфигов», то есть снимков настоящих машин;
* Dolphin — каталог шрифтов приходит запросом `/fingerprints/font-list`
  с параметром платформы.

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

## Что берём

### 1. Офлайновый подбор пояса и координат по IP

Undetectable кладёт в бандл три базы MaxMind на 83 МБ: `GeoLite2-City`,
`GeoLite2-ASN`, `GeoLite2-Country`. Проверено чтением их же базы: 176.9.1.1 →
`Europe/Berlin`, Falkenstein, ASN 24940 Hetzner; IPv6 `2a02:6b8::2:242` →
`Europe/Moscow`, ASN YANDEX.

Мы сейчас делаем второй шаг обращением к внешнему сервису. Перенос его к себе
даёт три вещи: исчезает третья сторона, которая видит полный список наших
выходов и время их работы; исчезает лимит запросов и точка отказа; IPv6
покрывается наравне с IPv4.

**Обязательная оговорка, которой у них нет.** MaxMind отдаёт `time_zone` не
всегда: у 1.1.1.1 записи нет вовсе, у 8.8.8.8 она есть, но это центроид страны
с `accuracy_radius` 1000 км. Для датацентрового адреса ответ может разойтись с
правдой на час-два. Значит автоподбор обязан быть перебиваемым руками — у
Undetectable ровно так: `tzEnabled`, `tzAuto`, `tzVal`.

Лицензию GeoLite2 надо разобрать **до** того, как класть базу в установщик.
Их поведение здесь не образец: в бандле Undetectable лежат три базы и **нет
ни файла лицензии, ни атрибуции** — только строка «MaxMind.com» в бинаре и
подпись «Set by Maxmind» под полем координат. Скопировать эту практику
нельзя, решать придётся самим.

### 2. Таблица «какой шрифт появился в какой версии ОС»

У Undetectable она зашита в бинарь:

* Windows 7 → Cambria Math, Lucida Console;
* Windows 8 → Aldhabi, Gadugi, Myanmar Text, Nirmala UI;
* Windows 8.1 → Leelawadee UI, Javanese Text, Segoe UI Emoji;
* Windows 10 → HoloLens MDL2 Assets, Bahnschrift, Ink Free;
* Windows 11 → Segoe Fluent Icons;
* macOS 10.9 → Helvetica Neue, Geneva; 10.10 → Luminari;
  10.11 → PingFang HK Light; 10.12 → American Typewriter Semibold, Futura Bold;
  10.15 → Galvji, MuktaMahee Regular.

Для нас это не список для подделки, а **генератор правил связности**: профиль,
заявляющий Windows 11, обязан показывать Segoe Fluent Icons, и обязан не
показывать маковских. И это же — способ выбрать различающие шрифты для зонда
вместо длинного списка наугад.

### 3. Сборка обязана отказываться работать без конфига

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

* Dolphin: `FATAL components/zubre/zubre.h:56 @dolphin: There's an error while
  reading browser config. Aborting`;
* Vision: `components/vision/browser/vision.cc:40 unable to load parameters`;
* Undetectable: `undetectable/profile_service.cpp:62 … Unable to read info file`;
* AdsPower и Incogniton — свои варианты того же.

Это не защита от нас, это защита клиента. «Голый» запуск сборки без конфига
профиля отдал бы площадке настоящий отпечаток машины — то есть ровно то, от
чего человек платил, чтобы уйти. У нас сегодня такой запуск возможен: бинарь из
пула стартует и без профиля. Когда появится свой форк, он обязан вести себя как
у них — падать, а не работать вслепую.

### 3. Правило: выдавать связанный кортеж целиком

У всех подмена идёт одним объектом: язык, `Accept-Language`, пояс, координаты,
UA, UA-CH, весь кортеж экрана, WebGL и WebGPU вместе, WebRTC. Независимого
случайного выбора по каждому полю нет ни у кого.

У нас это уже выражено в `identity/coherence.py`, но проверок там меньше, чем
осей у них.

### 4. Поверхности, которых нет в нашем зонде

Каждая из них — та, на которую кто-то потратил патч C++. Если конкурент правит
бинарь ради поля, поле различает машины.

| Поверхность | У кого | Почему важно |
|---|---|---|
| WebGPU: `adapter.info`, `features`, ~30 лимитов | Dolphin, Vision, Octo, 0detect, Undetectable | различает независимо от WebGL — лучшая перекрёстная проверка |
| UA-CH высокой энтропии | все | Windows 10 и 11 в UA обе «10.0», различить можно только здесь |
| Голоса синтеза речи | Dolphin (`voices.cc`), AdsPower | зависят от ОС, её версии и языковых пакетов |
| `navigator.connection` | Dolphin, Octo | |
| Батарея | Vision | различает ноутбук, настольную машину и виртуалку |
| Цветовой охват экрана (`color-gamut: p3`) | Octo | p3 у маков и дорогих мониторов, у офисной техники нет |
| WebGL из `OffscreenCanvas` | 0detect (отдельная проверка) | если у них отдельно — значит на этом ловят |
| `screen.isExtended`, ориентация | Octo | второй монитор |
| Квота хранилища | Dolphin, Vision | **слабее, чем кажется** — см. ниже |

**Оговорка про квоту хранилища.** Правдоподобное рассуждение «квота считается
от свободного места, значит различает машины» замером **опровергнуто**.
`navigator.storage.estimate()` замерен на трёх томах одной машины: том 2 ГиБ
дал ровно 2 ГиБ, том 60 ГиБ со свободными 5,6 — ровно 10 ГиБ, главный диск —
своё округлённое число. Величина квантуется и ограничивается, а не следует за
диском. Признак остаётся, но он грубый, класса «какого размера том», а не
подпись машины. Записано здесь потому, что рассуждение выглядело убедительно
и попало бы в план работ без проверки.

Отдельно: **наш блок `audio` в зонде мёртв.** Замерено в браузере:
`OfflineAudioContext(1, 44100, 44100)` возвращает `[44100, 1, 1]` — ровно то,
что мы сами передали аргументами. Настоящая частота этой машины 48000.
Настоящее берётся из `new AudioContext()`: `sampleRate`, `baseLatency`,
`outputLatency`. Та же ошибка была продублирована в
`identity/fingerprint_check.py`.

### 5. Сетевой отпечаток, которого не видит JavaScript

TLS (JA3/JA4), ALPN, порядок и значения `SETTINGS` в HTTP/2, HTTP/3. Ни один
патч Blink это не закрывает — это сетевой стек. Площадка уровня Cloudflare
смотрит именно туда. У нас покрытия нет, и зондом на JS его не получить:
нужен отдельный приёмник на стороне сервера.

Косвенно это подтверждает AdsPower: они держат **Firefox-движок**
(FlowerBrowser 132) отдельной осью — именно потому, что TLS, HTTP/2 и набор
API у другого движка другие целиком, и подменой внутри Chromium этого не
получить.

## Чего не берём

* **Чужие значения отпечатков в свои профили.** Сто профилей с одинаковым
  canvas ищутся одним `GROUP BY` — это записано в
  [fingerprint-corpus.md](fingerprint-corpus.md) и подтверждается тем, что у
  них самих один холст никогда не выдаётся многим профилям.
* **Инъекцию JS в прототипы.** Ни у кого из шестерых её нет как основного
  способа, и по причине: обёртка выдаёт себя через
  `Function.prototype.toString`, а переопределённое свойство уезжает в конец
  перечня ключей.
* **Шум по canvas, аудио, WebGL и ClientRects** — пока нет форка, шуметь
  нечем; из JS это как раз та инъекция, которой мы не занимаемся.
* **Подделку геолокации и меток медиаустройств** без реального устройства.
* **Защиту от сканирования локальных портов.** Dolphin зашивает чёрный список
  (RDP 3389, VNC 5900/5800, TeamViewer, AnyDesk). Проверено замером в нашей же
  сборке CfT 151: со страницы на `https://` слушатель на `127.0.0.1` не
  получает **ни одного** запроса — Chrome режет их до сети. Контроль: с
  http-страницы на том же адресе запросы доходят, открытый порт отвечает за
  22 мс против 1 мс у закрытого. Проверен только вектор через `<img>`.
  Вывод: срочной дырой это не является.
* **Диапазоны их полей как доказательство настоящих значений Chrome.** У
  AdsPower в локальном наборе 34 пары «вендор + рендерер», и это набор 2019
  года: Intel HD 520, GTX 750, Quadro K600. Копировать такое — выдать себя
  возрастом, а не спрятаться.

## Что ещё замечено

Каждый из них ходит к себе домой. У Vision — `install_browser` через три
клиента загрузки подряд, включая домен-фронтинг и S3 в обход системного
прокси. У Dolphin — `call_home/call_home_service.cc` и `loki_reporter`, а
также обращение к `geo.anty-proxy-checker.com/ip-info` **из самого браузера**.
У Octo — `telemetry/telemetry_impl.cc`.

Это к вопросу о том, кто видит список ваших профилей и прокси.

Ещё у Vision есть то, чего нет у остальных: очеловеченный ввод прямо в ядре —
`virtual_mouse_handler.cc` с параметрами `samplingRateHz` 1..240, `trajectory`,
`speed` 0..4, `targetWidth` 8..96, и `human_type_dispatcher.cc`. У Octo —
`octo_paste_like_human_executor.cc`. Мы делаем то же самое из Python через
`Input.dispatch*`, и это не хуже: события всё равно приходят с
`isTrusted: true`. Полезны их **диапазоны**, а не способ.

## Проверяльщики: чем нас будут ловить

Разобрано 15 августа по наводке знакомого владельца. Важно не спутать: это не
антидетекты, а инструменты ПРОТИВОПОЛОЖНОЙ стороны, и читать их вердикты надо
с поправкой на то, кто их пишет.

### proxydetect.live (он же incolumitas.com) — берём почти всё

Автор — Николай Чахер, настоящий исследователь с именем и публикациями, а не
аноним: работы «TCP/IP Fingerprinting for VPN and Proxy Detection» и
«Detecting Proxies and VPNs with Latency Measurements», открытый движок
`zardaxt` на GitHub. Продаёт он ОБНАРУЖЕНИЕ, не антидетект, — то есть его
интерес в том, чтобы у вас горело красным, и подача сознательно обвинительная.
Техника при этом настоящая.

**Ссылка, которую прислали (`proxy.incolumitas.com`), в браузере не
открывается**: домен отдаёт сертификат чужого сайта. Рабочий адрес —
`proxydetect.live`.

Наш профиль второго уровня через немецкий дата-центровый прокси, замер живой:
`Proxy Detected, 70%`, красных две из девяти — `TCP/IP Fingerprint` и
`Passive AI-based Proxy Detection`. Разбор этого замера и что он значит для
продукта — в [PRODUCT.md](PRODUCT.md), раздел «Сеть — вторая ось».

**Что взять к себе** (ни одного из этих замеров у нас сегодня нет):

* **задержки.** Свой маленький приёмник, который меряет время TCP-рукопожатия
  на сервере и сравнивает с временем ответа из браузера профиля. У него порог
  замерен: без прокси расхождение до ~14%, с прокси 45–136%. Это единственная
  проверка, которая ловит наш прокси НАПРЯМУЮ, и мерить её надо на своём
  сервере, а не у него;
* **MSS/MTU выходного узла.** 1500 — прямое подключение, 1400/1380/1420 —
  подпись туннеля. Один SYN, стоит копейки, показывать в карточке прокси;
* ~~**WebRTC**~~ — **проверено 15 августа, дыры нет.** Профиль второго уровня
  на `browserleaks.com/webrtc`: `No Leak`, локальный адрес пуст, публичный
  совпадает с выходным адресом прокси. Отдельно важно, что WebRTC при этом НЕ
  выключен (`RTCPeerConnection` и `RTCDataChannel` живы): выключенный WebRTC
  сам по себе признак, потому что у обычных людей он работает. Предполётную
  проверку всё равно стоит завести — она дешёвая, а поручиться за все прокси
  и все сборки этот один замер не может;
* **DNS.** Кто резолвит имена и из какой страны;
* **гигиена заголовков** (`Via`, `X-Forwarded-For`, `X-Real-Ip`, `Forwarded`) —
  ловит кривые HTTP-прокси мгновенно.

**Ещё четыре его проверки, которых у нас нет и о которых стоит знать.**

* **задержка против пинга.** Сервер сам пингует подключившийся адрес и
  сравнивает свой RTT с задержкой входящего соединения. Пинг заметно меньше —
  значит между вами и сервером есть промежуточный узел. Работает только если
  адрес отвечает на ICMP;
* **рисунок потока** — размеры пакетов и промежутки между ними в обе стороны.
  У прокси и VPN он свой из-за инкапсуляции и буферизации;
* **поведение сети из браузера**: запросы на `0.0.0.0`, на `127.0.0.1` и на
  зашитый внешний адрес с замером времени отказа. По тому, КАК быстро и с
  какой ошибкой падает запрос, видно, стоит ли перед браузером прокси и какого
  рода. Это та же семья приёмов, что и сканирование портов, — и напоминание,
  что страница видит про локальную сеть больше, чем кажется;
* **загрузка скрипта с нестандартного порта** — многие прокси и корпоративные
  фильтры такие порты режут, и это само по себе признак.

Отдельно: сканирование портов из JS и серверный `nmap` подключившегося адреса
у него ЕСТЬ, но на витрине выключены — автор сам называет их «очень
инвазивными». То есть приём известен и применяется, просто не всеми и не
всегда.

**И то, что бьёт прямо в наши методы.** Он снимает СТРОКУ функции
`getHighEntropyValues` и её прототип — прямая проверка, не подменена ли
функция скриптом. А ложь об ОС ловит перекрёстно: голоса `speechSynthesis`
(голос Lekha означает мак, голоса Microsoft — Windows), строка видеокарты
через `WEBGL_debug_renderer_info`, `navigator.platform` и `maxTouchPoints`
против User-Agent. Наш вывод «JS-инъекцией отпечаток не чинится» подтверждается
чужим кодом, написанным с другой стороны.

**Главная поправка, и она против совета знакомого.** «Хоть что-то красное —
качественного ворка не будет» неверно в обе стороны. Красное часто не значит
ничего: «IP принадлежит хостеру» загорится у любого дата-центрового прокси, а
половине площадок это безразлично; сам автор в README `zardaxt` пишет, что
большинство протоколов VPN расхождением TCP/IP-отпечатка НЕ выявляется. И
наоборот: всё зелёное не значит безопасно — он не смотрит ни поведение, ни
историю аккаунта, ни редкость отпечатка среди населения, а банят обычно за это.

### f.vision / fv.pro — витрина ПРОИЗВОДИТЕЛЯ АНТИДЕТЕКТА, и туда нельзя

Сам `f.vision` мёртв: домена нет в реестре. Живой адрес — `fv.pro`, и за ним
**Tenebris Team, авторы антидетект-браузера Linken Sphere**. Доказано архивом:
на старой странице стояло «Made by Tenebris», Fake Vision был вшит стартовым
чекером в сам Linken Sphere, а `f.vision` в 2024 редиректил на `fv.pro`.
Прямого признания владельца сегодня нет — вывод восстановлен по следам, но
плотно.

То есть «независимый проверяльщик отпечатков» здесь — витрина продавца
конкурирующего антидетекта. Его вердикты о ЧУЖИХ сборках надо читать
соответственно.

**ТУДА НЕЛЬЗЯ ХОДИТЬ БОЕВЫМ ПРОФИЛЕМ, и это не паранойя.** За один визит он
получает, по собственному же описанию: отпечаток профиля, ваш прокси, список
установленных расширений (вплоть до криптокошельков) и список сервисов, куда
профиль залогинен. И он это **не забывает**: «счётчик конфигураций и
соединений» и «схожесть отпечатков» — это база отпечатков и прокси с проверкой
повторов. Прогнав через него двадцать профилей с одной машины, вы своими
руками соберёте у чужого продавца ровно тот `GROUP BY`, от которого уходите на
площадках.

Техника у него при этом сильная, и три вещи стоит забрать.

* **Сверка одних и тех же полей в разных контекстах** — брать первой, у нас
  этого нет вообще. Подсказки UA-CH, строка видеокарты, число ядер, память,
  вывод `Intl` снимаются в окне, в Worker и в ServiceWorker и сравниваются.
  Патч ядра такое переживает, инъекция в главный мир — нет. Проверка ничего не
  стоит и ловит целый класс подделок;
* **прямоугольники SVG** как вторая ось к `getBoundingClientRect`: у них для
  этого отдельный iframe. Наш зонд меряет только первое;
* **`Math` как отпечаток** — результаты тригонометрии различают библиотеки
  разных платформ и сборок. У нас в зонде этого нет.

Отдельно то, что бьёт прямо в нас: **он сканирует локальные порты прямо со
страницы** через `new Image()` — 1080, 3128, 8080, 8888, 9050, 5000, 2000 и
**9222**. Последний это порт удалённой отладки Chrome, то есть прямая попытка
увидеть, что браузером управляют по CDP.

**Проверено на себе 15 августа: у него это не сработает.** Наша страница,
поданная с публичного адреса, не увидела порт профиля НИ `fetch`, ни
`new Image()` — запрет приходит за 2–4 мс, то есть ещё до сети: Chrome не
пускает публичную страницу в локальную. С локального адреса тот же порт виден
сразу, так что дело не в приёме, а в том, откуда подана страница. Осталось
проверить случай страницы по HTTPS — там правила другие (см. handover).

Ещё одна связь, которую стоит знать: сетевую часть fv.pro делает **не сам** —
подключает `pd-lib.js` с `incolumitas.com`, то есть библиотеку того самого
исследователя, чей проверяльщик разобран выше. Два разных на вид сайта меряют
сеть одним и тем же кодом.

### zl0y.team — витрина продавца, но с одной ценной строкой

Анонимная команда (юрлица и юрисдикции нет, связь через Telegram/Matrix),
торгует своими роутером, прокси и «подменой отпечатков L4+L5+L7». На главной
пишет «мы ни с кем не сотрудничаем, поэтому можем говорить правду» — и тут же
продаёт своё. Их «Отчёт антифрода» — сетевой чекер (IP, DNS, прокси), а не
проверяльщик отпечатка браузера. Полный отчёт отдают после входа через их
кабинет, то есть в обмен на ваш отпечаток и адрес: боевым профилем туда ходить
незачем.

Ценность одна, зато настоящая: в открытой теме на `zenno.club` (её завёл сам
продавец) администратор форума продукт ПРОВЕРЯЛ. Вывод — подмена TLS и HTTP/2
работает, **подмена TCP-отпечатка не работает**, разработчики это признают и
обещают починить; для работы требуется поставить их SSL-сертификат, то есть
пустить их в свой трафик.

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