# Раскрывают ли плейсхолдеры учётных данных детали работы системы?

Замаскированные учётные данные всё равно могут рассказать тому, кто их увидит, как устроено ваше рабочее окружение. Команды нередко радуются тому, что заменили токен на `***`, а затем оставляют без изменений поддомен арендатора, роль администратора, регион, номер аккаунта, путь к секрету и синтаксис подстановки. Токен остался в тайне. Устройство системы уже нет.

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

## Плейсхолдер тоже метаданные, только с другим радиусом ущерба

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

Рассмотрим такой фрагмент развертывания:

```yaml
billing_export:
  url: https://acme-prod.eu.example.net/v2/exports
  authorization: Bearer ${ACME_PROD_EU_BILLING_ADMIN_TOKEN}
  tenant: northstar-retail
  credential_ref: vault://teams/finance/prod/billing-export-admin
```

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

При проверках безопасности это часто сводят к бинарному вопросу: «Содержит ли это секрет?» Вопрос слишком узкий. Лучше задать два:

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

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

Документация AWS наглядно показывает это различие на примере Amazon Resource Names. ARN идентифицирует ресурс с помощью таких полей, как раздел, сервис, регион, ID аккаунта, тип ресурса и ID ресурса. AWS прямо говорит, что ARN не являются учётными данными, и это верно. Но та же документация показывает, почему они могут содержать полезную структурную информацию. Ссылка на ресурс может раскрыть облачный раздел, регион размещения, аккаунт владельца, сервис, имя или путь к ресурсу. «Не секрет» не означает «безопасно для любой аудитории».

## Псевдонимы часто раскрывают владельца и уровень доступа

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

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

Самые опасные имена объединяют четыре вида информации:

- Окружение: `prod`, `staging`, `dr`, `sandbox`
- Полномочия: `admin`, `root`, `write`, `breakglass`
- Бизнес-функция: `payroll`, `claims`, `refunds`, `identity`
- Арендатор или владелец: имя клиента, код приобретения, название команды или номер аккаунта

Метка может раскрывать и связи между системами. `SALESFORCE_TO_ERP_SYNC_PROD` сообщает, что две системы обмениваются данными. `PAYROLL_SFTP_VENDOR_A` намекает на внешний канал передачи. `EMERGENCY_DB_RESTORE_KEY` указывает на учётные данные, за которыми стоит охотиться, даже если само значение недоступно.

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

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

```text
Запись в закрытом реестре
Владелец: системы выручки
Назначение: отправка рабочих корректировок возвратов
Цель: платежный арендатор northstar-retail в ЕС
Полномочия: запись корректировок возвратов
Псевдоним среды выполнения: cred_4d91

Широко видимое операционное событие
credential=cred_4d91 action=refund_adjustment outcome=denied
```

Псевдоним среды выполнения сохраняет возможность сопоставлять события. Оператор увидит повторяющиеся ошибки, связанные с `cred_4d91`, а полностью восстановить бизнес-контекст смогут только люди с доступом к реестру.

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

## Форматы раскрывают больше, чем ожидают правила маскирования

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

Рассмотрим такие ссылки:

```text
arn:aws:iam::123456789012:role/ci-prod-deployer
projects/810245991002/secrets/payments-prod-api/versions/latest
https://tenant-44.api.vendor.example/v1/invoices
postgresql://reporting:${DB_PASSWORD}@db-prod-2.internal:5432/revenue
```

Пароля может не быть ни в одном примере, но оставшийся синтаксис всё равно раскрывает разные сведения. ARN содержит облачный раздел, сервис, структуру аккаунта и имя роли. Ссылка на менеджер секретов показывает проект, назначение секрета и подход к версиям. Имя API-хоста раскрывает модель арендаторов. Формат подключения к базе данных показывает протокол, схему имён хостов, порт, имя базы, а также место подстановки пароля.

RFC 3986 определяет основные части URI, включая схему, authority, хост, порт, путь, запрос и фрагмент. В документе также сказано, что форма `user:password@host` для пользовательской информации в URI устарела, а приложениям не следует отображать данные после первого двоеточия как открытый текст. Практический вывод шире буквальных паролей: URI состоит из нескольких полей, и маскирование одного из них не стирает остальную историю об устройстве системы.

