# Модель угроз ## Периметр **Контур доверенный: домашняя LAN, публичного интернета здесь нет — не выдумывай его.** Сервис слушает `:8080` на хосте umbar внутри локальной сети, наружу не проброшен, доменного имени и обратного прокси у него нет. Веб-UI и REST API работают **без авторизации** осознанно; поле `[http].trusted_subnets` зарезервировано, но не применяется. Целевой периметр — **тот же**: выставлять jellybit в интернет не планируется. Если это когда-нибудь изменится, первым шагом идёт задача «Авторизация веб-UI», и модель угроз пересматривается целиком, а не дополняется. **Находки строятся против сегодняшнего периметра.** «Любой может открыть страницу и удалить загрузку» — это принятое решение, а не дефект; чтобы стать дефектом, ему нужен путь снаружи LAN. ## Недоверенный вход Что приходит извне и каким каналом. Всё перечисленное контролируется не нами и может быть враждебным по содержанию, даже когда канал доверенный. | Что | Канал | Чем опасно | | --- | --- | --- | | Имена файлов и каталогов раздачи | qBittorrent API | разделители пути, `..`, управляющие символы, юникод-омоглифы, длина сверх лимита ФС | | Имя торрента, поля magnet (`dn`, `tr`) | приём | то же плюс подстановка в промпт | | Байты `.torrent` | приём (файл на форме) | bencode-разбор недоверенных данных, размер, вложенность | | Текстовый контекст человека | все транспорты | попадает в промпт LLM целиком | | Сообщение торрент-бота | Telegram (пересылка) | чужой формат, парсер, ссылки; текст автора бота, а не отправителя | | **Ответ LLM** | HTTP к эндпоинту | целиком под влиянием входа выше; названия, годы, номера сезонов и серий, из которых строится целевой путь | | Ответы метабаз | HTTP к TMDB/TVDB/TVMaze | канонические названия, из которых тоже строится путь | | Ответы qBittorrent | HTTP | пути, состояния, размеры | | Запросы веб-UI и REST | LAN | идентификаторы, параметры действий | **Выход LLM не отвечает за безопасность.** Инъекция в промпт считается состоявшейся по умолчанию; защита стоит ниже — на валидации целевого пути. ## Из чего строятся пути и ключи Отсюда строится выход за пределы песочницы — самое ценное место для враждебного прохода. - **Целевой путь** = `paths.movies`/`paths.series` + имя папки тайтла + (для сериала) `Season NN` + имя файла + расширение. Имя папки и файла собираются в `internal/naming` из полей распознавания: `title`, `original_title`, `year`, `season`, `episode`, provider-тег вида `[tmdbid-…]`. **Все эти поля — недоверенный вход.** - **Правило:** компоненты санитизируются (убираются разделители пути, `..`, управляющие символы), финальный `filepath.Clean`-путь обязан быть **строго под** соответствующей библиотекой, иначе операция отклоняется. Проверка на результате, а не на входе. - **Исходный путь** = `save_path` из qBittorrent + относительное имя файла из `/torrents/files`. Тоже недоверенный, но по нему мы только **читаем и линкуем**; писать в `paths.downloads` нельзя вообще. - **Ключ идентичности загрузки** — инфохэш (v1 SHA-1 / v2 SHA-256), нормализуется в lowercase hex фиксированной длины. Крафт-магнет с чужим или подставным хешем — известное направление атаки на владение (задача в беклоге). - **Идентификаторы сущностей** — ULID, `ident.Parse` на каждой входной границе: строка из запроса не доходит до SQL непроверенной. - **Владение целевым путём** — один путь, один владелец-`file_link`; смена владельца возможна только на свободном пути. ## Что разграничивает доступ - **Telegram** — allowlist `telegram.allowed_user_ids`, **fail-closed**: пустой список запрещает всем. Это единственное реальное разграничение в системе. - **Веб-UI и REST** — не разграничивают ничего: любой в LAN может всё. Осознанно, см. «Периметр». - **Файловая система** — контейнер под `1000:1000`, смонтирована только песочница `/srv/media` и собственные каталоги `/config` (ro) и `/data`. `/srv/applications` целиком в контейнер не попадает. - **qBittorrent** — логин и пароль WebUI; docker-подсеть намеренно не входит в его LAN-whitelist. ## Что чувствительнее чего 1. **Медиафайлы в раздаче** — единственное, что невосстановимо. Отсюда инвариант «источник неприкосновенен» и гард последней копии в Undo (`nlink <= 1` → отказ целиком). 2. **База `/data/jellybit.db`** — восстановима только перезапуском всей работы: теряется всё in-flight состояние и история привязок. 3. **Секреты**: пароль qBittorrent, ключи LLM и метабаз, токен Telegram, API-ключ Jellyfin. Живут только в `config.toml` (`0600`, рендерит деплой) и **никогда не попадают в логи, в диагностику состояния и в ответы API** — правило в [conventions/logging.md](conventions/logging.md). 4. **Библиотечные хардлинки** — восстановимы повторной раскладкой, поэтому ниже по шкале, хотя видны пользователю первыми. ## Что вне модели Перечислено явно: против этого находки не строятся. - **Злонамеренный участник LAN.** Сеть считается доверенной; «сосед по вайфаю удалил загрузку через веб-UI» — не дефект в сегодняшнем периметре. - **Злонамеренный оператор.** Владелец может всё по определению, включая удаление раздачи вместе с файлами. - **Компрометация соседних сервисов** — qBittorrent, Jellyfin, LLM-эндпоинта, хоста umbar. Если qBittorrent врёт про пути, мы проиграли раньше. - **Отказ в обслуживании изнутри контура.** Огромная раздача, тысяча файлов, бесконечный ответ LLM — это вопросы устойчивости и ресурсов ([architecture.md](architecture.md) → «Эксплуатация»), а не безопасности. Отсутствие лимита на размер ответа LLM — известный пробел, задача в беклоге. - **Целостность содержимого медиафайлов.** Что в контейнере mkv — не наша забота. - **Цепочка поставки** — модули Go, базовый образ distroless, плагины тулинга. - **Приватность запросов к внешним сервисам.** Названия раздач уезжают в LLM и метабазы; это принято сознательно, прокси в конфиге есть. - **Физический доступ к серверу и бекапам.**