Files
av 42d5b73a04 docs: перевод документации на канон av-dev
- Раскладка 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.
2026-08-04 09:27:26 +03:00

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 и
метабазы; это принято сознательно, прокси в конфиге есть.
- **Физический доступ к серверу и бекапам.**