# Имена пользователей Basic-аутентификации - это чувствительные метаданные

Имена пользователей Basic-аутентификации - это чувствительные метаданные.

Это утверждение звучит слишком педантично, пока вам не приходится разбираться с последствиями автоматического запуска, который скопировал `billing-export@north-division` в историю командной оболочки, журнал CI, промпт агента и тикет об инциденте. Пароля может не быть ни в одном из этих четырёх мест. Но злоумышленник всё равно узнает, что существует арендатор north-division, у него есть интеграция для экспорта биллинга, а учётная запись, вероятно, обращается к определённому старому API.

Команды часто делят данные на секреты и всё остальное. Для работы агентов с API этого недостаточно. Имя пользователя, идентификатор арендатора, имя хоста, путь API и код ответа по отдельности могут выглядеть безобидно. Вместе они описывают структуру учётных записей, которая представляет интерес для атаки. Считайте такие сочетания чувствительными метаданными, особенно когда агент может копировать контекст намного дальше и быстрее, чем человек, вводящий один запрос.

## Имя пользователя может раскрыть устройство системы учётных записей

Basic-имя пользователя часто содержит больше смысла, чем предполагает название поля. В старых системах оно может быть логином, кодом клиента, обозначением подразделения, сервисной ролью, маркером окружения или составным идентификатором, который придумали до появления более удачной модели идентификации.

Посмотрите на такие значения:

```text
acme-east:password
svc-payroll-prod:password
tenant-48291-export:password
j.smith@customer.example:password
```

Первое значение сообщает о клиенте и региональном разделении. Второе называет внутреннюю возможность и окружение. Третье раскрывает идентификатор арендатора и назначение. Четвёртое показывает человека и его связь с клиентом. Смена пароля исправляет только часть с паролем. Скопированная карта учётной записи никуда не исчезает.

Это важно, потому что злоумышленники не всегда начинают атаку с учётных данных. Сначала они составляют список целей. Узнаваемое имя пользователя помогает подготовить убедительный запрос на сброс пароля, угадать связанные учётные записи, искать записи в утечках, проверять маршрут конкретного клиента или давить на службу поддержки деталями, которые звучат как внутренние.

Не считайте это чисто теоретической проблемой только потому, что имя пользователя действительно лишь вместе с паролем. Знание о существовании `svc-orders-import-prod` не равнозначно контролю над этой учётной записью, но оно гораздо полезнее полного незнания. Работа с безопасностью становится дороже, когда команды ждут, пока поле превратится в полноценные учётные данные, прежде чем начать его защищать.

Правильная классификация зависит от контекста. Общему публичному имени пользователя, которое используют все учётные записи песочницы, может требоваться минимальная защита. Сервисная учётная запись, привязанная к конкретному арендатору и внутренней конечной точке, требует гораздо более строгого обращения. Зафиксируйте это различие для каждой интеграции, а не применяйте общий ярлык уже после утечки.

## Basic-аутентификация прикрепляет имя учётной записи к каждому вызову

Basic-аутентификация отправляет значение `user-id:password`, закодированное в Base64, в HTTP-заголовке `Authorization`. Base64 меняет представление данных. Он не скрывает исходные байты от того, кто может прочитать заголовок.

Типичный запрос выглядит так:

```http
GET /v1/exports/monthly HTTP/1.1
Host: api.legacy.example
Authorization: Basic c3ZjLWJpbGxpbmctZXhwb3J0LXByb2Q6cmVkYWN0ZWQ=
Accept: application/json
```

Закодированный текст содержит обе части при каждом вызове API. Именно это повторение команды часто забывают. Bearer-токен тоже может раскрывать контекст учётной записи, но многие Basic-интеграции используют понятные имена пользователей, которые после декодирования прямо говорят о назначении.

RFC 7617 определяет эту схему и содержит два важных для нас момента. Первый двоеточие отделяет user-id от пароля, поэтому двоеточие в имени пользователя недопустимо. Управляющие символы также запрещены. Из-за этого старый поставщик может принимать визуально удобное соглашение об именах, которое на деле не проходит через соответствующий стандарту Basic-заголовок. Не придумывайте собственное правило экранирования в надежде, что все библиотеки будут трактовать его одинаково.

