Skip to content

Security: ModernByte/orbitfm

Security

SECURITY.md

Безопасность OrbitFM

Документ описывает, что именно OrbitFM считает угрозой, что защищено, а что сознательно принято как остаточный риск. Это не обещание «безопасного приложения», а модель, по которой можно рецензировать правки. Актуальные решения: ADR-0005 ред. 2 (single-instance, IPC и локальный транспорт), ADR-0025 ред. 4 (политика артворка), ADR-0011 ред. 2026-10-29 (контур поставки), ADR-0040 ред. 2 (импорт внешних потоков).

1. Что мы защищаем

OrbitFM — настольный плеер в пользовательской сессии. Ценность, которую мы защищаем: целостность пикселей и байтов, уходящих в декодер изображений; отсутствие скрытого исходящего трафика; отсутствие интерпретации недоверенного текста каталога как терминальных последовательностей или разметки; предсказуемость ресурсного потребления длинной сессии. Мы не защищаем: содержимое пользовательского каталога, состояние процесса и управление воспроизведением от самого пользователя — у него полный доступ к файлам и сокету по определению.

2. Кто может управлять приложением

Управление идёт через локальный транспорт: на Linux — Unix-сокет $XDG_RUNTIME_DIR/orbitfm.sock, на прочих Unix (macOS) — $TMPDIR/orbitfm-<uid>/orbitfm.sock, на Windows — named pipe \\.\pipe\orbitfm-<fnv1a64 хеш профиля>. Сокет имеет права 0600; каталог обязан принадлежать эффективному UID и не иметь group/other-битов (0700), иначе bind отказывается. На macOS каталог создаётся с 0700 и проверяется тем же правилом. Имя pipe включает профиль пользователя, first_pipe_instance не даёт занять его второму экземпляру, а reject_remote_clients закрывает сетевой доступ к каналу. Разграничения прав нет и не будет: любой процесс от имени пользователя, которому доступен сокет, может play, pause, stop, volume, search, favorite, play-result, play-library, play-url, hide, panel, settings и quit. Это осознанный контракт — плеер не является привилегированным сервисом и не обслуживает чужих пользователей. Если в системе есть процесс, который может писать в ваш $XDG_RUNTIME_DIR, он уже может всё, что можете вы. play-url при этом остаётся командой явного действия: ни окно, ни восстановление истории, ни старт процесса её не вызывают (п. 6). Будущий orbitfm-tui (ADR-0047) — ещё один локальный клиент того же контракта: он не открывает портов и не расширяет права, а его команды идут через тот же CLI/IPC.

3. Публичных сетевых интерфейсов нет

Приложение ничего не слушает в сети: единственные слушатели — сокет и pipe из п. 2. Файл сокета и pipe не публикуются наружу, HTTP-сервер внутри процесса отсутствует. Интеграции (плагины оболочек, браузерная панель) проходят через CLI и native messaging как адаптеры, а не через превращение IPC в публичный API. orbitfm native-host запускается браузером по stdio, не слушает порт и не поднимает GUI; его allowlist запрещает quit, play, mode и неизвестные команды, а играть расширение может только результат собственного поиска (play-result), элемент библиотеки (play-library) или явно выбранный URL страницы (play-url). Сообщения ограничены 1 МиБ в обе стороны (ADR-0038, интерфейс 14). Веб-страницы не имеют пути к порту: у расширения нет content scripts, background и externally_connectable.

4. Исходящие запросы: полный список

Всё, что уходит наружу, и зачем:

Куда Когда Примечание
Зеркала Radio Browser API поиск, карточка станции, random_station сервер выбирается DNS-discovery all.api.radio-browser.info, один адрес в ротации не хардкодится
…/json/url/<stationuuid> (click-count) при старте станции правило API провайдера; отказ от счёта кликов означал бы нарушение условий использования
Хост потока станции воспроизведение libmpv, мы не посредничаем и не проксируем
Хост артворка (favicon) когда изображение нужно интерфейсу единственный автоматический путь, проходящий через политику п. 5
Хост импортируемого потока или страницы play-url по явному действию резолвер читает плейлист/HTML не более 1 МиБ и отдаёт конечный адрес mpv (п. 6)

User-Agent — OrbitFM/1.0 (ModernByte; Linux; Rust) (ADR-0004). Телеметрии, аналитики, обновлений «с собой» и фоновых опросов нет. Настройки, избранное и история лежат в $XDG_CONFIG_HOME/orbitfm и $XDG_DATA_HOME/orbitfm и никуда не отправляются.

