Shelter на Android: что видно изнутри отдельного профиля?


Исследуем границы Android Work Profile на практике: изоляция приложений, наблюдаемость сетевых ресурсов и обнаружение VPN
Сначала все работало
Я достаточно серьезно отношусь к темам приватности в интернете, безопасности и цифровой гигиене, особенно когда вопрос касается личного смартфона. Поэтому летом решил несколько изменить привычную схему работы с Android‑приложениями: отделить российские сервисы от основного пользовательского окружения и по возможности скрыть от них факт использования VPN.
Для этого воспользовался Shelter — приложением, позволяющим создать отдельный рабочий профиль средствами самого Android. Часть приложений перенес туда, оставив VPN работать в основном профиле. При этом настроил split tunneling так, чтобы выбранные приложения, включая находящиеся в рабочем профиле, выходили в интернет напрямую, минуя VPN‑туннель.
И, надо сказать, результат меня вполне устроил. Раньше некоторые приложения, даже будучи исключенными из VPN‑маршрутизации, постоянно предупреждали, что на устройстве установлен VPN. После переноса в Shelter эти предупреждения исчезли. Все лето система работала стабильно, и я уже считал, что нашел вполне удачный вариант разделения приложений.
Но в конце сентября — начале октября начали происходить странные вещи.
VK Messenger стал заметно тормозить, а временами практически отказывался работать. Через основное приложение VK еще удавалось пользоваться сервисом, но дважды дело дошло до блокировки аккаунта. Причем после первой блокировки я уже действовал осторожнее, в частности перестал заходить во ВКонтакте с ноутбука через VPN. Тем не менее ситуация повторилась.
Примерно в тот же период заметил еще одну особенность: приложение одного из банков при включенном VPN иногда практически переставало работать. Стоило полностью отключить VPN — и оно снова оживало. Хотя само приложение находилось в рабочем профиле, для которого был предусмотрен прямой выход в интернет.
И тут меня начали терзать смутные сомнения. А настолько ли самостоятельна эта изолированная среда, которую создает Shelter? Если приложение не использует VPN для собственных соединений и вдобавок находится в отдельном профиле Android, означает ли это, что оно вообще не может узнать о работающем VPN?
Разумеется, связывать блокировки аккаунта или сбои приложений именно с обнаружением VPN было бы преждевременно. Но технический вопрос от этого не становился менее интересным.
Вот я и решил проверить это на практике. В качестве основного инструмента выбрал RKNHardering — диагностическое приложение с открытым исходным кодом, доступное через F‑Droid. Провел серию из шести экспериментов и уже в первом получил любопытный результат:
Находясь внутри Shelter, диагностическое приложение не обнаружило VPN‑клиент, установленный в основном профиле, но увидело созданный им интерфейс
tun0. При этом проверенные соединения самого приложения выходили напрямую.
К тому времени тема VPN‑детекции в российских Android‑приложениях уже активно обсуждалась. Еще в апреле 2026 года РБК сообщил о методических рекомендациях Минцифры интернет‑компаниям по выявлению VPN на устройствах пользователей. RKS Global опубликовали анализ 30 российских Android‑приложений, а в статье на Хабре «Android. Три буквы. Российские приложения» рассматривались способы изоляции, включая Shelter, и уже демонстрировалась видимость tun0 из рабочего профиля.
Мне хотелось не просто повторить это наблюдение, а разобраться, что именно меняется при разделении профилей: на какую защиту можно рассчитывать, а где возникают неоправданные ожидания.
Эта публикация открывает небольшую серию авторских материалов о сетевой наблюдаемости и изоляции Android.
1. Shelter: отдельный профиль, но не отдельная система
Разделить приложения, не покупая второй телефон
Строго говоря, Android изначально предусматривает изоляцию приложений. Их песочница (Application Sandbox) опирается на UID, ограничения доступа к данным других приложений и систему разрешений.
Но есть нюанс: изоляция отдельных приложений и разделение их на разные пользовательские окружения — не совсем одно и то же.
Например, банковское приложение и VPN‑клиент в одном профиле остаются частью одного пользовательского контекста Android. Это не дает им права свободно читать данные друг друга. Но доступные системные API могут раскрывать сведения об установленных пакетах и характеристиках устройства — информацию за пределами собственных данных приложения.
Здесь и появляется другой подход — разделить приложения не только средствами их индивидуальных песочниц, но и по разным пользовательским профилям.
Work Profile предназначен прежде всего для корпоративного использования: сотрудник работает с личными и служебными приложениями на одном смартфоне, сохраняя разделение их данных, аккаунтов и политик управления.
Что на самом деле делает Shelter
Shelter — свободное приложение с открытым исходным кодом, использующее штатный механизм Android Work Profile, или управляемый рабочий профиль (Managed Profile). Оно не создает виртуальную машину и не запускает вторую копию Android, а управляет возможностями самой платформы.
При создании рабочего профиля Android формирует дополнительный пользовательский контекст, связанный с основным. У него собственная область данных приложений, отдельные аккаунты и пространство установленных приложений. Shelter получает роль владельца профиля (Profile Owner) и через системные механизмы Android позволяет устанавливать или клонировать туда приложения, управлять ими, а при необходимости — «замораживать», останавливая их работу в фоне.
Согласно документации AOSP, рабочий профиль реализуется как отдельный пользователь Android. Его приложения получают UID, учитывающие идентификатор пользователя, а данные размещаются в пользовательских каталогах — например, /data/user/<userId>/.
Поэтому две копии одного приложения, установленные в личном и рабочем профилях, имеют одинаковое имя пакета, но работают в разных пользовательских контекстах, со своими данными и настройками. Даже если для обеих используется один и тот же APK.
Android также ограничивает взаимодействие между профилями. Например, передача данных через Intent и доступ к компонентам приложений другого профиля регулируются системными политиками, а не происходят произвольно.
Разумеется, предусмотрены и разрешенные способы межпрофильного взаимодействия. Work Profile создавался не для полной изоляции двух независимых пользователей, а для управляемого разделения личной и рабочей среды.
Разработчик Shelter прямо указывает, что возможности и безопасность программы ограничены реализацией Work Profile. Поэтому особенности конкретной прошивки здесь имеют значение.
Один смартфон — не две независимые системы
Рабочий и личный профили действительно имеют раздельные пользовательские пространства. Однако они продолжают сосуществовать в рамках одной операционной системы, с общим ядром Linux, физическими сетевыми адаптерами и рядом системных служб.
Android может применять разные правила доступа и маршрутизации к приложениям, но разделение их данных не означает автоматического сокрытия всех сведений о сетевом состоянии устройства. Доступность таких сведений зависит от системных API, ограничений для конкретного процесса и реализации ОС.
Так где же проходит эта граница?
Чтобы ответить, я решил посмотреть, что способен обнаружить обычный диагностический APK по разные стороны профилей и как меняются результаты при переключении сетевых сценариев.