Второй момент связан с обработкой символов. RFC 7617 разрешает серверу указать UTF-8 в challenge, но этот сигнал носит рекомендательный характер, а многие старые системы непоследовательно обрабатывают не-ASCII-идентификаторы. Если имя учётной записи содержит диакритические знаки, правила регистра или преобразованный Unicode, протестируйте точную пару клиента и сервера до выхода в продакшен. Несовпадение может привести к ошибке аутентификации, которую кто-то попытается «исправить», выведя все учётные данные в отладочный журнал.

Basic-аутентификация также требует HTTPS. OWASP описывает Basic-учётные данные как закодированные, а не зашифрованные, и рекомендует использовать TLS везде, где применяется Basic. TLS защищает соединение при передаче. Но он не защищает заголовок после того, как клиентская библиотека, обратный прокси, агент трассировки, обработчик ошибок или инструмент отладки записал его.

## Идентификаторы арендаторов и конечные точки опасны в сочетании

Сам по себе идентификатор арендатора может быть непрозрачным числом. Отдельная конечная точка может быть обычным маршрутом. Вместе они способны указать бизнес-функцию клиента и систему, которая её обслуживает.

Представим, что агент получает такую инструкцию:

```text
For tenant 48291, call https://ledger.internal.example/v2/reconciliation/import
with username tenant-48291-ledger-import.
```

Даже если другой компонент подставляет пароль, инструкция сообщает агенту арендатора, хост, операцию и сервисную учётную запись. Агент может процитировать её в собственных рабочих заметках. Обёртка инструмента может записать её. Расшифровка работы модели может её сохранить. Разработчик может вставить ошибку в чат. Пароль остался скрытым, но структура учётной записи уже раскрыта.

Утечка становится серьёзнее, когда имена строятся по предсказуемой грамматике. Если одно имя пользователя имеет вид `tenant-48291-ledger-import`, можно предположить существование `tenant-48291-ledger-export`, `tenant-48291-reporting` и таких же ролей у других арендаторов. Предсказуемость удобна операторам и полезна тем, кто перебирает цели. Не обязательно отказываться от всех соглашений об именах, но нужно понимать, когда такое соглашение превращает одно утёкшее имя в запрос к каталогу.

Пары конечных точек тоже несут информацию. `/admin/users`, `/payroll/export`, `/claims/submit` и `/archive/retention` раскрывают разные виды работы даже при нейтральном имени хоста. Хост вместе с путём и арендатором часто даёт достаточно данных, чтобы сделать фишинговое сообщение или попытку выдать себя за сотрудника поддержки убедительными.

Классифицируйте кортежи, а не только отдельные поля:

- сервисная учётная запись плюс идентификатор арендатора
- сервисная учётная запись плюс имя хоста
- идентификатор арендатора плюс путь конечной точки
- путь конечной точки плюс тело ответа или текст ошибки
- время плюс запись об успешном действии

Это более точное правило, чем «скрывайте пароли». Оно объясняет проверяющим, почему строка журнала без буквального секрета всё равно может быть небезопасна для отправки в широкую систему наблюдаемости.

## Агенты копируют контекст там, где обычные клиенты этого не делают

Обычный API-клиент получает URL, имя учётной записи и пароль через узкий путь конфигурации. AI-агент работает с инструкциями как с текстом. Он может изучить репозиторий, прочитать тикет, выполнить команду, разобрать ошибку и написать сводку. При каждой передаче могут сохраняться идентификаторы, которые традиционному клиенту никогда не пришлось бы показывать.

Проблема не в том, что агенты по своей природе особенно неосторожны. Дело в том, что агентские процессы специально делают контекст переносимым. Та же возможность, которая позволяет агенту рассуждать одновременно по заметке о развёртывании и ответу API, позволяет ему переносить имена арендаторов и шаблоны конечных точек в аргументы инструментов, расшифровки или создаваемые исправления.

