Канон документов, каталог задач и OpenSpec
docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью. Конвенции перенесены из jellybit; места, где код им не следует, помечены строкой «Расхождение» как объявленный долг. tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и многопользовательский режим), два направления (все форматы, долгие записи) и пять задач в беклоге. openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет. CLAUDE.md переписан по форме канона: инварианты с severity, семантика гейта, запреты с путями. Taskfile получил task gate.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# Модель угроз
|
||||
|
||||
## Периметр
|
||||
|
||||
**Сервис открыт наружу: HTTP-порт опубликован в интернет через обратный прокси, и
|
||||
аутентификации не делает ни прокси, ни само приложение.** Находки строятся против
|
||||
этого — сегодняшнего — периметра.
|
||||
|
||||
Целевой периметр: те же порты наружу, но вход через OIDC у Authelia, и каждый
|
||||
пользователь видит только свои записи. Он **не** развёрнут; описанное ниже
|
||||
разграничение доступа относится только к Telegram.
|
||||
|
||||
Отсюда главное следствие, из которого читается всё остальное: **`POST /api/audio`
|
||||
доступен кому угодно из интернета**. Отправитель не назван, не ограничен по числу
|
||||
запросов и не ограничен по размеру файла.
|
||||
|
||||
## Недоверенный вход
|
||||
|
||||
Что приходит извне и каким каналом.
|
||||
|
||||
| Вход | Канал | Кто может слать |
|
||||
| --- | --- | --- |
|
||||
| Аудиофайл и его имя | `POST /api/audio`, multipart-поле `audio` | Любой из интернета |
|
||||
| Идентификатор задачи | `GET /api/status/:id` | Любой из интернета |
|
||||
| Голосовое, аудио, документ | Telegram, длинный опрос | Любой пользователь Telegram; обрабатывается только из белого списка |
|
||||
| Имя файла в Telegram | Поле `file_path` ответа Bot API | Telegram, а через него — отправитель |
|
||||
| Содержимое аудио | Файл, скармливаемый `ffmpeg` и `ffprobe` | Отправитель по любому из каналов |
|
||||
| Текст расшифровки | Поток gRPC от SpeechKit | Yandex, а через него — содержимое записи |
|
||||
|
||||
## Из чего строятся пути и ключи
|
||||
|
||||
Раскладка файлов на диске, состав пути к файлу и ключа объекта, имя каталога.
|
||||
Отсюда возможен выход за пределы каталога хранения — запись файла туда, куда
|
||||
путь не предполагался.
|
||||
|
||||
- **Путь на диске** — `filepath.Join(cfg.Storage.Path, fileId + ext)`, где
|
||||
`fileId` наш UUID, а **`ext` берётся из имени файла отправителя** через
|
||||
`filepath.Ext`. Расширение в путь попадает без проверки списком; `filepath.Ext`
|
||||
режет по последней точке и не пропускает разделитель каталогов, но это
|
||||
единственное, что стоит между входом и именем файла.
|
||||
- **Ключ объекта в Object Storage** — то же имя файла, то есть UUID с
|
||||
расширением. Бакет один на все записи, префикса по пользователю нет.
|
||||
- **Каталог** один и плоский: `data/files` целиком, вложенности нет.
|
||||
- **Идентификатор задачи** — UUID v4. Он же единственное, что защищает
|
||||
`GET /api/status/:id`.
|
||||
|
||||
## Что разграничивает доступ
|
||||
|
||||
- **Telegram** — белый список `[server] users_while_list`. Сверяется со строкой
|
||||
автора сообщения (`update.Message.From.String()`, то есть `@username` либо имя
|
||||
с фамилией), а не с числовым идентификатором. Имя пользователя Telegram
|
||||
меняется владельцем в любой момент: список привязан к изменяемому значению.
|
||||
- **HTTP API** — ничего. Ни ключа, ни сессии, ни ограничения по адресу.
|
||||
- **Метрики и здоровье** — `GET /metrics` и `GET /health` открыты вместе с
|
||||
остальным.
|
||||
|
||||
Владения записью в модели данных нет: у задачи нет пользователя. Пока API
|
||||
анонимен, знание UUID задачи и есть право её читать.
|
||||
|
||||
## Что чувствительнее чего
|
||||
|
||||
1. **Содержимое записей и расшифровок.** Голосовые сообщения — личная переписка;
|
||||
это самое чувствительное, что здесь есть.
|
||||
2. **Токен бота Telegram.** Даёт полный доступ к боту и к перепискам с ним.
|
||||
3. **Ключи Yandex Cloud** — `speech_kit_api_key` и пара ключей Object Storage.
|
||||
Утечка оплачивается деньгами и доступом к бакету.
|
||||
4. **Белый список пользователей** — сам по себе перечень имён.
|
||||
|
||||
Всё перечисленное лежит в `config.toml`. Файл в `.gitignore`, на сервер его
|
||||
кладёт Ansible; `gitleaks` на pre-commit смотрит только индекс коммита.
|
||||
|
||||
Тексты расшифровок и имена файлов в логи не пишутся — логируется длина текста и
|
||||
идентификаторы. Токен бота попадает в URL скачивания файла (`file.Link(token)`),
|
||||
и этот URL нигде не логируется.
|
||||
|
||||
## Что вне модели
|
||||
|
||||
Перечислить явно.
|
||||
|
||||
- **Атака на сам сервер и на контур.** Компрометация хоста, прокси, Docker и
|
||||
Ansible — не наша граница.
|
||||
- **Злоупотребление со стороны пользователя из белого списка.** Приглашённому
|
||||
доверяем полностью.
|
||||
- **Достоверность расшифровки.** Подмена или искажение текста на стороне
|
||||
SpeechKit не рассматривается.
|
||||
- **Стойкость к целенаправленной нагрузке.** Ограничения по числу запросов и по
|
||||
размеру файла нет, и защищаться от исчерпания диска мы сейчас не пытаемся.
|
||||
- **Стойкость `ffmpeg` к вредоносному входу.** Разбор чужого формата отдан
|
||||
внешней программе, своей песочницы вокруг неё нет.
|
||||
- **Удаление данных по требованию.** Ни файлы, ни расшифровки не удаляются
|
||||
вовсе; забвение не реализовано и в задачах не стоит.
|
||||
Reference in New Issue
Block a user