Форматы приводят к двум типичным ошибкам.

Первая ошибка, частичное маскирование. Преобразователь логов видит `Authorization: Bearer` и заменяет следующий токен, но выводит полный подписанный URL, параметры запроса которого содержат `X-Amz-Credential`, идентификатор ключа доступа, дату, регион, сервис и область действия. Секретная подпись скрыта, но запрос всё равно раскрывает формат идентификатора и цель.

Вторая ошибка, восприятие ссылки как непрозрачной строки, хотя на самом деле она структурирована. Шаблон вроде `vault://path/to/item#field` содержит отдельные части с разной чувствительностью. Если одна команда убирает значение поля, а другая выводит полный путь, у них нет общего правила для аудитории. Есть только разрозненные замены строк.

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

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

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

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

Внутренние имена создают ещё больше проблем. Команды используют метки вроде `payer-west`, `acquisition-cedar`, `health-data` или `gov-contracts`, потому что так проще работать. Одновременно они раскрывают деловую активность, регулируемые нагрузки и связи между организациями. Плейсхолдер `${ACQUISITION_CEDAR_SFTP_KEY}` может выдать информацию о сделке раньше официального объявления.

То же относится к структуре конечных точек. Сравните два события:

```text
request failed: credential=cred_4d91 target_class=payment_export status=403

request failed: POST https://northstar-retail.prod-payments.eu.internal/v3/refunds/export
credential=PROD_NORTHSTAR_REFUNDS_ADMIN status=403
```

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

Не удаляйте всю информацию о цели автоматически. Операторы не смогут расследовать расплывчатое событие `request failed`, если сервис недоступен. Когда широкой аудитории нужен контекст, заменяйте точные идентификаторы контролируемыми классами: `payment_export`, `customer_data_write`, `artifact_publish`, `repository_deploy`. Точную конечную точку оставляйте в закрытой записи события, доступ к которой оправдан задачей расследования.

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

## Маркеры подстановки раскрывают путь доверия

Маркер вроде `${TOKEN}` означает больше, чем отсутствие значения. Он говорит, что процесс подставит значение позже. По написанию часто можно понять, какой именно процесс.

Для разработчика эти примеры выглядят похожими, но они раскрывают разные границы доверия:

```text
${GITHUB_ACTIONS_DEPLOY_TOKEN}
${{ secrets.DEPLOY_TOKEN }}
{{ vault "kv/prod/deploy" "token" }}
secretKeyRef: name: prod-deployer key: token
op://Infrastructure/Production Deploy/token
```

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

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

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

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

## Замаскированный лог всё равно может дать атакующему рабочий план

Обычно проблема возникает из-за отладочного изменения, которое в тот момент казалось разумным.

Представим задачу развертывания, обращающуюся к API поставщика. Токен хранится в секрете CI. Из-за периодических ошибок аутентификации shell-скрипт включает трассировку команд. Платформа CI маскирует буквальное значение токена, поэтому команда считает логи безопасными.

В трассировке появляется следующее:

```text
+ API_BASE=https://tenant-44.eu.vendor.example
+ TOKEN=***
+ curl -X POST https://tenant-44.eu.vendor.example/v1/admin/export \
  -H 'Authorization: Bearer ***' \
  -H 'X-Account: 784221' \
  -H 'X-Client-Name: finance-nightly-export'
< HTTP/2 403
< x-request-id: 81b5b8e1
< x-region: eu-central
```

Замаскированный bearer-токен никто не сможет повторно использовать. Но человек с доступом к логам теперь знает поставщика, схему имён арендаторов, административную конечную точку, номер аккаунта, имя рабочей нагрузки, регион и примерное расписание. Он может поискать в той же организации `finance-nightly-export`, обратиться к владельцу аккаунта с правдоподобной просьбой или воспользоваться сведениями о конечной точке, найдя другие учётные данные.

Обычный ответ звучит так: «Будем маскировать больше». Это полезно, но не устраняет ошибку проектирования. Задача вывела полный план запроса в лог с широким доступом и длительным хранением. Редактор не знает, важны ли для бизнеса `tenant-44`, `784221` или `finance-nightly-export`. Он может сопоставить только строки, которые вы заранее указали.

