- Раскладка 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.
109 lines
9.5 KiB
Markdown
109 lines
9.5 KiB
Markdown
# Модель угроз
|
|
|
|
## Периметр
|
|
|
|
**Контур доверенный: домашняя 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 и
|
|
метабазы; это принято сознательно, прокси в конфиге есть.
|
|
- **Физический доступ к серверу и бекапам.**
|