docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью. Конвенции перенесены из jellybit; места, где код им не следует, помечены строкой «Расхождение» как объявленный долг. tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и многопользовательский режим), два направления (все форматы, долгие записи) и пять задач в беклоге. openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет. CLAUDE.md переписан по форме канона: инварианты с severity, семантика гейта, запреты с путями. Taskfile получил task gate.
7.0 KiB
Модель угроз
Периметр
Сервис открыт наружу: 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 задачи и есть право её читать.
Что чувствительнее чего
- Содержимое записей и расшифровок. Голосовые сообщения — личная переписка; это самое чувствительное, что здесь есть.
- Токен бота Telegram. Даёт полный доступ к боту и к перепискам с ним.
- Ключи Yandex Cloud —
speech_kit_api_keyи пара ключей Object Storage. Утечка оплачивается деньгами и доступом к бакету. - Белый список пользователей — сам по себе перечень имён.
Всё перечисленное лежит в config.toml. Файл в .gitignore, на сервер его
кладёт Ansible; gitleaks на pre-commit смотрит только индекс коммита.
Тексты расшифровок и имена файлов в логи не пишутся — логируется длина текста и
идентификаторы. Токен бота попадает в URL скачивания файла (file.Link(token)),
и этот URL нигде не логируется.
Что вне модели
Перечислить явно.
- Атака на сам сервер и на контур. Компрометация хоста, прокси, Docker и Ansible — не наша граница.
- Злоупотребление со стороны пользователя из белого списка. Приглашённому доверяем полностью.
- Достоверность расшифровки. Подмена или искажение текста на стороне SpeechKit не рассматривается.
- Стойкость к целенаправленной нагрузке. Ограничения по числу запросов и по размеру файла нет, и защищаться от исчерпания диска мы сейчас не пытаемся.
- Стойкость
ffmpegк вредоносному входу. Разбор чужого формата отдан внешней программе, своей песочницы вокруг неё нет. - Удаление данных по требованию. Ни файлы, ни расшифровки не удаляются вовсе; забвение не реализовано и в задачах не стоит.