5. Политика артворка (почему у изображений есть свой контур)

Ссылка favicon приходит из чужого каталога, а её содержимое попадает в декодер. Поэтому загрузка ограничена двумя ярусами (ADR-0025 ред. 4):

  • ярус 1 — синтаксический, без DNS. Отклоняются: не-http(s), любой userinfo, порт 0, IP-литералы приватных и служебных диапазонов (loopback, RFC 1918, link-local и облачные метаданные 169.254/16, CGNAT 100.64/10, documentation-сети, benchmark-диапазон 198.18/15, multicast и резерв), IPv6 ::1/ULA/link-local/multicast/Teredo/v4-mapped, 6to4 с вложенным приватным адресом, NAT64 64:ff9b::/96 с вложенным приватным IPv4 и local-use 64:ff9b:1::/48, одноярлыковые хосты (http://router/), localhost, *.localhost, *.local, *.internal, *.home.arpa. Числовые трюки (0x7f.0.0.1, 127.1, 0177.0.0.1, 2130706433) канонизирует парсер WHATWG, поэтому проверка идёт по типизированному хосту.
  • ярус 2 — по разрешённым адресам. Резолв с таймаутом 3 с, all-or-nothing: хотя бы один неприватный адрес в ответе — отказ всему набору. Далее запрос клиентом, припинованным к проверенному набору (resolve_to_addrs), без системных прокси (.no_proxy()): прокси на loopback или в LAN не должен становиться обходом политики.
  • Редиректы переделываются по одному хоппу (Policy::none(), до 3 хоппов): каждый новый URL проходит ярусы заново, понижение https → http запрещено всегда, http как конечная цель — только при явном opt-in artwork_allow_http (по умолчанию выключен; включение переключает политику на лету и возвращает в очередь отклонённые favicon). Общий бюджет — 6 с wall-clock, и он покрывает весь обмен: соединение, заголовки и тело, а не только ожидание ответа. Медленное «капание» тела бюджет не продлевает.
  • Содержимое: только image/png, image/jpeg, image/webp (ICO и SVG — нет), не более 512 КиБ, лимит разрешения до декодера, декодер под catch_unwind, а слот загрузки освобождается RAII-страховкой даже при панике.
  • Кэш изображений ограничен по построению: entries.len() <= 256, in_flight <= 8, результаты идут в bounded-канал, текстура не аллоцируется для записи, которой уже нет.
  • Метаданные наружу (MPRIS artUrl, SMTC, Now Playing) проходят гейт Session::publish_gate: ярус 1 синхронно и свежий вердикт яруса 2 из bounded-кэша (64 записи, TTL 60 с, stale-while-revalidate). Нет вердикта — поле уходит пусто, резолв считается фоном и пробуждает кадр; не проходит — уходит пусто, без переписывания адреса. Главный поток и парк не блокируются.

6. Импорт внешних потоков (play-url)

Stream Discovery — единственный путь, который запускает воспроизведение по адресу, введённому человеком (ADR-0040 ред. 2). Поэтому у него отдельные правила, а не политика артворка:

  • Триггер — только явное действие. CLI orbitfm play-url <url> или клик в попапе («Play this page», выбор кандидата). Автозапуска при открытии окна, восстановлении истории или старте процесса нет; веб-страница не может инициировать импорт, скан вкладки выполняется только по клику.
  • Схемы и форма. Только http/https, без userinfo и пробелов; каждая ссылка из Location, плейлиста и HTML перепроверяется заново. HLS отклоняется до воспроизведения: .m3u8, application/vnd.apple.mpegurl, тело #EXT-X-, HLS-альтернатива на странице.
  • Лимиты. Ручной цикл редиректов, не более 5 хоппов; .m3u/.pls — до 20 записей и 2 уровней вложенности, защита от циклов; HTML-скан до 1 МиБ без парсера (og:audio, <audio src>, <source src>, заголовок); общий бюджет резолва 8 с; клиент без системных прокси.
  • LAN разрешён только здесь. Политика SSRF не применяется к явному действию: человек вправе слушать локальную станцию или тестовый сервер. Но если исходная цель публична, включается строгий режим: каждый редирект, плейлист и HTML-хоп обязан разрешаться только в публичные адреса (DNS с таймаутом 3 с, all-or-nothing), иначе импорт закрывается с ошибкой «ссылка ведёт во внутреннюю сеть». Публичная страница не может увести импорт в LAN через редирект.
  • Станция из потока. stationuuid = "url:" + FNV-1a 64(исходный URL), source = Stream, homepage — исходная страница; favicon, теги и страна пустые. Такая станция сохраняется в избранное и историю и играется позже без повторного резолва и обращения к каталогу (сам поток, разумеется, требует сети). Click-tracking Radio Browser для url: пропускается.
  • Автоматический путь защищён отдельно. Запись каталога StationSource::Radio перед стартом проходит ярус 1 (publishable): литеральный приватный адрес, локальное имя или userinfo не запускаются. Резолв в LAN для каталожного имени ярусу 1 не виден — это остаточный риск п. 8.7.

7. Недоверенный текст

Имя, страна, теги и кодек станции — пользовательский контент каталога. До попадания в status --json, меню трея, MPRIS/SMTC и панели оболочек они проходят санитизацию: управляющие символы C0/C1, DEL и bidi-переопределения (U+200E/200F, U+202A–202E, U+2066–2069) заменяются пробелом, пробельные последовательности схлопываются. Это закрывает внедрение терминальных escape-последовательностей через orbitfm status и бар-скрипты; JSON при этом остаётся валидным, а обычный Unicode (включая эмодзи и письменности) не страдает. Будущий orbitfm-tui повторяет эту очистку на своей стороне перед отрисовкой — как последний рубеж, даже если ядро уже санитизировало строки.

Каждая оболочка экранирует вывод по своим правилам: Polybar получает % как %%, Waybar — Pango-экранирование ("escape": true), i3blocks — XML, Plasma и Omarchy — Text.PlainText с заменой угловых скобок. Строки, которые бар печатает сам (классы playing/paused/…), — фиксированный словарь.

Поля-ссылки (favicon, homepage) санитизируются как текст, но их допустимость решается в точке использования: homepage открывается только при http(s), favicon проходит политику п. 5. Сырое значение favicon в status --json остаётся сырым — п. 8.3.

8. Принятые остаточные риски

Перечислено не «извинение», а решение с датой пересмотра.

  1. Кэш припинованных клиентов живёт 60 с. За это время DNS может смениться (rebinding), а соединение останется припиненным к прежнему, проверенному адресу. Bounded-карта вердиктов яруса 2 закрывает публикацию, но не само соединение фетчера: это цена переиспользования клиентов, а не дыра в гейте.
  2. HTTPS-only сужает покрытие обложек по умолчанию. Часть каталога раздаёт favicon только по http — такие станции показывают инициалы, пока владелец не включит artwork_allow_http. Включение сознательно возвращает риск on-path подмены байтов, уходящих в image; понижение https → http в редиректе запрещено и при включённом opt-in.
  3. Поле station.favicon в status --json — сырое значение каталога. Оно не проходит ярус 1: JSON-статус описывает станцию, а не принимает решения о загрузке, и менять схему 1 ради фильтрации нельзя (клиенты обязаны игнорировать неизвестное). Внутренний интерфейс фильтрует сам — обложка в MPRIS/SMTC проходит ярусы 1 и 2, пиксели глобуса — политику фетчера; внешний потребитель обязан относиться к station.favicon как к недоверенному вводу и применять свою политику перед загрузкой.
  4. Пять прощённых RustSec-адвизори (deny.toml, [advisories] ignore) — семья zbus 5 и её quick-xml 0.38.4, где safe upgrade отсутствует: playwire 2.0.0 и ksni 0.3.7 (последние релизы) semver-заперты на zbus 5. XML разбирается только от локальной session bus. Каждая запись имеет revisit-дату, а CI роняет job, когда дата наступает.
  5. xesam:url в MPRIS несёт click-tracking-параметры потока. Публикуется url_resolved станции — тот самый адрес, который Radio Browser отдаёт вместе с параметрами учёта прослушивания, и он же участвует в Track Id. Проверено на живой станции: любой локальный наблюдатель метаданных видит те же s/e/h, что и libmpv в запросе. Серверу этим ничего дополнительного не раскрывается (клик отправляет сам поток), но параметры становятся заметны в DE. Их удаление — изменение контракта метаданных, то есть отдельная запись с ADR; в 0.16 поведение сохранено как есть.
  6. Браузерное расширение распространяется распакованным. Публичный ключ задаёт стабильный Chrome-ID и позволяет native-манифесту разрешить ровно одно расширение, но не удостоверяет целостность popup: тот, кто может изменить файлы в пользовательском каталоге, меняет поведение панели. Firefox-дополнение временное; постоянная установка требует подписи Mozilla. Магазинная поставка и подпись — отдельное решение, в 0.16 не заявлены.
  7. Каталожная станция может резолвиться в LAN. Ярус 1 отбивает приватные литералы и локальные имена, но не делает DNS; libmpv резолвит адрес сам, без пина и без строгого режима. Имя из каталога, разрешающееся во внутреннюю сеть, вызовет запрос к ней при автоматическом воспроизведении. Ответ наружу не уходит — это плеер, а не прокси, — но сам факт обращения возможен. Закрытие потребовало бы резолва перед каждым автостартом и пина для mpv; пересмотр — при следующей ревизии ADR-0025.
  8. Строгий режим play-url — проверка перед соединением, а не пин. DNS проверяется ярусом 2, затем reqwest резолвит имя сам; при rebinding между проверкой и соединением адрес может оказаться приватным. Если резолв исходной цели не удался, режим остаётся нестрогим: импорт не запрещается из-за временного сбоя DNS. Влияние ограничено явным действием пользователя, которому LAN и так разрешён контрактом: гейт существует против ленивого pivot с публичной страницы, а не как граница SSRF. Пересмотр — если play-url когда-нибудь станет автоматическим.
  9. DACL named pipe задаётся не явно. Имя канала персонально для профиля, второй экземпляр имени не займёт (first_pipe_instance), удалённые клиенты отклоняются (reject_remote_clients), но собственная security descriptor не выставляется: действует DACL по умолчанию (владелец, SYSTEM, администраторы). Явный DACL через FFI отложен до отдельной правки Windows-слоя.

9. Целостность поставки

Каждый релиз — четыре артефакта (AppImage, MSI, DMG, .plasmoid) и файл SHA256SUMS; начиная со следующего релиза рядом публикуется помощник install-appimage.sh, который ставит AppImage в меню и не входит в само приложение. Важно: SHA256SUMS — это дайджесты, а не сигнатуры. Артефакты не подписаны Authenticode и Developer ID, notarization не выполняется — сертификатов у проекта нет, и мы не делаем вид, что это защищённый канал. Проверить скачанное нужно по SHA256SUMS и брать только из раздела Releases github.com/ModernByte/orbitfm. Поэтапный план подписей (detached-подпись Linux, Authenticode, Developer ID с notarization) зафиксирован в ADR-0011 и пока не выполнен.

Будущий orbitfm-tui в поставку этих четырёх артефактов не входит: он ставится из исходников (cargo install --path crates/orbitfm-tui). Пятый артефакт не добавляется без отдельного решения владельца; для него понадобится собственный контур целостности.

Инструменты сборки AppImage (linuxdeploy, appimagetool) скачиваются по пиннованным версиям и проверяются по SHA256 до запуска: подмена загрузки не должна давать код в артефакте. В GitHub Actions права записи выдаются только job'у публикации релиза, остальные job'ы работают с contents: read, а экшены пинуются полным SHA. Контур зависимостей проверяется cargo deny (лицензии, дубли, источники, адвизори) и чекером Flatpak-вендинга; сверх пушей и PR адвизори перепроверяются еженедельным job'ом. Для Windows и macOS пути вендинга общие, но live-приёмка не выполнена.

10. Логи

tracing пишет в stderr, фильтр по умолчанию orbitfm=info; уровни debug показывают причины отказа артворка (политика, MIME, размер, декод) и счётчики favicon-кэша. В логах остаются хосты и пути URL, содержимое ответов — нет. Ротации нет: лог — это то, что пользователь сам перенаправил.

11. Как сообщить об уязвимости

Пожалуйста, не заводите публичный issue. Используйте приватный отчёт об уязвимости в разделе Security репозитория ModernByte/orbitfm (GitHub → Security → Report a vulnerability) — он недоступен обычным посетителям и не попадает в публичную ленту. Полезно приложить: версию (orbitfm --version), платформу, точную последовательность шагов и, если речь про артворк, саму ссылку станции.

12. Связанные документы

  • ADR-0005 ред. 2: Single-instance и IPC
  • ADR-0025 ред. 4: безопасная обработка favicon
  • ADR-0016 ред. 2: асинхронный favicon-загрузчик
  • ADR-0011 ред. 2026-10-29: упаковка, LGPL и контур поставки
  • ADR-0038: браузерное расширение и native messaging host
  • ADR-0039: поток событий состояния
  • ADR-0040 ред. 2: импорт внешних потоков
  • ADR-0047: TUI на ratatui как локальный IPC-клиент (план)
  • deny.toml, packaging/cargo-deny-check.sh, docs/interfaces/05-ipc.md, docs/interfaces/14-native-host.md, docs/interfaces/16-tui.md

There aren't any published security advisories