Безопасность и доверие
Безопасность и доверие
- 0600
- Права у каждого файла с чужой тайной, на macOS и Linux
- AES-256-GCM
- Чем зашифрована сессия до того, как попадёт в вашу облачную папку
- 24 часа
- Сколько программа работает без сервера, прежде чем спросит снова
- 2233
- Проверок в стендах: 440 на сервере, 1602 в программе, 191 на сайте
Ваши аккаунты лежат на вашем диске
Куки, хранилища сайтов, IndexedDB, сохранённые входы и собственные базы браузера лежат в каталоге профиля на вашей машине. У ARMANOS Browser это fork-profiles/<номер профиля> внутри папки данных программы: ~/Library/Application Support/@armanos/desktop на macOS, %APPDATA%\@armanos\desktop на Windows, ~/.config/@armanos/desktop на Linux. Само по себе оттуда никуда ничего не уходит.
Рядом с этим каталогом лежат собственные файлы программы: profiles.json, proxies.json, auth.json, session-sync.json и ещё около десятка. Каждый пишется с правами только для хозяина и через соседний временный файл, который потом переименовывается на место. Отключение питания оставляет прежний файл целым, а не половину нового. Файл, который не разбирается, откладывается рядом под именем .corrupt-<время> ещё до того, как поверх него что-то запишут: нечитаемый список профилей это не то же самое, что пустой.
Пароли внутри этих файлов закрыты связкой ключей системы и лежат за приметой enc1:. Так закрыт пароль входа в аккаунт у профиля, пароль прокси, скопированный в профиль, и пароль от папки с сессиями. Если система закрыть не может, программа пишет значение как раньше и не делает вид, что закрыла. Постоянный ключ в исходнике выглядел бы как шифрование и не защищал бы ничего.
| Что это | На вашем диске | На нашем сервере |
|---|---|---|
| Куки, хранилища сайтов, живые сессии | Да | – |
| Логин и пароль профиля | Да | – |
| Ключ одноразовых кодов у профиля | Да | – |
| История, вкладки, загрузки | Да | – |
| Настройки профиля: имя, папка, сценарий, шаблон, прокси | Да | Да |
| Архив сессии для второй машины | В вашей облачной папке, зашифрован | – |
На Windows chmod не делает ничего
Серверу достаются настройки, а не сессии
Пока вы в аккаунте, настройки профилей отражаются на сервере, чтобы вторая машина и напарники видели тот же список. Отправляется немного: имя, папка, сценарий, шаблон отпечатка, поколение отпечатка, прокси, метки и заметка. В запросе едут только те поля, которые вы тронули, поэтому перенос профиля в папку не увезёт заодно устаревший прокси этой машины.
Важнее то, чего в этом списке нет. Куки не уезжают. Не уезжают ни пара логин и пароль профиля, ни его ключ одноразовых кодов, ни один байт баз Chromium. Сам отпечаток не хранится нигде: он выводится из номера профиля и его шаблона в момент запуска, поэтому на второй машине тот же профиль остаётся той же машиной.
Пароль прокси всё же едет: прокси принадлежит рабочему пространству, и вторая машина без него не подключится. Он закрывается до записи в базу ключом, который живёт в настройках сервера, а не в той же базе рядом с данными. Общий список прокси пароль не возвращает вовсе: строка говорит, что пароль задан, а взятие рабочего значения это отдельное действие, и оно остаётся в журнале.
Ваша почта и отпечаток пароля bcrypt
Цена 10. Сам пароль не хранится нигде, а его смена отзывает все ваши ключи обновления.
Ваши машины
Имя, система, когда была на связи. Отвязка помнится, поэтому машина не может тихо зарегистрироваться заново.
Журнал вашего пространства
Регистрация новой машины записывает адрес, с которого она пришла. Вход, которого вы не делали, будет вам виден.
Одна строка синхронизации на профиль и машину
Когда сессия последний раз уезжала или приезжала и получилось ли. Ни имён файлов, ни содержимого.
Кто держит профиль открытым
Человек, имя машины и время, до которого действует замок. Именно это не даёт появиться двум живым сессиям одного аккаунта.
Тариф, состояние и дата продления
Данные карты в нашу базу не попадают: их держит платёжная служба.
Сессия ездит через папку, которая принадлежит вам
Куки остаются на машине, поэтому пересесть за второй компьютер означало бы заново войти в тридцать аккаунтов. Обмен сессиями решает это без того, чтобы мы держали ваши данные. Вы показываете программе папку, которую и так что-то синхронизирует, облачный клиент или сетевой диск, и туда ложится по одному зашифрованному файлу на профиль. Мы этой папки не видим и видеть не хотим.
Архив зашифрован AES-256-GCM, а ключ вытягивается из вашего пароля через scrypt с N=32768, r=8, p=1. Неверный пароль даёт честный отказ, а не мусор. Сам пароль остаётся на вашей машине, закрытый связкой ключей, и в папку не попадает. Об этом сказано и на экране: спрашивать пароль при каждом закрытии профиля значит не сделать выгрузку вовсе.
Смена пароля забывает всё, что уже уехало, и каждый профиль отправляется заново под новым паролем. Иначе в папке копилась бы смесь из двух паролей: вторая машина получала бы честный отказ, а первая молчала бы.
- 1
Профиль закрывается
Chromium дописывает свои базы ещё пару секунд, поэтому программа ждёт четыре секунды и проверяет, что профиль всё ещё закрыт. Открытый профиль не копируется никогда.
- 2
Архив пишется рядом и переименовывается
Половина файла означала бы сессию, которую нельзя расшифровать, поверх сессии, которая работала. Облако подхватывает переименованный файл в тот же миг.
- 3
Рядом ложится маленькая метка с размером и временем
Другая машина сверяет размер на диске с размером в метке. Архив, который ещё качается или выгружен ради экономии места, считается неготовым, и ничего не затирается.
- 4
Вторая машина забирает архив перед запуском
Тот же номер профиля, тот же выведенный отпечаток и приехавшие вместе с ним куки. Аккаунт видит машину, которую видел вчера.
- 5
Менялось и там, и здесь? Не трогается ничего.
Значит, на двух машинах работали параллельно. Программа говорит об этом и даёт выбрать, вместо того чтобы молча затереть чужие входы.
Облако делает копии само
Один профиль не открывается в двух местах
Двое из команды могли открыть один профиль одновременно, и сайт видел один аккаунт живым сразу с двух машин. Это ровно то поведение, за которое аккаунты блокируют, то есть продукт делал бы то, ради предотвращения чего его покупают. Замок держит сервер, потому что машины друг друга не видят.
Запуск берёт замок, и держится он по времени, а не на честном слове. Программа продлевает его каждые 60 секунд, а истекает он через 3 минуты после последнего продления. Машина, выключенная из розетки, отпускает свой профиль сама, без звонка в поддержку. Закрытие последнего окна отпускает замок сразу, чтобы напарник не ждал срок впустую.
Занятый профиль отвечает отказом, в котором названы машина и время, с которого она его держит, и перехватить его можно осознанно. Если замок отобрали у вас, очередное продление не проходит, программа закрывает окна и говорит, что случилось. Та же дверь отказывает, если вашу машину отвязали, если места в команде больше нет или если профиль вышел за оплаченную норму. Все три проверки стоят и на продлении, поэтому окно, открытое до понижения тарифа, не переживёт его.
- Срок замка
- 3 минуты от последнего продления
- Продление
- каждые 60 секунд, пока профиль открыт
- Освобождение
- сразу при закрытии последнего окна
- Запуск отклонён, если
- профиль открыт в другом месте, машина отвязана, места нет или норма превышена
- Без аккаунта
- замка нет вовсе, профили живут только на этой машине
Опознаватель установки случайный, создаётся один раз и лежит в папке данных. Раньше он выводился из имени хоста и имени пользователя, и два свежих ноутбука с именем MacBook-Pro считались одной машиной.
Сервер это не разрешение работать
На входе два шага, а рядом список машин
Пароль хранится отпечатком bcrypt с ценой 10 и никогда открытым текстом. Десять промахов закрывают аккаунт на 15 минут, и тот же счёт распространяется на второй шаг. Шесть цифр это миллион вариантов, поэтому утёкший пароль вместе с терпеливым перебором иначе давал бы полный вход.
Второй шаг это обычное число из приложения на телефоне: RFC 6238, тридцать секунд, шесть цифр, читается любым привычным приложением. Арифметика написана здесь, а не взята библиотекой: чужая библиотека по дороге к вашему паролю однажды обновится сама. При включении вы получаете десять запасных кодов, от которых сервер хранит только отпечатки SHA-256 и сверяет их постоянным временем. Общий секрет лежит в базе закрытым.
Между двумя шагами есть короткий билет, и подписан он своим ключом. Билет, подписанный ключом пропусков, сам стал бы пропуском, и тогда одного пароля хватило бы для входа. Живёт он пять минут и несёт свою пометку.
Сеансы: пропуск живёт 15 минут, ключ обновления 30 дней и меняется при каждом использовании. Предъявленный дважды ключ отзывает всю цепочку, потому что повтор означает, что копия гуляет. Выход из аккаунта отзывает ключ и на сервере, а не только на диске: ключи лежат файлом, а ноутбуки продают и отдают в ремонт.
- Пароль
- bcrypt, цена 10
- Промахи
- 10, дальше 15 минут закрыто
- Второй шаг
- TOTP, 30 секунд, 6 цифр
- Запасные коды
- 10, хранятся только отпечатками SHA-256
- Билет между шагами
- 5 минут, свой ключ подписи
- Пропуск
- 15 минут
- Ключ обновления
- 30 дней, меняется, повтор отзывает цепочку
- Передача входа с сайта в программу
- один код, 2 минуты
- Сверка тарифа
- каждые 5 минут на связи
- Работа без сервера
- 24 часа
- Льгота на продление
- 3 суток после записанной даты
Переведённые назад часы срок без сервера не растягивают: проверка, датированная будущим, считается за отсутствие проверки.
Пароль прокси не попадает в командную строку
Отдельный процесс браузера не умеет взять пароль так, как встроенный, поэтому раньше пароль вписывался в строку запуска: --proxy-server=http://user:pass@host:port. Командную строку чужого процесса читает любая программа, запущенная под тем же человеком, а жилой трафик покупают гигабайтами. Хуже того, это не работало: измерено на отгруженной сборке 153.0.7978.0, такая запись сделала ноль запросов и не открыла ничего, а обычный адрес сделал 13.
Теперь на 127.0.0.1 поднимается небольшой мост. С браузером он говорит без пароля, с вашим прокси с паролем, поэтому в командной строке стоит только http://127.0.0.1:<порт>, а настоящий пароль не покидает памяти программы. Мост принимает подключения лишь с петли и спрашивает разовый пропуск, который отдаёт встроенное расширение профиля: чужая программа на этой же машине оплаченным каналом не воспользуется. Мост принадлежит профилю, а не отдельному запуску: раньше неудачный второй запуск гасил его, и живое окно уходило в сеть напрямую.
Правило прокси обязано покрывать всё, и заметить это удалось только замером. Запись вида http=узел:порт в правилах Chromium означает адреса http:// и только их. Запрос по https уходил напрямую, с настоящим адресом, пока программа показывала профиль как работающий через прокси. Теперь в правиле стоит полный адрес, и тот же замер показывает CONNECT для https-запроса, проходящий через прокси.
WebRTC это второй классический путь мимо прокси. Движок запускается с --force-webrtc-ip-handling-policy=disable_non_proxied_udp, поэтому браузер не открывает собственную дорогу по UDP мимо прокси ради сбора адресов. Проверить это на своей машине можно тестом утечки на этом сайте.

