имя файла отправителя убрано из журнала приёма

- расширение приводится к перечню известных форматов прежде метки метрики:
  страница метрик открыта, и хвост имени уезжал на неё дословно
- проверки приёма перехватывают все три потока журнала и читают реестр метрик,
  каждая падает при снятии того, что сторожит
This commit is contained in:
av
2026-08-11 16:37:58 +03:00
parent ffa36e96c7
commit bd6001cd8d
18 changed files with 1797 additions and 20 deletions
@@ -0,0 +1,45 @@
# ADR-2026-08-11. Наружу расширение выходит только приведённым к перечню
- **Дата:** 2026-08-11
- **Источник:** [openspec/changes/archive/2026-08-11-no-user-filename-in-log/design.md](../../openspec/changes/archive/2026-08-11-no-user-filename-in-log/design.md), раздел `Decisions`
## Решение
Расширение принятой записи приводится к закрытому перечню известных форматов
прежде, чем уйти меткой метрики; всё, чего в перечне нет, заменяется одним общим
значением. Имя файла на диске при этом не трогается — там расширение остаётся
тем, каким пришло.
Дословно из источника:
> Из трёх способов человек выбрал средний. Отвергнуты: оставить как есть и завести
> задачу — канал жил бы до неё, а закрытие этой задачи читалось бы как
> «починено»; приводить расширение везде, включая имя файла на диске, —
> раскладка каталога записей объявлена необратимой и меняется решением человека,
> а не по ходу починки журнала.
## Почему
Расширение берётся из имени, которое дал отправитель, дословно: имя
`запись.тайное-слово` отдаёт `тайное-слово`, а `Разговор с Петровым 11.08`
`08`. Оно уходило меткой метрики, а страница метрик отдаётся без проверки
отправителя. Канал оказался шире того, ради которого задача заводилась: журнал
читает владелец сервиса, метки — кто угодно, и то же значение оседает в
хранилище метрик. Тем же каналом множество значений метки становится
неограниченным: их задаёт анонимный отправитель.
Отказ от нормализации на диске — не экономия, а граница обратимости: формат
имени файла и раскладка `data/files` объявлены необратимыми, и меняются они
решением человека под свою задачу, а не попутно с починкой журнала.
## Цена
- Перечень форматов стал нормой и требует ведения: формат, который сервис
начнёт принимать, до внесения в перечень будет виден в метрике как общее
значение, неотличимо от чужого хвоста.
- Форма метки размера принятой записи изменилась — ведущая точка пропала
(`.mp3` стало `mp3`). Ряды, собранные до выкладки, перестают пополняться.
- Настоящий формат записи, попавшей в общее значение, остаётся видимым только в
журнале — по полю пути строки приёма и полю формата строки конвертации.
- Хвост расширения по-прежнему уходит в журнал внутри пути файла. Это остаток,
он записан в [../security.md](../security.md), и своей задачи у него пока нет.
+1
View File
@@ -32,6 +32,7 @@
| Дата | Запись | Статус |
| --- | --- | --- |
| 2026-08-11 | [Наружу расширение выходит только приведённым к перечню](ADR-2026-08-11-known-format-label.md) | |
| 2026-08-11 | [Приложение пишем на Vue, а Node входит в гейт и в образ](ADR-2026-08-11-spa-on-vue.md) | |
| 2026-08-11 | [Очередь остаётся своей таблицей, но коллекцией PocketBase](ADR-2026-08-11-queue-as-pocketbase-collection.md) | |
| 2026-08-11 | [Хранилище, файлы и вход переезжают в PocketBase](ADR-2026-08-11-pocketbase-storage-with-admin-panel.md) | |
+1
View File
@@ -104,6 +104,7 @@
| Завершение и отказ | `TranscribeService.completeJob` и `failJob` — они же отвечают пользователю |
| Разбор конфигурации | `internal/config.LoadConfig` |
| Метрики | `internal/metrics`, префикс имени `transcriber_` |
| Значения метки формата | `internal/metrics.FormatLabel` — приводит расширение к закрытому перечню, прочее заменяет на `other`; нормирует спека `intake` |
Единых точек, которых **нет** и которые ожидались бы: идентификаторы
генерируются вызовом `uuid.NewString()` по месту, время — вызовом `time.Now()`
+10 -1
View File
@@ -213,7 +213,10 @@ Object Storage, скачивание файла из Telegram и опрос оп
- ключ SpeechKit и заголовок `Authorization`;
- пара ключей Object Storage;
- **сам текст расшифровки и имена файлов пользователя** — это содержимое личной
переписки. Логируем длину текста, а не текст.
переписки. Логируем длину текста, а не текст. Имя файла ничем не заменяем:
ни укороченным именем, ни отпечатком от него — отпечаток та же приватная
величина, а корреляцию держат идентификаторы сущностей. Чем при этом
прослеживается приём, нормирует спека `intake`, а не эта запись.
Дополнительно:
@@ -233,6 +236,12 @@ Object Storage, скачивание файла из Telegram и опрос оп
она не логируется — то есть утечки нет, но защищает от неё только отсутствие
строки лога.
*Расхождение:* расширение берётся из имени отправителя дословно
(`filepath.Ext`), поэтому имя `запись.тайное-слово` отдаёт приватный хвост
расширением, и в журнал оно попадает полем пути. Наружу — в метку метрики — этот
хвост не выходит: там расширение приводится к перечню известных форматов. Остаток
описан в [../security.md](../security.md).
## Куда пишем и уровень
- Пишем JSON в `stdout` одним потоком; сбор и ротацию делает окружение. Не
+23
View File
@@ -103,6 +103,9 @@
- `security`: не строится ли путь на диске или ключ объекта из значения,
пришедшего снаружи, — расширение файла сегодня берётся из имени отправителя
(чтение `service/transcribe.go`, 2026-08-10).
- `security`: не уходит ли значение, пришедшее снаружи, меткой метрики — страница
метрик отдаётся без проверки отправителя, и метка это поверхность пошире
журнала (журнал, запись 2026-08-11 про хвост имени).
- `architecture`: не появился ли второй путь приёма мимо
`createTranscribeJob` — сегодня через него идут оба входа
([architecture.md](architecture.md), «Единые точки проекта»).
@@ -178,6 +181,26 @@ API и имя не откатываются обратной правкой по
поймать их было некому. У восстановленных нет поля «Чем воспроизведён», и
выдумывать его задним числом нельзя.
## 2026-08-11 — хвост имени отправителя уезжал на открытую страницу метрик [пойман ревью]
- **Где:** `internal/service/transcribe.go`, метки `file_extension` у размера
принятой записи и `source_format` у длительности конвертации
- **Симптом:** имя `запись.тайное-слово` клало `тайное-слово` меткой метрики, а
`GET /metrics` отдаётся без проверки отправителя. Тем же каналом множеством значений
метки распоряжался анонимный отправитель
- **Причина:** расширение берётся из имени отправителя дословно (`filepath.Ext`)
и употреблялось меткой без приведения. Канал старше задачи, которая его нашла
- **Чем воспроизведён:** прогон `filepath.Ext` на именах вида
`запись.тайное-слово`, `Разговор с Петровым 11.08`, затем чтение реестра
метрик после приёма — метка несла хвост дословно
- **Почему не поймали:** метрику никто не считал выходом приватного значения.
Тема `security` смотрела журнал, ответ и пути на диске; вопроса про метку в
перечне вопросов не было, и ни один проход её не открывал. Поймали три прохода
разом на задаче, которая закрывала соседний канал
- **Что меняем:** вопрос про метку добавлен в «Вопросы по темам»; правило
приведения нормировано спекой `intake` и записано
[решением](adr/ADR-2026-08-11-known-format-label.md)
## 2026-08-11 — проверка приёма не могла упасть [пойман ревью]
- **Где:** `internal/controller/http/transcribe_test.go`, случай успеха приёма
+28 -6
View File
@@ -186,12 +186,34 @@ Telegram отправителю.
который лежит **не в конфигурации**: его отпечаток хранит сама база.
Тексты расшифровок в логи не пишутся — логируется длина текста и
идентификаторы. **Имя файла, данное отправителем, пишется**: строка
`internal/service/transcribe.go:107` кладёт `file_name` на общем шаге заведения
задачи, то есть для обоих входов. Это нарушение инварианта приватности из
`CLAUDE.md`, оно объявлено критическим, и чинит его задача
`no-user-filename-in-log`; её место в очереди —
[tasks/BACKLOG.md](../tasks/BACKLOG.md).
идентификаторы. Имя файла, данное отправителем, из журнала приёма убрано
2026-08-11 задачей `no-user-filename-in-log`; запрет проверяют тесты приёма по HTTP на
успешном пути и на пути отказа — они ищут значение, а не имя поля.
**Остаток: хвост после последней точки остаётся в журнале.** Расширение берётся
из имени отправителя дословно (`filepath.Ext`), поэтому имя
`запись.тайное-слово` отдаёт `тайное-слово`, а `Разговор с Петровым 11.08`
`08`. Оно стоит в собственном имени файла на диске, а путь к файлу логируется.
Читает этот журнал владелец сервиса. Нормализация расширения на диске — отдельная
работа, задачи на неё пока нет: формат имени файла объявлен необратимым и меняется
решением человека.
**Наружу хвост не выходит.** Метки метрик (`file_extension` у
`transcriber_input_file_size_bytes`, `source_format` у
`transcriber_conversion_duration_seconds`) несут расширение, только приведённое к
закрытому перечню известных форматов; всё прочее заменяется значением `other`.
Это закрыто задачей `no-user-filename-in-log` 2026-08-11 вместе с самим именем.
Заодно у метки размера принятой записи пропала ведущая точка (`.mp3` стало
`mp3`) — форма выровнялась с меткой конвертации, которая точку не носила
никогда. Ряды, собранные до выкладки, перестают пополняться: панель, отобранная
по старому значению, покажет пустоту, и это не поломка.
Требование важно тем, что `GET /metrics` открыт вместе с остальным: без
приведения хвост читал бы кто угодно из интернета, а множеством значений метки
распоряжался бы анонимный отправитель.
Приём из Telegram имени, данного человеком, до сервиса не доводит: оттуда
приходит путь, выданный самим Telegram. Настоящее имя документа дальше проверки
типа файла не идёт.
Токен бота попадает в URL скачивания файла (`file.Link(token)`), и этот URL
нигде не логируется.