-
Notifications
You must be signed in to change notification settings - Fork 4
Operator API Security ru
Operator API — это trust-bounded поверхность расширения. Клиент трактует каждое значение как недоверенный вход из сети и применяет жёсткую валидацию, прежде чем оно коснётся UI, темы или диска.
Этот документ описывает, что клиент гарантирует, что нет, и от каких атак мы защищаемся.
Релевантные классы атакующих:
- Злонамеренный оператор — контролирует ответ URL подписки. Может слать враждебные заголовки (oversize значения, пейлоады, вредоносные URL). Мы относимся к нему как к любому внешнему входу.
- Сетевой атакующий — перехватывает HTTP-ответ подписки в скомпрометированной сети (редко, т.к. подписки по HTTPS).
- Компрометация image-хоста оператора — CDN лого захвачен и начинает отдавать враждебные изображения / редиректы во внутренние сервисы.
- App-store ревьюер / регулятор — должен видеть, что «брендинг» не может тихо превратить приложение в другое (смена идентичности ограничена тем, что видит юзер, а не тем, как приложение себя ведёт).
Заметка: оператор, который к тому же провайдер подписки, и так может абьюзить многое вне этого API (выбор прокси, правила роутинга). Брендинг добавляет пару ручек — они должны быть безопасны по отдельности.
- Trim пробелов
- Удаление control-символов (
U+0000–U+001F,U+007F, кроме\n/\r/\tгде поле разрешает) - Truncation до макс. длины поля (см. Справочник заголовков)
- Декодирование
base64:префикса - После всего — blank → поле считается отсутствующим
Принимаются, только если начинаются с одного из:
https://tg://mailto:-
t.me/(auto-промоут вhttps://t.me/)
http:// отклоняется для брендинг-URL. Единственное исключение — метадата-пробы самих URL подписки, где юзер явно согласился на plaintext подписку.
URL дополнительно проходят looksLikeUrl (длина, без встроенных пробелов).
- Только regex
^#[0-9A-Fa-f]{6}$— никаких#RGB,rgba(), именованных. - Клиент считает WCAG-яркость против цвета поверхности. Если контраст ниже 3:1 (минимум AA «large text») — цвет отклоняется, используется дефолтный акцент.
- Boolean:
true/1/yes/on→ true;false/0/no/off→ false; иначе — заголовок отсутствует. - Enum: только документированные значения; неизвестные игнорируются.
-
Subscription-Userinfoквоты:Long≥0;expire=в[0, now + 10 лет], иначе игнор.X-Brand-Max-Devices: 1–999.
Поле наивысшего риска — качаем произвольные байты по выбранному оператором URL. Защиты:
- Только HTTPS. http:// отклоняется до построения запроса.
-
Никаких редиректов в приватные сети. Перед каждым хопом резолвнутый IP проверяется против:
-
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,127.0.0.0/8,169.254.0.0/16 -
::1/128,fc00::/7,fe80::/10 - Любого другого RFC1918 / RFC4193 / RFC6890 зарезервированного пространства
-
- Connect + read таймауты: 15с каждый · Макс. редиректов: 3
- Привязано к DNS-резолверу устройства (без системного оверрайда во время фетча)
-
Content-Typeдолжен бытьimage/png,image/webpилиimage/jpeg. Всё прочее (включаяimage/svg+xml,image/gif,text/html) отклоняется. -
Content-Length(если есть) ≤ 512KB. Если отсутствует — чтение капается на 512KB, любой байт сверх прерывает загрузку. - Байты парсятся
BitmapFactory.decodeByteArrayсначала в bounds-only режиме; второй декод только еслиoutWidth × outHeight × 4 ≤ 4MB(макс ~1024×1024). - Анимированные (animated WebP) декодятся как первый кадр.
- Лого в
<filesDir>/brand/<sha256(url)>(приватно приложению) - Атомарная запись:
tmp+rename - Чистится: при удалении подписки; при новом лого с другим URL для той же подписки
- Никогда не пишется во внешнее хранилище, недоступно другим приложениям
SVG может нести XSS-подобные пейлоады (<script>, xlink:href на удалённые ресурсы, CSS-импорты, foreignObject HTML). Даже безопасный парсинг не гарантия против DoS (billion-laughs, geometry explosion). Лого — это флаг-битмапы; PNG/WebP покрывают кейс без SVG-attack surface.
- Правда ли лого представляет оператора? Нет — мы не верифицируем владение брендом.
- Правда ли сайт оператора его? То же.
- Совпадает ли имя бренда с панелью? Нет — свободная форма.
Это операторские trust-вопросы, не клиент-безопасность. Юзер уже выбрал доверять этой подписке, добавив её.
By design, брендинг никогда тихо не меняет идентичность приложения так, чтобы юзер не заметил:
- Иконка лаунчера и имя пакета Android никогда не меняются. Ревьюеры стора всегда опознают приложение.
- Экран About всегда показывает строку
<brand> — powered by ClashFest(непереводимую, фиксированную). - Когда брендинг активен, в Settings есть «Reset branding» — восстанавливает дефолтную идентичность в один тап.
Кэш бренда (имя, лого, акцент, ссылки) чистится:
- Активный профиль удалён (и других профилей нет)
- Юзер нажал «Reset branding» в Settings
- Последующий фетч той же подписки вернул заголовки без brand-значения (очистка брендинга — операторское действие, не залипание навсегда)
Мы не сбрасываем брендинг на апгрейде приложения — он переживает бампы версий.
- Фетчи лого — не чаще раза за цикл обновления подписки
- Неудачные фетчи запоминаются на 1ч, чтобы избежать retry-штормов
- «Активный бренд» в
SharedPreferences— источник истины между обновлениями: потеря сети не стирает брендинг посреди сессии
Если нашли security-проблему в Operator API или парсере — следуйте security-политике в корне репозитория.
📱 User Guide
- Getting Started
- Profiles & Nodes
- Routing & Rules
- Settings
- Deep Links
- Encrypted Subscriptions
- Troubleshooting
🏢 Operator API
📺 Companion