# 🐞 Судить многозначность 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`). Два шага: 1. Запрос с `Remote-User: squatter` и `Remote-Email: victim@corp.example` с доверенного адреса → `200`, `SELECT email FROM users WHERE provider_login='squatter'` → `victim@corp.example`. 2. Запрос настоящего владельца `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`. Отдельной записи не заводится — решение владельца.