Типичная ошибка выглядит так:

1. Разработчик помещает Basic-имя пользователя и код арендатора в локальный файл `.env`, потому что пароль поступает из хранилища секретов.
2. Агент читает файл, чтобы разобраться с неисправной интеграцией.
3. API возвращает ответ 401 с подробной ошибкой, в которой повторяются имя пользователя и арендатор.
4. Агент создаёт отчёт по устранению проблемы с фрагментом конфигурации и ошибкой.
5. Разработчик вставляет этот отчёт в задачу, доступную более широкой группе.

Никто не собирался публиковать учётные данные. И всё же в задаче теперь записаны рабочая схема именования, арендатор, хост, маршрут, сервисная роль и временной промежуток активности интеграции. Этого контекста достаточно для новых рисков.

Не решайте проблему запретом агентам видеть любую строку, которая не считается секретом. Это блокирует полезную работу и обычно не работает на практике. Вместо этого определите, какие факты нужны для каждого действия. Агенту, который должен запросить ежемесячный экспорт, могут потребоваться абстрактное имя действия и месяц. Ему не нужны Basic-имя пользователя, пароль, правило маршрутизации арендаторов или исходный целевой URL.

Так вопрос меняется с «Может ли агент пройти аутентификацию?» на «Какое минимальное описание действия нужно агенту, чтобы создать нужный запрос?». Именно этот вопрос помогает не помещать структуру учётных записей в контекстное окно агента.

## Держите идентификационные данные вне промптов и репозиториев

Промпт - плохое место для хранения конфигурации. То же относится к исходным файлам, примерам curl, шаблонам задач, псевдонимам оболочки и тестовым фикстурам. Все они распространяются дальше, чем ожидает автор.

Сначала разделите три вещи, которые команды часто смешивают:

1. **Материалы учётных данных** - имя пользователя и пароль для аутентификации.
2. **Метаданные маршрутизации** - сведения о том, куда направлять запрос, например хост, сегмент арендатора или семейство конечных точек.
3. **Намерение действия** - бизнес-операция, например «скачать файл сверки за март».

Человек или агент часто может сообщить намерение действия, не видя две другие категории. Это предпочтительная граница. Если старый API требует маршрутизации через хост или имя пользователя конкретного арендатора, храните соответствие в компоненте, который выполняет запрос, а не в промпте, где запрашивается действие.

Не добавляйте в репозиторий такие примеры `.env`:

```bash
LEGACY_API_URL=https://tenant-48291.api.legacy.example/v2/payroll/export
LEGACY_API_USER=tenant-48291-payroll-export
LEGACY_API_PASSWORD=replace-me
```

Заглушка `replace-me` не делает пример безопасным. URL и имя пользователя всё ещё документируют схему интеграции конкретного клиента. Скопированный пример также может позже превратиться в рабочую конфигурацию, обычно под давлением сроков.

Вместо этого используйте абстрактный локальный контракт:

```yaml
actions:
  export_monthly_payroll:
    account_ref: payroll-export-production
    target_ref: payroll-export-api
    inputs:
      - tenant_alias
      - month
```

Метки `account_ref` и `target_ref` должны быть достаточно непрозрачными, чтобы читатель репозитория не мог вывести клиента, хост или сервисную роль. Исполнитель разрешает их локально. Агент получает `tenant_alias` только при реальной необходимости, и этот псевдоним не должен быть идентификатором рабочего арендатора, если задачу можно решить локальным соответствием.

Такой подход также предотвращает распространённую ошибку миграции: пароль переносят в менеджер секретов, но имя пользователя, целевой URL и код клиента оставляют жёстко заданными в приложении. Это лучше, чем коммитить пароль, но структура всё равно открыта каждому разработчику, журналу сборки и выгрузке сканера кода.

## Используйте псевдонимы, которые не превращают одну утечку в каталог