Документация GitHub по безопасности содержит связанное предупреждение: маскирование секретов в основном зависит от точных совпадений, а структурированные данные, такие как JSON, XML или YAML, усложняют надёжное маскирование. Также рекомендуется регистрировать для маскирования производные чувствительные значения, например JWT, созданный на основе другого секрета. Это разумная мера, но лишь запасной барьер. Безопаснее формировать сводку запроса для людей, а не печатать shell-трассировку, рассчитанную на терминал.

Замените трассировку ограниченным событием:

```text
outbound_call
operation=finance_export
credential=cred_4d91
target_class=vendor_admin_api
method=POST
result=403
request_id=81b5b8e1
```

Точный запрос оставляйте только в закрытой диагностической записи, если он действительно нужен поставщику для поддержки. Установите короткий срок хранения такой записи. Не вставляйте её в задачу, ветку чата или задание агента.

## Классифицируйте ссылку, а не только раскрытый секрет

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

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

| Класс | Примеры | Широкие логи и промпты | Закрытые операционные записи |
| --- | --- | --- | --- |
| Значение учётных данных | токены, закрытые ключи, пароли, подписи запросов | Никогда | Только когда без этого нельзя, в зашифрованном виде и с жёстким сроком хранения |
| Прямой идентификатор | имена арендаторов, ID аккаунтов, точные имена хостов, пути в хранилище | Обычно удалить или заменить | Разрешить, если нужно для расследования |
| Структурные метаданные | провайдер, окружение, класс сервиса, модель подстановки учётных данных | Только если это нужно аудитории | Разрешить |
| Операционная метка | непрозрачный псевдоним, класс действия, результат, маркер корреляции | Разрешить | Разрешить |

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

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

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

## Создавайте ссылки для наименее доверенной аудитории

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

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

Рабочее соглашение состоит из трёх частей:

```yaml
# Широко видимая конфигурация
export_job:
  action: finance_export
  credential_alias: cred_4d91
  target_class: vendor_admin_api

# Закрытый реестр учётных данных
cred_4d91:
  owner: revenue-systems
  approved_action: finance_export
  exact_target: https://tenant-44.eu.vendor.example/v1/admin/export
  secret_reference: restricted-store-record
```

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

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

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

```python
import re
from pathlib import Path

pattern = re.compile(
    r"(?i)(prod|staging|admin|root|breakglass|tenant|customer|"
    r"account|vault://|secretkeyref|secrets\.)"
)

for path in Path(".").rglob("*"):
    if path.is_file() and path.suffix in {".yml", ".yaml", ".json", ".env", ".txt"}:
        for number, line in enumerate(path.read_text(errors="ignore").splitlines(), 1):
            if pattern.search(line):
                print(f"REVIEW {path}:{number}: {line.strip()}")
```

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

```text
REVIEW deploy.yaml:7: credential_alias: PROD_NORTHSTAR_REFUNDS_ADMIN
REVIEW deploy.yaml:11: secret_reference: vault://finance/prod/northstar/refunds
```

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

## Агентам нужен контекст действия, а не устройство учётных данных

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

Дайте агенту минимальный полезный словарь действий. Ему может потребоваться запросить `finance_export` для цели класса `vendor_admin_api`. Но ему редко нужны путь в хранилище, ID арендатора, формат заголовка авторизации или точная переменная окружения, которая разрешит секрет. Если он не видит эти поля, он не сможет повторить их в описании патча, транскрипте терминала или вызове внешнего инструмента.

Это улучшает и подтверждение человеком. Человек одобряет действие по его последствиям: «отправить финансовый экспорт в одобренный API поставщика». Отображение `vault://teams/finance/prod/...` или имени хоста конкретного клиента не помогает принять более безопасное решение. Такие детали мешают подтверждающему и дают дополнительную информацию каждому, кто позже увидит запись.

Sallyport разделяет эти уровни: API- и SSH-учётные данные хранятся в его зашифрованном хранилище, а действие выполняется без передачи учётных данных агенту. Сеансы и записи активности могут показывать запуск агента и отдельные вызовы, не раскрывая агенту значения секретов. Эта граница полезна, но выбранные вами имена действий, конечные точки и псевдонимы всё равно нужно проверять как метаданные.

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

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