Выход через свой сервер по SSH мостом не отдаётся никогда
Дверь автоматизации закрыта, пока вы её не откроете
Программой можно управлять из сценариев через местный ход HTTP. По умолчанию он выключен. При включении он слушает только 127.0.0.1 и никогда наружную сеть, а каждый вызов, кроме состояния, требует ключ с того же экрана: в параметре token, в заголовке x-api-token или предъявителем.
Одной петли мало, и это то место, которое легко упустить. Страница, которую вы открыли, дотягивается до 127.0.0.1 из вашего же браузера, а под подменённым именем DNS делает это как своя и читает ответы. Для антидетект-браузера это худшая из утечек: ответ о состоянии называет продукт и версию и связал бы все профили машины с одной установкой. Поэтому запрос отклоняется, если в нём есть заголовки, бывающие только у браузера, Origin или Sec-Fetch-Site не none, либо Host не равен этому адресу петли.
Отладочный канал, нужный автоматизации, открывается так же осторожно. Порт нулевой, то есть свободный выбирает система, адрес петлевой, а признак, объявляющий сайту об автоматизации, выключается. Замерено на этом движке: без порта navigator.webdriver ложь, с открытым портом становится истиной, а с этим доводом снова ложь.

Чего мы не собираем
В программе нет сбора статистики, и на сайте его нет тоже. Ни диспетчера тегов, ни записи сеансов, ни отправки отчётов о падениях, ни одного чужого сценария. Поищите в исходниках привычные имена, и найдёте их в правилах блокировки слежки, то есть среди того, что мы закрываем.
Отчёт о проблеме собирается на месте: версия, система, выбранный движок, собственные уведомления программы и число профилей. Всё, похожее на тайну, вырезается по имени поля, а не по виду значения: пароль бывает похож на обычное слово. Password, token, cookie, seed, passphrase и остальные превращаются в [скрыто], а имя поля остаётся, чтобы вы видели, что оно было. Никуда отчёт не уходит, пока вы не нажмёте отправку.
Бесплатные инструменты этого сайта считают внутри вашего браузера и не делают ни одного запроса. У теста утечки список серверов ICE пуст: чужой сервер STUN сам узнал бы о посетителе. Два стенда роняют сборку, если в этих файлах появится fetch, XMLHttpRequest, sendBeacon, WebSocket или EventSource, и там же запрещены localStorage и куки.
Проверка страны выхода
При запуске профиля и при проверке прокси программа спрашивает публичную службу по HTTPS и через прокси этого же профиля. Ответ задаёт часовой пояс и язык. Служб четыре, поэтому один отказ не решает, какой будет личность профиля.
Лента движка
Проверка обновлений и загрузка ядра идут по HTTPS, перенаправления отвергаются, и ядро ставится, только если SHA-256 скачанного файла совпал с записью в ленте.
Наш сервер
Настройки профилей, тариф, команда и поддержка, и только пока вы в аккаунте.
Поддержка, когда вы нажали отправку
Вместе с вашим сообщением уезжает вычищенный отчёт. В тишине не отправляется ничего.
Выгрузка сценария оставляет значения дома
В выгруженном сценарии остаются имена переменных, а содержимое нет: там лежат логины, пароли и коды приглашений.
Сам сайт отдаётся с закрытыми дверями
Кабинет живёт на том же домене, что и эти страницы, поэтому заголовки сайта значат не меньше серверных. Они заданы в одном месте и уходят с каждым ответом.
Шрифты часть этого же. Они скачиваются при сборке и отдаются с нашего домена, поэтому открытие страницы не сообщает шрифтовой службе, что вы здесь были. Ни одной чужой таблицы стилей и ни одного чужого сценария нет, поэтому в правиле источников наш сервер можно назвать единственным местом назначения, а остальное запретить.
Правило не самое строгое из возможных, и об этом надо сказать прямо. Встроенные сценарии всё ещё разрешены, потому что их подставляет в каждую страницу сам фреймворк. Чужие сценарии, чужие рамки и чужие адреса закрыты, наш собственный встроенный код нет. Это настоящий зазор между тем, что отгружено, и тем, как правильно, и лучше написать о нём здесь.
| Заголовок | Значение | Что закрывает |
|---|---|---|
| Strict-Transport-Security | max-age=31536000; includeSubDomains | Первый заход по http, который можно перехватить по дороге |
| Content-Security-Policy | default-src 'self', connect-src наш сервер | Чужой сценарий, чужую рамку и отправку данных на чужой адрес |
| X-Frame-Options и frame-ancestors | DENY и 'none' | Показ вашего кабинета внутри чужой страницы с ловлей нажатий поверх |
| X-Content-Type-Options | nosniff | Догадку браузера о виде файла и выполнение его как сценария |
| Referrer-Policy | strict-origin-when-cross-origin | Утечку полного адреса страницы кабинета на сайты, куда вы перешли |
| Permissions-Policy | camera=(), microphone=(), geolocation=() | Просьбу о приборах, которые сайту незачем |
Удаление это удаление
Удаление профиля сперва останавливает его браузер. Работающий Chromium продолжает ходить по сайтам и писать в каталог, у которого больше нет хозяина, и снять его можно было только диспетчером задач. По той же причине стирание данных прежде не удавалось на Windows, где живой процесс держит свои файлы открытыми.
Дальше уходит всё, что было привязано к профилю: каталог Chromium, его расширения, его расписания, счётчики в stats.json и pace.json и записи о запусках в notifications.json. Последнее было самым тихим. Каждый удачный запуск дописывал туда имя профиля, а файл переживает перезапуски, и антидетект-браузер хранил вечную историю профилей, которые человек считал удалёнными.
Удаление насовсем убирает и зашифрованный архив из вашей папки обмена вместе с отметками о нём. Иначе файл с живыми куками продолжал лежать в облачной папке, любая вторая машина могла принести профиль обратно, а окно при этом обещало, что вернуть ничего нельзя.
Выход из аккаунта отзывает ключ обновления на сервере, а не только на диске: ключи лежат файлом, а ноутбук продают или отдают в ремонт. Отвязка машины в кабинете освобождает место и помнится, поэтому машина не зарегистрируется молча на следующем же ударе сердца.
Чего это не делает
- Это не обещание, что вас нельзя узнать. Здесь сделаны изоляция и устройство, которое не меняется от запуска к запуску. Сайт всё равно судит по поведению, по истории самого аккаунта и по репутации адреса, с которого вы выходите.
- Сборки пока не подписаны и не заверены. macOS и Windows предупредят при первом запуске, и разрешить программу придётся руками.
- Права 0600 настоящие только на macOS и Linux. На Windows chmod трогает лишь признак «только для чтения», и защитой там служит сам профиль пользователя.
- Пароль от папки с сессиями лежит на вашей машине, закрытый связкой ключей там, где система это позволяет. Спрашивать его при каждом закрытии профиля значит не сделать выгрузку вовсе, и об этом сказано на экране.
- Правило источников сайта всё ещё разрешает наши собственные встроенные сценарии, потому что их подставляет фреймворк. Это не самый строгий вид заголовка.
- Без аккаунта замка между машинами нет, а недоступный сервер запуску не мешает. Замок защищает от второго окна, а не от плохой сети.
Как проверить
У каждого утверждения выше есть файл, который его исполняет, и стенд, который падает, когда оно перестаёт быть правдой.
- Файлы с чужими тайнами пишутся только для хозяина и целиком
- apps/desktop/src/lib/secureFile.js · apps/desktop/test/secrets-on-disk.js
- Куки и входы профиля на сервер не уезжают
- apps/desktop/src/lib/profiles.js · apps/desktop/test/login-stays-local.js
- Пароль прокси не попадает в командную строку, а прокси с логином работает
- apps/desktop/src/lib/proxyAuthBridge.js · apps/desktop/test/proxy-auth-bridge.js · apps/desktop/test/мост-принадлежит-профилю.js
- Сессия ездит зашифрованной, а удаление убирает её и из папки
- apps/desktop/src/lib/sessionSync.js · apps/desktop/test/session-sync-folder.js · apps/desktop/test/purge-clears-cloud.js
- Один профиль, одно открытое окно
- apps/server/src/profiles/profiles.service.ts · apps/server/test/profile-lock.js · apps/desktop/test/profile-busy.js
- Бесплатные инструменты сайта не делают ни одного сетевого вызова
- apps/web/components/tools/FingerprintCheck.tsx · apps/web/test/инструмент-отпечатка.js · apps/web/test/тест-утечки.js
Читать дальше
Доказательства
Каждое обещание сайта рядом с файлом и стендом, которые его держат.
Сессия на двух компьютерах
Как зашифрованный архив ездит через папку, которая уже есть у вас.
Прокси
Мост, страна выхода и что делает профиль при смене адреса.
ARMANOS Browser
Движок, который несёт отпечаток, и что в нём поправлено.
Одноразовые коды
Коды для аккаунтов профиля, лежащие рядом с профилем на этой машине.
Проверьте на своей машине
Поставьте программу, откройте профиль и посмотрите на права у файлов и на адрес в командной строке. Два профиля бесплатны, карта не спрашивается.