2. Как проверяли границу изоляции
Для экспериментов использовался RKNHardering 2.11.0: он проверяет признаки VPN, сведения об установленных приложениях, сетевых интерфейсах, маршрутах и внешних IP‑адресах. Это позволяет сравнивать не только присутствие клиента или туннеля, но и результаты конкретных сетевых запросов.
Испытательный стенд — мой Vivo Y53s с Android 13 и Funtouch OS 13, без root‑доступа. Рабочий профиль создан через Shelter. В личном профиле были установлены два VPN‑клиента с разными сетевыми движками и поддержкой различных протоколов. При этом активное VPN‑соединение обеспечивал один из них, с поддержкой split tunneling, второй оставался просто установленным в системе.
Меня интересовали четыре вещи:
видимость VPN‑клиента из другого профиля,
признаки работающего туннеля,
маршрут собственных соединений RKNHardering, и
возможность принудительно воспользоваться обнаруженной VPN‑сетью.
Чтобы отделить влияние самого Shelter от состояния VPN и настроек маршрутизации, я провел шесть последовательных проверок:
№ | Где работает RKNHardering | Состояние исследуемого VPN | Условия |
|---|---|---|---|
1 | Рабочий профиль Shelter | Включен | VPN не распространяется на рабочий профиль |
2 | Рабочий профиль Shelter | Выключен | Прямое подключение |
3 | Личный профиль | Включен | RKNHardering исключен из VPN |
4 | Личный профиль | Выключен | Прямое подключение |
5 | Личный профиль | Включен | Split tunneling отключен, трафик RKNHardering идет через VPN |
6 | Личный профиль | Удален | Контроль после удаления |
VPN‑клиент запускался только в личном профиле. В рабочем профиле Shelter VPN мы не включали — оттуда вели наблюдение.
Основными были два сравнения: №1 и №2 — меняем состояние VPN, наблюдая из Shelter; №1 и №3 — сравниваем рабочий и личный профили при включенном VPN. В последней паре условия исключения различались: в №1 туннель не обслуживал рабочий профиль, а в №3 из туннеля был исключен сам RKNHardering, то есть в обоих этих случаях проверенные соединения приложения проходили напрямую.
Все результаты сохранялись в JSON‑отчетах. При анализе я ориентировался именно на конкретные диагностические наблюдения и ошибки, а не на итоговый вердикт программы. Остальные сценарии служили контролем — к ним вернемся после основных сравнений.
Посмотрим, что получилось.
3. Первый результат: VPN не используется, но его интерфейс виден
VPN работает, но приложение выходит напрямую
Начал я с наиболее интересного для себя сценария: VPN включен в основном профиле Android, рабочий профиль Shelter исключен из VPN‑маршрутизации, а RKNHardering выполняет диагностику изнутри этого профиля.
На первый взгляд, все работало именно так, как и было задумано. Приложение выходило в интернет напрямую, через российского провайдера. Это подтверждалось несколькими внешними IP‑чекерами. Более того, Android не определял активную сеть приложения как VPN: атрибут TRANSPORT_VPN отсутствовал, а NET_CAPABILITY_NOT_VPN, напротив, присутствовал.
Казалось бы, все логично. Приложение исключено из туннеля, его трафик идет напрямую — чего еще ожидать?
Но технические результаты диагностики показывали еще кое‑что.
RKNHardering обнаружил в системе интерфейс tun0 с индексом 48 и MTU 1280. Это было не одиночное срабатывание эвристики: интерфейс находился при перечислении через Java API, в нативной диагностике и доступных системных записях. В результатах присутствовали и маршруты через tun0 для IPv4 и IPv6.
JSON-артефакт (выдержка)
{
"indirectSigns": {
"name": "Косвенные признаки",
"detected": true,
"needsReview": false,
"hasError": false,
"status": "[DETECTED]",
"maturity": "STABLE",
"findings": [
{
"description": "VPN-интерфейс обнаружен: tun0",
"detected": true,
"needsReview": false,
"isInformational": false,
"isError": false,
"source": "NETWORK_INTERFACE",
"confidence": "MEDIUM",
"maturity": "STABLE"
},
{
"description": "MTU аномалия: tun0 MTU=1280 (< 1500)",
"detected": true,
"needsReview": false,
"isInformational": false,
"isError": false,
"source": "NETWORK_INTERFACE",
"confidence": "MEDIUM",
"maturity": "STABLE"
},
{
"description": "Маршрут по умолчанию через нестандартный интерфейс: tun0",
"detected": true,
"needsReview": false,
"isInformational": false,
"isError": false,
"source": "ROUTING",
"confidence": "MEDIUM",
"maturity": "STABLE"
},
{
"description": "Выделенные маршруты через VPN\/нестандартный интерфейс: 2000::\/3 via tun0, 100.64.23.83\/32 via tun0",
"detected": true,
"needsReview": false,
"isInformational": false,
"isError": false,
"source": "ROUTING",
"confidence": "MEDIUM",
"maturity": "STABLE"
},
{
"description": "Маршрутизация указывает на split tunneling: одновременно видны direct и tunnel routes",
"detected": true,
"needsReview": false,
"isInformational": false,
"isError": false,
"source": "ROUTING",
"confidence": "MEDIUM",
"maturity": "STABLE"
}
]
}
}Правда, здесь есть одна тонкость. Несколько способов обнаружения tun0 не означают наличия нескольких статистически независимых подтверждений VPN. Все они в данном случае указывают на один и тот же сетевой интерфейс, просто сведения о нем доступны через разные системные механизмы Android и Linux.
Выключаем VPN: что изменилось?
Следующим шагом я полностью отключил VPN в основном профиле Android, оставив RKNHardering внутри Shelter. Остальные существенные условия проверки сохранились.
Результаты удобно сравнить напрямую:
Диагностический признак | № 1: VPN включен | № 2: VPN выключен |
|---|---|---|
Внешний IPv4 | Прямой IP провайдера | Тот же прямой IP |
| Не обнаружен | Не обнаружен |
Интерфейсы через | 6 | 5 |
| Обнаружен, MTU 1280 | Не обнаружен |
Маршруты через | Обнаружены | Не обнаружены |
Это важная контрольная проверка. Наблюдавшийся интерфейс не оказался постоянным артефактом конфигурации: после отключения VPN в основном профиле он исчез вместе со связанными маршрутами. Внешний IPv4 диагностического приложения при этом не изменился.
Что это нам дает?
В нашем опыте приложение внутри Shelter замечало изменение состояния VPN за пределами своего профиля, хотя его обычные IPv4-запросы шли напрямую. Именно это отличает наблюдаемость туннеля от маршрута собственного трафика.
Но одно имя tun0 еще не позволяет определить конкретный VPN‑сервис. А какие сведения о клиенте станут доступны, если запустить наблюдателя уже не в рабочем, а в личном профиле?
4. А если запустить тот же инструмент в личном профиле?
В эксперименте №3 я снова включил VPN в личном профиле и запустил там RKNHardering, исключив его из туннеля. Полученные результаты сопоставил с экспериментом №1.
Два профиля — два разных набора сведений о приложениях
Первое существенное отличие обнаружилось там, где его, собственно, и следовало ожидать, — в сведениях об установленных приложениях.
В основном профиле RKNHardering смог идентифицировать установленные VPN‑клиенты. Причем речь шла не просто о названиях приложений: диагностический инструмент получил сведения об их пакетах, версиях, объявленных VPN‑службах и некоторых нативных компонентах.
А вот из рабочего профиля Shelter аналогичные сведения получить не удалось. VPN‑клиенты, установленные в основном профиле, отсутствовали в результатах перечисления доступных приложений.
Это уже осязаемый результат изоляции: проверенные механизмы RKNHardering перестали раскрывать сведения об этих клиентах. Речь именно об использованном способе диагностики, а не о том, что пакет абсолютно невидим любыми средствами.
А вот сетевая картина почти не изменилась
При сопоставлении двух JSON‑отчетов выяснилось, что в разделе косвенных сетевых признаков совпали 13 из 14 текстовых наблюдений. Единственное расхождение касалось имени одного из мобильных интерфейсов, что могло быть связано с изменением состояния модема между проверками.
Посмотрим на основные результаты:
Диагностический признак | № 1: рабочий профиль Shelter | № 3: личный профиль |
|---|---|---|
VPN‑клиенты, установленные в личном профиле | Не обнаружены | Обнаружены |
| Обнаружен, 1280 | Обнаружен, 1280 |
Маршруты через | Обнаружены | Обнаружены |
| Отсутствует | Отсутствует |
Внешний IPv4 | Прямой IP провайдера | Тот же прямой IP |
JSON-артефакт (выдержка в обезличенном виде)
{
"directSigns": {
"evidence": [
{
"source": "INSTALLED_APP",
"detected": true,
"confidence": "LOW",
"packageName": "[пакет A]",
"kind": "GENERIC_VPN",
"detectionDomain": "VPN_OR_PROXY",
"hardSignal": false
},
{
"source": "VPN_SERVICE_DECLARATION",
"detected": true,
"confidence": "MEDIUM",
"packageName": "[пакет B]",
"kind": "GENERIC_VPN",
"detectionDomain": "VPN_OR_PROXY",
"hardSignal": false
},
{
"source": "VPN_SERVICE_DECLARATION",
"detected": true,
"confidence": "MEDIUM",
"packageName": "[пакет A]",
"kind": "GENERIC_VPN",
"detectionDomain": "VPN_OR_PROXY",
"hardSignal": false
}
],
"matchedApps": [
{
"packageName": "[пакет A]",
"appName": "[VPN-клиент A]",
"kind": "GENERIC_VPN",
"source": "VPN_SERVICE_DECLARATION",
"confidence": "MEDIUM",
"technicalMetadata": {
"serviceNames": [
"[служба A1]",
"[служба A2]",
"[служба A3]"
],
"coreType": "Xray/V2Ray",
"systemApp": false,
"matchedByNameHeuristic": false
}
},
{
"packageName": "[пакет B]",
"appName": "[VPN-клиент B]",
"kind": "GENERIC_VPN",
"source": "VPN_SERVICE_DECLARATION",
"confidence": "MEDIUM",
"technicalMetadata": {
"serviceNames": [
"[служба B1]"
],
"coreType": "sing-box",
"goVersion": "go1.25.0",
"systemApp": false,
"matchedByNameHeuristic": false
}
}
]
}
}Обнаружился и еще один интересный штрих. Из рабочего профиля Shelter RKNHardering смог определить, что само диагностическое приложение выполняется во вторичном пользовательском контексте Android — User 10. Здесь 10 — именно ID пользователя, а не версия операционной системы.
{
"nativeSigns": {
"evidence": [
{
"source": "SANDBOX_ISOLATION",
"detected": true,
"confidence": "MEDIUM",
"description": "Isolation: secondary user 10"
}
]
}
}Получается, что сам факт работы в отдельном профиле тоже не остаётся скрытым. Приложение способно определить, что оно запущено не в основном пользовательском пространстве Android, и это ещё один момент, который стоит учитывать на практике.
Что добавили остальные контрольные проверки
В эксперименте №4 я полностью выключил VPN, оставив диагностический инструмент в личном профиле. Интерфейс tun0 исчез, но сведения об обоих установленных VPN‑клиентах по‑прежнему оставались доступны.
Здесь мы убеждаемся, что обнаружить установленное VPN‑приложение и обнаружить активное VPN‑соединение — разные вещи.
Затем, в эксперименте №5, я отключил split tunneling и направил трафик RKNHardering через VPN. Теперь появился TRANSPORT_VPN, а внешние IP‑чекеры определили адрес VPN‑выхода в Нидерландах.
Получился положительный контроль: использование туннеля самим приложением отразилось и в характеристиках активной сети, и во внешнем IP.
Наконец, в эксперименте №6 я удалил исследуемый VPN‑клиент. После этого RKNHardering больше не обнаруживал ни интерфейс tun0, ни сведения об удаленном приложении и его VPN‑службах. Второе установленное VPN‑приложение продолжало определяться.
В совокупности шесть проверок позволили разделить то, что на практике нередко смешивают: наличие VPN‑клиента, существование активного туннеля и использование этого туннеля конкретным приложением.
Теперь можно обратиться к исходникам сканера и посмотреть, откуда берутся эти сведения.
5. Почему Android позволяет увидеть то, что находится за границей профиля?
Что можно узнать через Android Framework
Начнем со штатного сетевого API Android — ConnectivityManager. Через него приложение получает сведения о сетях, их характеристиках и состоянии подключения. Метод getNetworkCapabilities() возвращает свойства выбранного объекта Network, включая тип транспорта.
Для VPN предусмотрен специальный признак TRANSPORT_VPN. А атрибут NET_CAPABILITY_NOT_VPN, напротив, указывает, что рассматриваемая сеть не является VPN.
Но здесь важно не перепутать два понятия: активную сеть самого приложения и другие сетевые объекты, о которых ему может быть доступна информация.
Эту разницу хорошо показывает модуль UnderlyingNetworkProber.kt. Программа использует ConnectivityManager.allNetworks, запрашивает характеристики полученных сетей и ищет объект с TRANSPORT_VPN и NET_CAPABILITY_INTERNET.
Если такая сеть обнаружена, программа пытается выполнить запрос через нее. Если нет, но найден tun0, предусмотрен резервный механизм — прямая проверка интерфейса.
Так вот, в отчетах №1 и №3 сохранились сообщения о неуспешных попытках привязки сокета к Android Network с идентификаторами 141 и 165. Сами номера тип сети не определяют. Но сопоставление ошибок с веткой кода, в которой они возникают, указывает на обнаружение VPN Network в обоих опытах — в том числе из Shelter.
Получается, что отсутствие VPN‑транспорта у активной сети не помешало инструменту получить сведения о другой сети с этим признаком.
А что остается доступным через Linux?
Но ConnectivityManager — далеко не единственный способ исследовать сетевое окружение Android.
Стандартный Java API NetworkInterface позволяет перечислять сетевые интерфейсы и получать их характеристики. На нативном уровне RKNHardering использует getifaddrs() и if_indextoname() — функции системной библиотеки libc, а не Android Framework.
Дополнительные сведения доступны через виртуальную файловую систему procfs и маршрутную диагностику. Здесь программа обращается к сетевому состоянию Linux, а не к характеристикам своей активной Android Network. Поэтому отсутствие TRANSPORT_VPN в одном результате не противоречит появлению tun0 в другом: речь идет о разных объектах наблюдения.
Ограничение доступа через один API не обязательно закрывает остальные пути. При этом доступность конкретных сведений определяется разрешениями, политиками SELinux, реализацией системных API и прошивкой. В наших отчетах имя интерфейса появлялось через несколько таких механизмов.

