- Раскладка docs/ приведена к канону 2: заведены passport/architecture/ database/security/review и research; docs/specs, drafts, backlog, review/ и BRIEF.md разобраны и удалены, беклог переехал в docs/tasks (34 задачи, 6 целей, слаги на английский). - Нарративы specs удалены как дубли openspec-спек после поимённой сверки; остаток заведён задачами (редактор маппинга ревью, крайние случаи именования), отказ от сущности title промоутнут в ADR. - Проектные копии агентов и скиллов ревью/пайплайна удалены в пользу плагинов av-dev-pm и av-dev-pipeline; в task gate добавлен шаг canon вместо er-schema.
9.5 KiB
Модель угроз
Периметр
Контур доверенный: домашняя 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.
Что чувствительнее чего
- Медиафайлы в раздаче — единственное, что невосстановимо. Отсюда
инвариант «источник неприкосновенен» и гард последней копии в Undo
(
nlink <= 1→ отказ целиком). - База
/data/jellybit.db— восстановима только перезапуском всей работы: теряется всё in-flight состояние и история привязок. - Секреты: пароль qBittorrent, ключи LLM и метабаз, токен Telegram,
API-ключ Jellyfin. Живут только в
config.toml(0600, рендерит деплой) и никогда не попадают в логи, в диагностику состояния и в ответы API — правило в conventions/logging.md. - Библиотечные хардлинки — восстановимы повторной раскладкой, поэтому ниже по шкале, хотя видны пользователю первыми.
Что вне модели
Перечислено явно: против этого находки не строятся.
- Злонамеренный участник LAN. Сеть считается доверенной; «сосед по вайфаю удалил загрузку через веб-UI» — не дефект в сегодняшнем периметре.
- Злонамеренный оператор. Владелец может всё по определению, включая удаление раздачи вместе с файлами.
- Компрометация соседних сервисов — qBittorrent, Jellyfin, LLM-эндпоинта, хоста umbar. Если qBittorrent врёт про пути, мы проиграли раньше.
- Отказ в обслуживании изнутри контура. Огромная раздача, тысяча файлов, бесконечный ответ LLM — это вопросы устойчивости и ресурсов (architecture.md → «Эксплуатация»), а не безопасности. Отсутствие лимита на размер ответа LLM — известный пробел, задача в беклоге.
- Целостность содержимого медиафайлов. Что в контейнере mkv — не наша забота.
- Цепочка поставки — модули Go, базовый образ distroless, плагины тулинга.
- Приватность запросов к внешним сервисам. Названия раздач уезжают в LLM и метабазы; это принято сознательно, прокси в конфиге есть.
- Физический доступ к серверу и бекапам.