При редактировании карточки подтверждения нельзя скрывать суть решения
Редактирование карточки подтверждения должно скрывать учетные данные и личные данные, сохраняя достаточно ясными назначение, цель, эффект и рискованные параметры для принятия решения.

У карточки подтверждения одна задача: помочь человеку решить, может ли конкретное действие покинуть его компьютер. Если карточка скрывает достаточно деталей для защиты учетных данных, но одновременно скрывает, что именно сделает запрос, она не справляется с задачей.
Обычно ошибочно воспринимать редактирование как замену строк. Инженеры маскируют Authorization, очищают тело JSON и считают результат безопасным. Оператор видит POST https://api.example.com/... и кнопку «Подтвердить». Это не осознанное согласие, а пустой ритуал, который приучает людей нажимать кнопку именно в тот момент, когда человеческий контроль должен иметь значение.
Хорошая карточка сохраняет смысл действия и удаляет материал, который позволил бы человеку, читающему карточку, фотографирующему ее, просматривающему журналы или заглядывающему через плечо повторно использовать секрет. Для этого нужен рендеринг с учетом полей. Заголовки, строки запроса, тела и идентификаторы целей требуют разного подхода.
Карточка подтверждения должна объяснять действие
Оператор должен за несколько секунд ответить на четыре вопроса: кто запрашивает действие, куда направляется запрос, что он сделает и какой объект или область затронет. Если хотя бы одного ответа нет, карточка неполна, даже если все секреты скрыты идеально.
Начните со строки действия, где метод протокола сочетается с понятным глаголом:
POST api.billing.example /v1/invoices/inv_7KD2/refund
Action: оформить возврат
Метод важен, потому что GET, POST, PATCH и DELETE предполагают разное поведение. Понятный глагол тоже важен: один метод не объясняет оператору, создает ли POST /v1/invoices/inv_7KD2/refund черновик, отправляет платеж или запускает возврат. Не заставляйте человека выводить смысл операции из названия маршрута, если вызывающая сторона уже знает, что собирается сделать.
Затем покажите назначение как реальный авторитет, а не как имя аккаунта. «Рабочий биллинг» может быть полезным дополнительным контекстом, но не заменяет api.billing.example. Ошибочно направленный запрос может использовать знакомую метку. Хост показывает, какой сервис получит данные и учетные данные.
RFC 3986 разделяет URI на компоненты, включая authority, path, query и fragment. Для отображения подтверждения это разделение полезно, поскольку каждый компонент несет свой сигнал для принятия решения. Не сводите все к одной красивой строке URL в надежде, что последующая маскировка сохранит нужную информацию.
Карточка также должна сообщать, создает, изменяет, удаляет, публикует, передает действие или только читает данные. «Изменить запись клиента» слабее, чем «изменить направление выплат клиента». Если конструктор запроса не может предоставить такую фразу, исправьте конструктор. Рендерер карточки не сможет надежно восстановить смысл бизнеса из произвольного JSON.
Отображайте манифест запроса, а не приукрашенный запрос
Безопасной единицей работы служит типизированный манифест запроса. Он фиксирует намерение агента до добавления учетных данных и до появления представления для подтверждения.
Минимальный манифест может выглядеть так:
{
"channel": "http",
"method": "POST",
"destination": {
"scheme": "https",
"host": "api.billing.example",
"port": 443,
"path_template": "/v1/invoices/{invoice}/refund"
},
"action": "оформить возврат",
"targets": [
{"role": "invoice", "display": "inv_7KD2", "sensitivity": "internal"}
],
"query": [],
"headers": [],
"body": {
"media_type": "application/json",
"fields": []
},
"effect": "financial"
}
Это не представление HTTP-запроса на проводе. Это объект, который должен получать рендерер подтверждения. Разница важна. Запрос на проводе содержит добавленные учетные данные, закодированные значения и детали транспорта. Манифест содержит смысловые метки вроде action, target role и effect, которых нет в необработанном запросе.
Классифицируйте каждое отображаемое значение по тому, что оператору нужно знать для решения, а не по месту, где оно случайно оказалось. Bearer-токен в заголовке является секретом. Подписанный URL вебхука в строке запроса тоже является секретом. Адрес электронной почты в теле JSON может быть персональными данными. Имя репозитория может быть идентификатором цели, видимость которого необходима для безопасного решения.
Используйте небольшой и единообразный словарь:
public: безопасно показывать как есть.internal: показывать, если значение обозначает затронутый объект, но не копировать его в общие журналы.personal: показывать только минимальную полезную форму, обычно метку и часть значения.secret: никогда не показывать значение в карточке, журналах, буфере обмена или тексте ошибки.opaque: показывать утвержденный псевдоним или стабильную несекретную ссылку, только если это помогает отличить цель.
Не давайте вызывающей стороне неограниченный флаг safe_to_display: true. Кто-нибудь использует его для удобства отладки, а затем оставит в пути, обрабатывающем рабочие учетные данные. Требуйте конкретной классификации на границе, где агент создает действие.
Для заголовков нужны имена и назначение, а значения почти никогда не нужны
Имена заголовков часто сообщают оператору гораздо больше, чем их значения. Значения нередко содержат именно то, чему нельзя попадать на поверхность, предназначенную для людей.
Показывайте имя каждого важного для безопасности заголовка и короткую метку назначения. Например:
Заголовки
Authorization: bearer-учетные данные из хранилища
Idempotency-Key: сгенерированный идентификатор запроса
X-Request-Reason: «возврат запрошен финансовым отделом»
Content-Type: application/json
Первая строка сообщает оператору, что запрос будет аутентифицирован, а источник учетных данных показывает, используется ли ожидаемый сохраненный секрет. Отображение Bearer eyJ... не дает ничего для принятия решения. Оно создает путь утечки секрета и побуждает людей сравнивать бессмысленные фрагменты токена.
Спецификация bearer-токенов OAuth определяет заголовок запроса Authorization как предпочтительный способ передачи, а рекомендации по безопасности рассматривают bearer-токены как учетные данные, требующие защиты при передаче и хранении. Такой же подход нужен интерфейсу подтверждения: пользователю нужно знать, что применяется bearer-учетная запись, а не проверять ее содержимое.
Используйте такие правила для заголовков:
- Показывайте безопасные значения протокола, например
Content-Type,AcceptиIf-Match, если они влияют на поведение. - Показывайте имена, но не значения для
Authorization,Proxy-Authorization,Cookie,Set-Cookie, заголовков подписей, заголовков API-ключей и пользовательских заголовков, классифицированных как секретные. - Показывайте ограниченное экранированное значение заявленного несекретного бизнес-контекста, например
X-Request-Reason, только если оно короткое и не может содержать личные данные или секреты. - Показывайте отсутствие заголовка, если оно меняет решение. Отсутствие
If-Matchможет быть важно при операции перезаписи. - Никогда не выводите все заголовки по умолчанию. Библиотеки запросов добавляют шум, а шум скрывает единственный заголовок, меняющий действие.
Распространенный плохой подход маскирует секрет первыми и последними четырьмя символами: sk_live_...9a31. Такой шаблон кажется осторожным, но небезопасен для коротких значений, структурированных значений, тестовых ключей и значений, уже утекших в другие места. Он также заставляет людей думать, что фрагменты секретов нужно узнавать. Заменяйте значение описанием типа, например сохраненные API-учетные данные или подпись запроса.
Заголовки могут скрыто обозначать цели. Заголовок маршрутизации арендатора, заголовок олицетворения или X-Account-ID способны изменить того, на кого повлияет действие. Не скрывайте их только потому, что это заголовки. Показывайте роль и безопасную метку цели: X-Account-ID: account «Northwind production». Если нельзя сопоставить непрозрачный идентификатор с безопасной меткой, укажите, что будет использован непрозрачный идентификатор аккаунта, и потребуйте более осознанного подтверждения для чувствительных операций.
К строкам запроса нужно относиться подозрительнее
Строки запроса видны в URL, копируются в терминалы, попадают в отчеты об ошибках и часто записываются инфраструктурой, которая никогда не видит тело запроса. Именно это удобство требует осторожного обращения с ними в карточках подтверждения.
RFC 9110 предупреждает, что сведения в URI могут раскрыться через ссылки, журналы и другие каналы, и советует отправителям не помещать чувствительную информацию в целевые URI HTTP. Это не отвлеченная забота о стандартах. Карточка с полной строкой запроса может стать еще одним каналом раскрытия значения, которому вообще не следовало находиться в URI.
Не считайте все значения строки запроса безопасными только потому, что запрос использует GET. Учитывайте имена, заявленные типы и контекст операции.
GET api.crm.example /v2/contacts
Параметры запроса
status = «active»
owner = «sales-west»
include = «notes»
access_token = [секрет, скрыт]
search = [личный текст, скрыт]
status и include часто полезны для принятия решения. search может содержать имена, адреса электронной почты, медицинские термины или все, что агент извлек из локальных файлов. access_token, очевидно, является секретом, но дизайн не должен зависеть от очевидных имен. Некоторые API используют sig, token, key, code, state, assertion или специфичный для поставщика параметр, в названии которого нет предупреждения.
По умолчанию считайте значения строки запроса секретными, если манифест явно их не классифицировал. Это намеренно строже, чем во многих обозревателях API. Интерфейс подтверждения не является консолью отладки. Читателю нужно достаточно информации, чтобы разрешить запрос, но не побайтовая реконструкция.
Сохраняйте повторяющиеся параметры и их порядок, если они влияют на смысл. Рендерер, превращающий строку запроса в словарь, может незаметно потерять tag=urgent&tag=finance, преобразовать повторяющиеся значения или скрыть ошибку подписи. Отображайте список записей, а не карту:
Параметры запроса
label = «finance»
label = «urgent»
expand = «line_items»
Если скрытое поле запроса меняет маршрутизацию или авторизацию, скажите об этом. signature = [подписанное значение запроса, скрыто] дает оператору больше информации, чем пустая строка. Если запрос содержит непрозрачную ссылку общего доступа, не раскрывайте токен. Покажите известную метку ресурса, например общий отчет: прогноз на второй квартал, а иначе выведите присутствует токен общего ресурса.
После редактирования тела должны сохранять структуру
Тело, превратившееся в [redacted], почти ничего не сообщает оператору. Тело, в котором каждое поле показано как есть, рано или поздно раскроет то, чему не место на поверхности подтверждения. Нужна структурная редактура.
Отображайте тело как типизированное дерево. Сохраняйте ключи объектов, количество элементов массивов, типы данных, безопасные значения перечислений и выбранные метки целей. Небезопасные конечные значения заменяйте поясняющей меткой.
{
"invoice": "inv_7KD2",
"amount": {"currency": "USD", "minor_units": 12500},
"reason": "duplicate charge",
"customer_note": "[личный текст, 84 символа]",
"payment_method": {
"id": "[непрозрачный способ оплаты]",
"token": "[секрет, скрыт]"
}
}
Такое представление позволяет оператору увидеть, что действие возвращает 125,00 USD по указанной причине и отправляет с компьютера личную заметку. Этого достаточно, чтобы понять, соответствует ли запрос задуманной задаче. Заметка и токен остаются скрыты.
Сохраняйте числа, когда именно число определяет эффект. Скрытие суммы платежа, количества мест, периода хранения, ограничений скорости, уровней разрешений и числа удаляемых объектов делает подтверждение бессмысленным. Считайте такие значения параметрами действия, а не случайными данными. В теле DELETE с {"purge": true} нужно показать purge: true, иначе карточка скроет необратимую часть.
Для текста нужно отдельное правило. Свободный текст может содержать исходный код, данные клиентов, вставленные секреты или инструкции, меняющие действие. Произвольный фрагмент хочется показывать, чтобы оператор мог заметить бессмыслицу. Но так окно подтверждения превращается в канал вывода данных. Для не классифицированного свободного текста показывайте имя поля, число символов и роль назначения. Ограниченный фрагмент выводите только если вызывающая сторона пометила поле как public или internal, а рендерер экранировал управляющие символы.
Для массивов нужны количества и сводки. Плохо:
recipients: [скрыто]
Лучше:
recipients: 37 адресов электронной почты [личные значения скрыты]
В разрушительной операции количество меняет решение. При изменении доступа покажите роль и количество: добавить 4 участников в роль: billing-admin. Если внутренние идентификаторы участников нужны оператору для различения целей, показывайте утвержденные имена или псевдонимы, а не исходные ID.
Никогда не определяйте чувствительность только по имени поля. password, token и secret должны входить в жесткий список запрета, но те же данные могут находиться в content, message, value, data и metadata. Классификацию должна предоставлять схема, конструктор действия или явная аннотация поля. Фильтр по имени служит последней линией защиты, а не основой дизайна.
Идентификаторы целей должны быть понятными, но не полностью раскрытыми
Цель это объект, который придает запросу последствия. Она может находиться в сегменте пути, заголовке, параметре запроса, поле JSON или аргументе SSH-команды. Карточка должна сделать цель видимой, даже если безопасно показать исходный идентификатор нельзя.
Разделяйте машинную ссылку на цель и ее понятное человеку представление:
{
"role": "repository",
"raw_reference": "repo_01HZX8M9...",
"display": "payments-service",
"scope": "production",
"sensitivity": "internal"
}
Исходная ссылка может быть нужна для выполнения, но в карточке должна быть ее отображаемая форма. Если действие меняет разрешения, используйте фразу, называющую связь: Предоставить аккаунту автоматизации выпуска разрешение на развертывание в рабочем окружении payments-service. Карточка не должна заставлять оператора запоминать непрозрачные ID.
Иногда доступен только исходный идентификатор. Не решайте проблему, показывая его целиком. Выберите стабильную необратимую ссылку, например локальный псевдоним или короткую ссылку подтверждения, сгенерированную из защищенного значения. Не называйте усеченный идентификатор хешем, если это не настоящий криптографический дайджест и вы не понимаете последствия коллизий и сопоставления. Во многих случаях запись клиента [непрозрачная ссылка 4F8C] честнее, чем делать вид, будто оператор может с первого взгляда проверить cus_Qa8J7kW2m9.
Не скрывайте чрезмерно идентификаторы, определяющие масштаб последствий. Запрос DELETE /projects/{project}/members со скрытым проектом опасен, даже если каждый личный идентификатор участника замаскирован. Покажите название проекта, окружение и число затронутых участников. Личные значения оставьте скрытыми.
Здесь есть принципиальная разница: сокрытие секрета защищает конфиденциальность, а сокрытие цели ослабляет авторизацию. Команды часто объединяют обе задачи под словом «редактура». Это разные задачи, и карточке нужны разные правила для каждой.
Объем подтверждения должен соответствовать информации в карточке
Полная карточка не должна разрешать больше, чем в ней описано. Если человек подтвердил запрос на чтение одного репозитория, это подтверждение не может молча распространяться на последующий запрос, меняющий настройки репозитория, только потому, что оба запроса исходят от одного процесса агента.
Подтверждение сессии и подтверждение отдельного вызова отвечают на разные вопросы. Подтверждение сессии отвечает, может ли этот подписанный процесс действовать через шлюз в течение текущего запуска. Подтверждение вызова отвечает, может ли произойти этот конкретный исходящий запрос с данной целью и эффектом. Объединение их в одно огромное разрешение возлагает на первую карточку невозможную нагрузку.
Используйте правило повышения требований, основанное на последствиях. Чтение из известного сервиса может укладываться в разрешение сессии. Вызов, который использует особо защищенные учетные данные, меняет доступ, отправляет сообщение, создает финансовое обязательство или удаляет данные, требует карточки, связанной с конкретным манифестом запроса.
Результат подтверждения должен быть связан с каноническим дайджестом действия, а не с видимым текстом карточки. Дайджест должен включать метод, нормализованное назначение, ссылки на цели, классифицированные несекретные параметры и представление защищенных полей. В него также должно входить достаточно метаданных, чтобы обнаружить изменение запроса после рендеринга. Не связывайте решение только с кратким описанием, удобным для снимка экрана.
Например, этим двум вызовам нужны разные подтверждения, хотя небрежный рендерер может показать их одинаково:
POST /v1/roles/grant
body: role = «viewer», subject = «build-bot»
POST /v1/roles/grant
body: role = «owner», subject = «build-bot»
Роль не является деталью, которую можно спрятать в свернутом JSON. Это и есть действие. Если инженер говорит, что карточка стала слишком перегруженной, сначала уберите декоративные данные протокола. Не убирайте поле, определяющее, сможет ли агент завладеть аккаунтом.
По этой причине Sallyport разделяет авторизацию сессии и ключи отдельных вызовов. Решение по сессии может идентифицировать и допустить новый процесс агента, а учетные данные, помеченные для подтверждения при каждом использовании, все равно запрашивают разрешение перед отдельным действием.
Ошибка редактирования обычно возникает еще до рендеринга
Представьте агента, которому поручили отправить договор на подпись. Он создает такой запрос:
POST /v1/envelopes?template=msa&signature=QmFzZTY0U2lnbmVkVmFsdWU HTTP/1.1
Host: api.signing.example
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
{
"recipients": [
{"name": "Maya Chen", "email": "[email protected]"}
],
"subject": "MSA for Northwind",
"message": "Please sign the attached agreement.",
"document": "JVBERi0xLjQK..."
}
Поверхностная реализация форматирует исходный запрос, заменяет значение Authorization и обрезает длинные строки. Теперь карточка раскрывает подпись в строке запроса, адрес получателя и, возможно, начало документа, закодированное как текст. Усечение не является редактированием. Оно лишь делает утечку менее предсказуемой.
Правильный манифест сначала разделяет части:
POST api.signing.example /v1/envelopes
Действие: отправить договор на подпись
Цель: шаблон «msa»
Получатели: 1 адрес электронной почты [личное значение скрыто]
Тема: «MSA for Northwind»
Сообщение: открытый текст, 39 символов
Документ: 1 вложение PDF [содержимое скрыто]
Учетные данные: bearer-учетные данные из хранилища
Подпись запроса: присутствует, скрыта
Такая карточка позволяет оператору заметить неправильный хост, неверный шаблон, неожиданное число получателей или случайную отправку. Она не раскрывает учетные данные, подпись, адрес электронной почты или байты документа.
Опасная версия не сработала не потому, что шаблон маскировки пропустил signature. Она не сработала потому, что система сочла HTTP-запрос готовым текстом для отображения. Рендерер получил содержащий секреты блок без типов полей, ролей целей и понимания того, какие значения несут смысл операции.
Создавайте рендерер с отказом по умолчанию
Рендерер должен принимать только структурированные данные, применять разрешенные правила отображения и отказываться показывать действие с не классифицированными исходящими полями. Это звучит строго, потому что так и есть. Неклассифицированное поле означает, что кто-то отложил решение, а во время подтверждения уже поздно гадать.
Практический контракт рендеринга состоит из трех этапов:
- Нормализуйте запланированное действие в манифест до добавления учетных данных и транспортного кодирования.
- Проверьте, что у каждого поля есть тип, метка чувствительности и правило отображения. Отклоняйте неизвестные заголовки, значения строки запроса и конечные поля тела, если вызывающая сторона явно не направила их в безопасное скрытое представление.
- Отображайте фиксированный макет карточки, отводя заметное место назначению, действию, целям, эффекту и уведомлениям о защищенных полях.
Не разрешайте HTML, управляющие символы терминала, Markdown или произвольные управляющие символы направления Unicode в отображаемых значениях. Экранируйте их до компоновки. Вредоносное значение не должно превращать recipient: [email protected] в вводящую в заблуждение строку, создавать ненастоящие кнопки или визуально менять порядок идентификатора цели.
Задавайте и ограничения на отображение. Даже открытая строка может содержать 50 000 символов и сделать карточку непригодной. Ограничивайте видимый текст по типу поля, сообщайте об усечении и оставляйте подробный просмотр только для содержимого, которое манифест объявил безопасным. Универсальная кнопка «показать полный запрос» недопустима.
Тестируйте рендерер не только на обычных API-вызовах, но и на враждебных примерах. Используйте bearer-токен в каждом возможном месте, повторяющиеся имена параметров, JSON с вложенными массивами, сегмент пути с процентно-кодированными разделителями, пустой секрет, короткий секрет, очень длинное текстовое поле и значение с переводами строк. Проверяйте также запросы, вредный смысл которых задают логическое значение, количество, роль или хост назначения.
Наконец, фиксируйте, что именно охватывало подтверждение, не копируя открытые секреты в журнал доказательств. Записи активности и сессий Sallyport строятся на одном зашифрованном аудиторском журнале с цепочкой хешей, а команда офлайн-проверки может проверить цепочку без ключа хранилища. К этому стандарту нужно стремиться: аудит должен подтверждать произошедшее, не превращаясь во второе хранилище многократно используемых учетных данных.
Карточка должна делать неправильное действие явно неправильным. Если человек может подтвердить запрос с учетными данными, не увидев его назначения, эффекта и цели, система скрыла именно те факты, которые должны были его защитить.
Вопросы и ответы
Какие сведения должна показывать подсказка подтверждения API?
Показывайте метод, хост назначения, понятную форму пути и названия отправляемых полей. Скрывайте учетные данные, токены сессии, подписанные значения, личное содержимое и идентификаторы, которые раскрывают больше, чем нужно оператору для подтверждения действия.
Должна ли карточка подтверждения показывать полный URL?
Обычно нет. Полный URL может раскрыть секреты в строке запроса, а также личные идентификаторы аккаунта, документа или арендатора. Показывайте хост и нормализованный путь, а выбранные имена параметров запроса и безопасные значения выводите отдельно.
Нужно ли показывать значения API-токенов в подсказке подтверждения?
Полностью скрывайте значение, если только короткий префикс или суффикс не меняет решение. Для bearer-учетных данных, подписанных заголовков, cookie и API-ключей обычно достаточно имени поля: оператору нужно знать, что учетные данные будут использованы, а не видеть их содержимое.
Как скрывать чувствительные поля JSON в подсказке подтверждения?
Используйте имя поля, тип и безопасные структурные сведения: присутствует ли значение, пусто ли оно, его длину, если она полезна, и необратимую классификацию, например «секрет» или «непрозрачный идентификатор». Не применяйте обратимую маску, которая раскрывает достаточно короткого значения для его восстановления.
Безопасно ли показывать параметры строки запроса?
Считайте параметры строки запроса недоверенными, пока не классифицируете их. Имена могут быть полезны, но значения часто содержат учетные данные, подписанные запросы, поисковые фразы, адреса электронной почты, реферальные коды или состояние приложения.
Что такое идентификатор цели в карточке подтверждения?
Идентификатор цели сообщает оператору, какой объект будет затронут: организацию, репозиторий, окружение, счет или аккаунт. Он должен оставаться видимым, если влияет на решение о доступе, но его следует обобщить или скрыть, если он раскрывает личный или секретный идентификатор.
Делает ли редактирование опасный запрос безопасным для подтверждения?
Нет. Редактура защищает читающего карточку от просмотра секрета, но не уменьшает полномочия запроса. В карточке по-прежнему нужно достаточно ясно указать действие, назначение, область действия и необратимый эффект, чтобы подтверждение имело смысл.
Может ли одно подтверждение распространяться на все запросы агента?
Не подтверждайте всю сессию по умолчанию, если отдельные вызовы могут существенно отличаться. Подтверждение сессии может установить, кто выполняет действия, но вызовы, которые тратят деньги, удаляют данные, меняют доступ или используют специально отмеченные учетные данные, должны по-прежнему требовать отдельного решения.
Как разработчикам реализовать редактирование карточек подтверждения?
Храните внутреннее каноническое представление запроса с типизированными полями и метками чувствительности, а затем создавайте из него отдельное представление для подтверждения. Никогда не стройте карточку из строки необработанного запроса с помощью нескольких регулярных выражений в конце обработки.
Что следует записывать в аудиторский журнал после подтверждения?
Записывайте форму действия и защищенные ссылки, но не секреты в открытом виде. Проверяющий должен понимать, какой процесс агента выполнил запрос, куда он отправился, какую операцию пытался выполнить и подтвердил ли ее человек. Аудиторский журнал не должен превращаться в еще одно хранилище секретов.