- пять задач партии review-2026-08-23: многозначность Remote-Name и Remote-Email, мелочи входа, механизация правила о зависимостях конфига, снятие мёртвого читателя .env, управляющие знаки в расширении - две существующие разведки дополнены находками того же прогона: адрес объекта в тексте отказа SpeechKit и выводимость имени копии из журнала
5.7 KiB
🐞 Судить многозначность Remote-Name и Remote-Email тем же правилом, что и логин
- Тип: fix
- Категория: Очередь — Правило узнавания правится там же, где личность связывается с учётной записью: обе задачи трогают один сбор contract.Identity
- Зачем: чужой адрес, занятый первым пришедшим, навсегда и молча оставляет без почты настоящего владельца: EnsureUser найденную запись не переписывает, ветвь отката заводит владельца без почты и без строки журнала
- Теги: review-2026-08-23
Многозначный Remote-User даёт 401 — многозначный Remote-Email даёт 200,
и в базу ложится первое значение. Правило узнавания судит логин и не судит
остальные заголовки личности.
Один и тот же адрес почты, занятый чужим логином, достаётся первому пришедшему
навсегда: EnsureUser найденную запись не переписывает, а ветвь отката
уникальности заводит настоящего владельца без почты — и делает это молча,
без строки журнала.
Нашёл прогон ревью config-test-headers-login 2026-08-23, оракул воспроизведён.
Воспроизведение
Свой падающий сценарий, прогнан на настоящей поверхности сервиса (setupEnv +
AppChain, база SQLite во временном каталоге, маршрут /app/me). Два шага:
- Запрос с
Remote-User: squatterиRemote-Email: victim@corp.exampleс доверенного адреса →200,SELECT email FROM users WHERE provider_login='squatter'→victim@corp.example. - Запрос настоящего владельца
Remote-User: victim,Remote-Email: victim@corp.example→200,SELECT email … WHERE provider_login='victim'→"", строк журнала о занятом адресе ноль.
Ожидалось: многозначный заголовок судится тем же правилом, что и логин, а откат по занятому адресу виден владельцу сервиса строкой журнала.
Затрагивает
internal/controller/http/identity.go:134-138— сборcontract.Identityиз заголовков:Loginберётся из проверенного перечня значений,NameиEmail— черезHeader.Get, то есть первым значением без суждения;internal/adapter/repo/sqlite/identity.go:77-110— ветвь отката уникальности почты: заводит запись с пустым адресом и не пишет ни строки;- требование спеки
access: сегодня оно нормирует многозначность только дляRemote-User; docs/security.md, раздел «Периметр» — что именно сервис принимает от контура на веру.
Критерии приёмки
- Многозначный
Remote-Emailдаёт пустое значение, а не первое. Оракул: тест на настоящей поверхности сервиса — запрос с двумя значениямиRemote-Emailзаводит учётную запись с пустым адресом; тот же тест наRemote-Name. - Узнавание принимает однозначные
Remote-NameиRemote-Emailпо-прежнему. Оракул: тот же тест — одно значение доезжает до колонки без изменений. - Ветвь отката уникальности почты оставляет строку журнала видимого уровня. Оракул: тест хранилища — второй логин с занятым адресом заводится, и в журнале стоит строка о занятом адресе; значения адреса в ней нет.
Рамки
Правится боевой путь узнавания, а не отладочная подстановка заголовков. Владение адресом почты между учётными записями не пересматривается: занятый адрес по-прежнему достаётся тому, кто завёлся первым, — задача только о том, чтобы это перестало происходить молча и по многозначному заголовку.
Отдельно: требование к контуру про X-Forwarded-For не записано ни в модели
угроз, ни в спеке и этой правкой не покрывается — правило «справа налево до
первого недоверенного» верно постольку, поскольку правое значение приписал
прокси, а шаблон лежит в files/caddyproxy/Caddyfile.template репозитория
pet-project-server. Отдельной записи не заводится — решение владельца.