Непрозрачные имена не обладают магическими свойствами, но уменьшают объём информации при случайном раскрытии. Имя учётной записи должно объяснять назначение небольшой группе администраторов, а не рекламировать связь с клиентом каждой системе, которая его увидит.

Сравните такие сервисные псевдонимы:

```text
bad:  acme-east-payroll-export-prod
better: svc-47f2-export-p1
```

Во втором варианте всё ещё есть понятный префикс сервиса и маркер окружения. Это часто практично. На этом полезные детали заканчиваются. Отдельный защищённый реестр может сопоставлять `svc-47f2-export-p1` с клиентом, владельцем, конечной точкой, разрешениями и записью о ротации.

Не считайте похожие на случайные псевдонимы заменой авторизации. Злоумышленник с паролем всё ещё сможет пройти аутентификацию, а сотрудник с доступом к реестру всё ещё увидит соответствие. Псевдонимы уменьшают ненужное раскрытие. Минимальные права и проверки доступа определяют, что может делать учётная запись.

Иногда формат имени задаёт поставщик. Некоторые старые API требуют номер клиента, адрес электронной почты или составное значение с регионом. Если это так, считайте поле чувствительным и измените окружающий процесс. Не притворяйтесь, что значение публично, только потому, что его нельзя переименовать.

Храните требуемые поставщиком идентификаторы в границе учётных данных, а последующие результаты делайте нейтральными. Исполнитель запроса может вернуть `export accepted` с идентификатором задания. Ему не нужно повторять вызывающему агенту отправленное имя пользователя, полный целевой URL или значение маршрутизации арендатора.

## Журналам нужен аудит, но не каталог учётных записей

Журнал безопасности должен содержать достаточно сведений, чтобы ответить, кто инициировал действие, что произошло, когда это произошло и был ли успех. Ему не нужно сохранять каждый байт, отправленный клиентом. Руководство OWASP по журналированию прямо предупреждает против записи чувствительной информации, такой как пароли, идентификаторы сессий и ненужные системные детали, но одновременно требует фиксировать события аутентификации и контроля доступа.

Противоречие реально. Если убрать всё, операторы не смогут расследовать инцидент. Если хранить необработанные заголовки и полные URL вечно, журналы превратятся в каталог учётных записей с поиском.

Используйте структуру записи, которая отделяет операционную корреляцию от чувствительных метаданных:

```json
{
  "event": "legacy_api_call",
  "action": "export_monthly_payroll",
  "request_id": "req_01J...",
  "actor_run": "run_01J...",
  "credential_ref": "cred_4c91",
  "target_ref": "target_a77e",
  "result": "denied",
  "http_status": 401,
  "reason": "authentication_failed",
  "occurred_at": "2026-07-22T14:03:21Z"
}
```

Эти ссылки позволяют уполномоченным операторам сопоставлять события с защищённым реестром. Они не помещают имя пользователя, идентификатор арендатора, исходную конечную точку или заголовок Authorization в каждую запись. Доступ к реестру должен быть ограничен сильнее, чем обычный доступ к журналам.

Не думайте, что хеш имени пользователя решает проблему. Детерминированный хеш небольшого пространства имён с предсказуемым шаблоном часто можно угадать, а стабильный хеш всё равно позволяет отслеживать одну учётную запись в разных записях. Если нужна корреляция, используйте случайную ссылку на учётные данные, созданную вашей системой. При смене или отключении учётных данных меняйте и эту ссылку.

Будьте осторожны с диагностикой ошибок. Вот такой ответ нельзя передавать дальше:

```text
401 for tenant-48291-payroll-export at /v2/payroll/export: user exists but password rejected
```

Он подтверждает существование учётной записи, арендатора, маршрута и результат проверки. Лучше сохранить точную диагностику поставщика в ограниченной записи поддержки, если она действительно нужна, а в общем журнале активности указать `authentication_failed`. Агент должен получить короткое сообщение с указанием остановиться и запросить помощь, а не новую подсказку для перебора вариантов.

## Редактирование и минимизация решают разные задачи

