имя файла отправителя убрано из журнала приёма
- расширение приводится к перечню известных форматов прежде метки метрики: страница метрик открыта, и хвост имени уезжал на неё дословно - проверки приёма перехватывают все три потока журнала и читают реестр метрик, каждая падает при снятии того, что сторожит
This commit is contained in:
@@ -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), и своей задачи у него пока нет.
|
||||
@@ -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) | |
|
||||
|
||||
@@ -104,6 +104,7 @@
|
||||
| Завершение и отказ | `TranscribeService.completeJob` и `failJob` — они же отвечают пользователю |
|
||||
| Разбор конфигурации | `internal/config.LoadConfig` |
|
||||
| Метрики | `internal/metrics`, префикс имени `transcriber_` |
|
||||
| Значения метки формата | `internal/metrics.FormatLabel` — приводит расширение к закрытому перечню, прочее заменяет на `other`; нормирует спека `intake` |
|
||||
|
||||
Единых точек, которых **нет** и которые ожидались бы: идентификаторы
|
||||
генерируются вызовом `uuid.NewString()` по месту, время — вызовом `time.Now()`
|
||||
|
||||
@@ -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` одним потоком; сбор и ротацию делает окружение. Не
|
||||
|
||||
@@ -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
@@ -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
|
||||
нигде не логируется.
|
||||
|
||||
Reference in New Issue
Block a user