Files
transcriber/docs/security.md
T
av 4d1c2bf44c Канон документов, каталог задач и OpenSpec
docs/ по канону 12: паспорт с целью проекта, архитектура сегодняшнего
устройства, схема хранилища, модель угроз, конвенции кода, журнал ревью.
Конвенции перенесены из jellybit; места, где код им не следует, помечены
строкой «Расхождение» как объявленный долг.

tasks/ с роадмапом: две достигнутые цели, две запланированные (веб и
многопользовательский режим), два направления (все форматы, долгие
записи) и пять задач в беклоге.

openspec/config.yaml — маршрутизатор с адресами документов, спек пока нет.

CLAUDE.md переписан по форме канона: инварианты с severity, семантика
гейта, запреты с путями. Taskfile получил task gate.
2026-08-10 21:19:07 +03:00

7.0 KiB
Raw Blame History

Модель угроз

Периметр

Сервис открыт наружу: 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 Cloudspeech_kit_api_key и пара ключей Object Storage. Утечка оплачивается деньгами и доступом к бакету.
  4. Белый список пользователей — сам по себе перечень имён.

Всё перечисленное лежит в config.toml. Файл в .gitignore, на сервер его кладёт Ansible; gitleaks на pre-commit смотрит только индекс коммита.

Тексты расшифровок и имена файлов в логи не пишутся — логируется длина текста и идентификаторы. Токен бота попадает в URL скачивания файла (file.Link(token)), и этот URL нигде не логируется.

Что вне модели

Перечислить явно.

  • Атака на сам сервер и на контур. Компрометация хоста, прокси, Docker и Ansible — не наша граница.
  • Злоупотребление со стороны пользователя из белого списка. Приглашённому доверяем полностью.
  • Достоверность расшифровки. Подмена или искажение текста на стороне SpeechKit не рассматривается.
  • Стойкость к целенаправленной нагрузке. Ограничения по числу запросов и по размеру файла нет, и защищаться от исчерпания диска мы сейчас не пытаемся.
  • Стойкость ffmpeg к вредоносному входу. Разбор чужого формата отдан внешней программе, своей песочницы вокруг неё нет.
  • Удаление данных по требованию. Ни файлы, ни расшифровки не удаляются вовсе; забвение не реализовано и в задачах не стоит.