Редактирование удаляет известное опасное значение после того, как оно уже прошло через систему. Минимизация не даёт значению попасть туда, где оно не нужно. Нужны оба подхода, а их смешение приводит к плохим проектным решениям.

Фильтр заголовков может заменить это:

```http
Authorization: Basic c3ZjLWJpbGxpbmctZXhwb3J0LXByb2Q6cmVkYWN0ZWQ=
```

на это:

```http
Authorization: [REDACTED]
```

Это необходимо. Но фильтр ничего не делает с путём, именем хоста, параметром арендатора в запросе, подробным телом ответа 401, меткой запроса или атрибутами трассировки, записанными рядом с заголовком. Система, которая гордится скрытыми паролями, но сохраняет `tenant-48291.api.legacy.example/payroll/export`, устраняет один риск и оставляет полезную карту учётных записей.

Минимизация задаёт более сложные вопросы ещё до выполнения запроса:

- Нужен ли агенту настоящий целевой хост или достаточно имени действия?
- Нужен ли журналу активности идентификатор арендатора или только защищённая ссылка?
- Нужен ли поддержке исходный ответ поставщика в обычном потоке журналов?
- Нужно ли указывать арендатора в URL, если исполнитель может разрешить его локально?
- Нужна ли человеку, проверяющему одобрение, полная идентичность или понятная, но не раскрывающая деталей метка?

Именно здесь команды часто дают популярный, но ошибочный совет: «Один раз записывайте весь запрос, а потом улучшите фильтры». Так говорят потому, что отлаживать старые API тяжело, а полные захваты быстро отвечают на вопросы. Но затем эти данные навсегда попадают в резервные копии, хранилища аналитики, тестовые окружения и скопированные заметки об инцидентах. Вместо этого создайте ограниченный диагностический путь для короткого расследования. Полный захват не должен быть обычным режимом работы.

## Старым API нужна граница изоляции, а не слепое доверие

Возможно, вы не сможете заменить Basic-аутентификацию в этом квартале. Поставщик может поддерживать только один вариант интеграции. У складского устройства может быть API, замороженный много лет назад. Практический ответ - изоляция, а не декларация о том, что старый API якобы безопасен.

Поместите Basic-учётные данные и соответствия идентификаторов за компонент, который сам выполняет HTTP-запрос. Агент должен запрашивать именованное действие со структурированными входными данными. Компонент запроса выбирает цель, получает учётные данные, подставляет их в заголовок, проверяет нужную область действия и возвращает ограниченный результат.

Полезный контракт действия агента может выглядеть так:

```json
{
  "action": "export_monthly_payroll",
  "tenant_alias": "tenant_ref_91ab",
  "month": "2026-06"
}
```

Исполнитель действия может проверить месяц, разрешить `tenant_ref_91ab` через локальное защищённое соответствие и выполнить вызов поставщика. Он должен отклонять дополнительные поля вроде `url`, `authorization`, `username` и `headers`. Если вызывающие стороны могут переопределить эти поля, они смогут направить доверенные учётные данные на произвольный хост или превратить узкоспециализированное действие обратно в клиент необработанных HTTP-запросов.

Это ограничение важно не только из-за раскрытия учётных данных, но и из-за ошибок типа SSRF. Пользовательский URL не является безобидным удобством, если исполнитель хранит учётные данные. Цель должна поступать из контролируемого определения, а перенаправления требуют такой же осторожности. Не переходите на новый источник, сохраняя заголовок Authorization.

Эта граница должна также управлять повторами. Плохо спроектированный агент может повторить запрос 401 с изменёнными входными данными или многократно вызвать операцию после тайм-аута. Исполнитель должен отличать безопасный повтор передачи от ошибки аутентификации или неизвестного результата операции записи. Для неидемпотентной старой конечной точки лучше вернуть статус, требующий проверки человеком, чем повторно отправлять тот же запрос только потому, что агент уверенно попросил об этом.