А удалось ли воспользоваться обнаруженным туннелем?
Это RKNHardering тоже попытался проверить. В экспериментах №1 и №3 вызов Network.bindSocket() для обнаруженной VPN Network завершился ошибкой EPERM (Operation not permitted).
Программа предприняла и резервную попытку — через libcurl с явным указанием интерфейса tun0 (CURLOPT_INTERFACE, if!tun0). Но сетевой запрос завершился тайм‑аутом примерно через пять секунд. Получить внешний IP VPN таким способом не удалось ни из Shelter, ни из личного профиля.
Таким образом, принудительное использование обнаруженного VPN‑интерфейса в наших экспериментах не подтвердилось.
6. Так что в итоге дает Shelter?
После всех экспериментов у меня не возникло желания отказаться от Shelter.
Если задача — разделить приложения и их пользовательское окружение, Shelter остается полезным инструментом. Если требуется гарантированно скрыть работающий VPN — полагаться только на него не стоит.
Поэтому исчезновение предупреждений после переноса приложений, которое я наблюдал летом, не следовало воспринимать как подтверждение полной невидимости VPN.
Правда, сами данные приложений, аккаунты и файловую изоляцию мы отдельно не испытывали — это документированные свойства Work Profile. А вот ограничение видимости установленных пакетов удалось наблюдать непосредственно.
Остальные результаты относятся к конкретным сетевым проверкам на нашем стенде: Vivo Y53s, Android 13 / Funtouch OS 13, RKNHardering 2.11.0. На других версиях Android и прошивках доступность отдельных механизмов может отличаться.
И, пожалуй, главный результат этого исследования для меня именно в этом: теперь мы гораздо точнее представляем, где проходят границы возможностей Shelter. Так что, как говорится, кто предупрежден — тот вооружен. 😉
Вместо послесловия
Кстати, мне было бы интересно узнать, замечали ли вы похожие изменения в российских Android‑приложениях. Ниже оставил небольшой опрос, а подробностями при желании предлагаю поделиться в комментариях: что за приложение, какие сбои или предупреждения появились и когда.
Особенно интересны случаи, когда приложение уже исключено из VPN через split tunneling, но его поведение все равно меняется при полном отключении туннеля. А если используете Shelter или другое приложение для изоляции — поделитесь, насколько это помогло в подобных ситуациях.
Ну а я продолжу исследовать, что еще Android‑приложения способны узнать о сетевом окружении. В следующем материале разберемся с WebRTC, механизмами ICE/STUN и возможностями обнаружения сетевых адресов. Для этого я разработал собственный инструмент RTCProbe и провел первые эксперименты.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
100%Да, заметные изменения: предупреждения о VPN, сбои, ограничения доступа, вплоть до блокировки аккаунтов.1
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.