Документ описывает, что именно OrbitFM считает угрозой, что защищено, а что сознательно принято как остаточный риск. Это не обещание «безопасного приложения», а модель, по которой можно рецензировать правки. Актуальные решения: ADR-0005 ред. 2 (single-instance, IPC и локальный транспорт), ADR-0025 ред. 4 (политика артворка), ADR-0011 ред. 2026-10-29 (контур поставки), ADR-0040 ред. 2 (импорт внешних потоков).
OrbitFM — настольный плеер в пользовательской сессии. Ценность, которую мы защищаем: целостность пикселей и байтов, уходящих в декодер изображений; отсутствие скрытого исходящего трафика; отсутствие интерпретации недоверенного текста каталога как терминальных последовательностей или разметки; предсказуемость ресурсного потребления длинной сессии. Мы не защищаем: содержимое пользовательского каталога, состояние процесса и управление воспроизведением от самого пользователя — у него полный доступ к файлам и сокету по определению.
Управление идёт через локальный транспорт: на 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.
Приложение ничего не слушает в сети: единственные слушатели — сокет и 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.
Всё, что уходит наружу, и зачем:
| Куда | Когда | Примечание |
|---|---|---|
| Зеркала 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 и никуда
не отправляются.
Ссылка favicon приходит из чужого каталога, а её содержимое попадает в
декодер. Поэтому загрузка ограничена двумя ярусами (ADR-0025 ред. 4):
- ярус 1 — синтаксический, без DNS. Отклоняются: не-
http(s), любой userinfo, порт 0, IP-литералы приватных и служебных диапазонов (loopback, RFC 1918, link-local и облачные метаданные169.254/16, CGNAT100.64/10, documentation-сети, benchmark-диапазон198.18/15, multicast и резерв), IPv6::1/ULA/link-local/multicast/Teredo/v4-mapped, 6to4 с вложенным приватным адресом, NAT6464:ff9b::/96с вложенным приватным IPv4 и local-use64: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-inartwork_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). Нет вердикта — поле уходит пусто, резолв считается фоном и пробуждает кадр; не проходит — уходит пусто, без переписывания адреса. Главный поток и парк не блокируются.
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.
Имя, страна, теги и кодек станции — пользовательский контент каталога. До
попадания в 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.
Перечислено не «извинение», а решение с датой пересмотра.
- Кэш припинованных клиентов живёт 60 с. За это время DNS может смениться (rebinding), а соединение останется припиненным к прежнему, проверенному адресу. Bounded-карта вердиктов яруса 2 закрывает публикацию, но не само соединение фетчера: это цена переиспользования клиентов, а не дыра в гейте.
- HTTPS-only сужает покрытие обложек по умолчанию. Часть каталога раздаёт
favicon только по
http— такие станции показывают инициалы, пока владелец не включитartwork_allow_http. Включение сознательно возвращает риск on-path подмены байтов, уходящих вimage; понижениеhttps → httpв редиректе запрещено и при включённом opt-in. - Поле
station.faviconвstatus --json— сырое значение каталога. Оно не проходит ярус 1: JSON-статус описывает станцию, а не принимает решения о загрузке, и менять схему 1 ради фильтрации нельзя (клиенты обязаны игнорировать неизвестное). Внутренний интерфейс фильтрует сам — обложка в MPRIS/SMTC проходит ярусы 1 и 2, пиксели глобуса — политику фетчера; внешний потребитель обязан относиться кstation.faviconкак к недоверенному вводу и применять свою политику перед загрузкой. - Пять прощённых 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, когда дата наступает. xesam:urlв MPRIS несёт click-tracking-параметры потока. Публикуетсяurl_resolvedстанции — тот самый адрес, который Radio Browser отдаёт вместе с параметрами учёта прослушивания, и он же участвует в Track Id. Проверено на живой станции: любой локальный наблюдатель метаданных видит те жеs/e/h, что иlibmpvв запросе. Серверу этим ничего дополнительного не раскрывается (клик отправляет сам поток), но параметры становятся заметны в DE. Их удаление — изменение контракта метаданных, то есть отдельная запись с ADR; в 0.16 поведение сохранено как есть.- Браузерное расширение распространяется распакованным. Публичный ключ задаёт стабильный Chrome-ID и позволяет native-манифесту разрешить ровно одно расширение, но не удостоверяет целостность popup: тот, кто может изменить файлы в пользовательском каталоге, меняет поведение панели. Firefox-дополнение временное; постоянная установка требует подписи Mozilla. Магазинная поставка и подпись — отдельное решение, в 0.16 не заявлены.
- Каталожная станция может резолвиться в LAN. Ярус 1 отбивает приватные
литералы и локальные имена, но не делает DNS;
libmpvрезолвит адрес сам, без пина и без строгого режима. Имя из каталога, разрешающееся во внутреннюю сеть, вызовет запрос к ней при автоматическом воспроизведении. Ответ наружу не уходит — это плеер, а не прокси, — но сам факт обращения возможен. Закрытие потребовало бы резолва перед каждым автостартом и пина для mpv; пересмотр — при следующей ревизии ADR-0025. - Строгий режим
play-url— проверка перед соединением, а не пин. DNS проверяется ярусом 2, затемreqwestрезолвит имя сам; при rebinding между проверкой и соединением адрес может оказаться приватным. Если резолв исходной цели не удался, режим остаётся нестрогим: импорт не запрещается из-за временного сбоя DNS. Влияние ограничено явным действием пользователя, которому LAN и так разрешён контрактом: гейт существует против ленивого pivot с публичной страницы, а не как граница SSRF. Пересмотр — еслиplay-urlкогда-нибудь станет автоматическим. - DACL named pipe задаётся не явно. Имя канала персонально для профиля,
второй экземпляр имени не займёт (
first_pipe_instance), удалённые клиенты отклоняются (reject_remote_clients), но собственная security descriptor не выставляется: действует DACL по умолчанию (владелец, SYSTEM, администраторы). Явный DACL через FFI отложен до отдельной правки Windows-слоя.
Каждый релиз — четыре артефакта (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-приёмка не выполнена.
tracing пишет в stderr, фильтр по умолчанию orbitfm=info; уровни debug
показывают причины отказа артворка (политика, MIME, размер, декод) и счётчики
favicon-кэша. В логах остаются хосты и пути URL, содержимое ответов — нет.
Ротации нет: лог — это то, что пользователь сам перенаправил.
Пожалуйста, не заводите публичный issue. Используйте приватный отчёт об
уязвимости в разделе Security репозитория ModernByte/orbitfm (GitHub →
Security → Report a vulnerability) — он недоступен обычным посетителям и не
попадает в публичную ленту. Полезно приложить: версию (orbitfm --version),
платформу, точную последовательность шагов и, если речь про артворк, саму
ссылку станции.
- 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