Sallyport подходит для такого сценария, когда MCP-совместимому агенту нужно вызвать HTTP API, но он не должен получать Basic-учётные данные. Его HTTP-канал подставляет Basic-учётные данные внутри приложения, а настройки для каждой сессии и вызова позволяют человеку решать, когда запуск агента может использовать эту учётную запись. Агент получает результат действия, а не открытые имя пользователя и пароль, которые можно повторно использовать где-либо ещё.

## В одобрении должно быть достаточно контекста, чтобы заметить неправильное действие

Диалог с единственным вопросом «Разрешить вызов API?» не помогает. Диалог, который печатает полное Basic-имя пользователя, арендатора, полный URL и исходное тело запроса, раскрывает слишком много. Проверяющему нужно краткое описание, по которому очевидно неправильное действие можно заметить без раскрытия всей структуры учётной записи.

Показывайте имя действия, защищённую метку цели, выбранную для проверяющего метку арендатора, тип операции и её общий результат. Например:

```text
Allow export_monthly_payroll?
Target: Payroll export service
Tenant: Finance tenant 7
Operation: Create June 2026 export
Agent run: signed local coding process
```

Этого достаточно, чтобы заметить неожиданный месяц, функцию или цель. При этом в записи об одобрении не раскрываются имя хоста поставщика, номер арендатора или имя сервисной учётной записи.

Метки отображения требуют управления. Если «Finance tenant 7» показывается в команде, где есть только один финансовый клиент, оно всё равно может его идентифицировать. Используйте метки, соответствующие группе, которая их видит. Безопасность не достигается заменой каждого полезного имени на непрозрачный код, непонятный проверяющим.

Для чувствительных Basic-учётных записей требуйте одобрение каждого использования, если действие может перемещать деньги, экспортировать регулируемые записи, менять права доступа или связываться с внешней стороной. Одобрение на сессию практичнее для короткого запуска с множеством операций чтения низкого риска. Решение должно зависеть от полномочий учётной записи и последствий действия, а не от того, что Basic-аутентификация устарела.

Храните проверяемую запись решения об одобрении и последующего вызова. Защищённая от подделки запись полезна только тогда, когда описывает исполнителя, действие, результат и состояние одобрения, не превращаясь в ещё одну копию карты учётных записей. Отдельные журналы сессий и активности Sallyport построены вокруг такого разделения, а оба представления формируются из его зашифрованного журнала аудита с цепочкой хешей.

## Сделайте структуру учётных записей трудной для сбора с самого начала

Решение не в огромном документе политики со словами «осторожно работайте с метаданными». Безопасный путь должен быть естественным выбором разработчиков и агентов.

Составьте перечень всех Basic-интеграций и ответьте для каждой на такие вопросы: что раскрывает имя пользователя? Содержит ли оно человека, клиента, окружение, продукт или роль? Какая пара хоста и конечной точки делает это значение более информативным? Где эти значения появляются сейчас? Кому действительно нужно их видеть?

Затем удалите простые копии. Замените необработанные примеры curl примерами действий. Замените имена фикстур с конкретными арендаторами нейтральными тестовыми псевдонимами. Блокируйте заголовки Authorization и userinfo в URL в журналах приложений. Не передавайте полную диагностику поставщика в вывод, видимый агенту. Отклоняйте цели запросов, заданные пользователем, на границе доступа к учётным данным. Дайте службе поддержки контролируемый способ получить детали, если это оправдано инцидентом.

RFC 9110 запрещает отправителям создавать ссылки HTTP или HTTPS с userinfo вида `user:password@host` и предупреждает, что реализации могут раскрыть идентификатор пользователя или пароль, если такой формат используется в конфигурации или параметрах команд. Это полезное предупреждение выходит за рамки URL: сведения об идентичности, помещённые в удобные текстовые поля, обычно копируются туда, где им никогда не предназначалось находиться.

Basic-аутентификация может сохраняться потому, что поставщик не перешёл на новый вариант. Ваш процесс работы с ней не обязан оставаться прежним. Когда вы начинаете считать имена пользователей, идентификаторы арендаторов и пары конечных точек чувствительными метаданными, вы перестаёте передавать агенту каталог учётных записей вместе с задачей.
