# Как подписи HTTP-запросов ограничивают вызовы API AI-агентов

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

Подписи HTTP-запросов улучшают ситуацию, связывая авторизацию с конкретным сообщением. Продуманная схема может сделать подпись действительной только для `POST https://api.example.test/v1/releases/42`, только с точным телом, подготовленным агентом, только в течение короткого времени и только от одной личности подписи. Это полезный контроль. Он не заменяет авторизацию, подтверждение действий или изоляцию учетных данных.

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

## Подпись ограничивает сообщение, а bearer-токен лишь доказывает владение

Bearer-токен отвечает на один вопрос: предъявил ли этот запрос секрет, который сейчас принимается? Обычно он не связывает секрет с методом, адресом, телом или временем запроса. OAuth-токены доступа могут содержать области действия, аудитории и срок действия, что помогает, но владелец все равно может использовать каждую разрешенную область до истечения срока.

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

Представим сервис развертывания с bearer-токеном, которому разрешено создавать релизы. Сначала агент создает безобидный релиз в staging. Позже prompt-инъекция дает тому же агенту указание создать релиз в production. Если область действия токена охватывает обе среды, он пропустит оба запроса. Замена токена на подписанный запрос сама по себе не исправляет ошибку авторизации. Если личности подписи разрешено создавать релизы в production, она подпишет такой запрос.

Подписи дают сервису средства защиты, которых нет у обычного bearer-заголовка:

- Перехваченный подписанный запрос быстро истекает и не может воспроизводиться бесконечно.
- Скопированную подпись обычно нельзя перенести с `POST /v1/staging/releases` на `POST /v1/production/releases`.
- Измененный JSON не пройдет проверку, если подписан дайджест тела.
- Сервис может определить учетные данные подписи, не принимая их как многоразовое значение заголовка.
- Проверяющий код может точно записать, какие поля одобрил подписант.

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

Для AI-агентов это различие имеет практическое следствие: подпись превращает действие в конкретный объект, который можно проверить, одобрить и записать в журнал. Bearer-токен в основном выглядит как доступная среде способность. Благодаря этому становится возможен контроль, но только если граница инструментов не дает агенту извлечь секрет подписи.

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

RFC 9421, HTTP Message Signatures, задает структурированный способ объявлять покрываемые компоненты и передавать подпись. Это избавляет от привычного частного формата, в котором одна сторона соединяет поля символами новой строки, другая по-другому нормализует URL, а при ошибке проверки обе обвиняют криптографию.

RFC разделяет две идеи. `Signature-Input` объявляет метку, покрываемые компоненты и такие параметры, как `created`, `expires`, `nonce`, `alg` и `keyid`. `Signature` содержит полученное криптографическое значение под той же меткой. Проверяющий код строит базу подписи из объявленных компонентов и параметров, а затем проверяет ее.

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

```http
POST /v1/releases/42?environment=staging HTTP/1.1
Host: api.example.test
Content-Type: application/json
Content-Digest: sha-256=:rPMyV6WTE4Duf0JApE9tXvDYy9EzrgFQbq3e2XTCwbs=:
Signature-Input: sig1=("@method" "@authority" "@path" "@query" "content-digest" "date");created=1735689600;expires=1735689660;keyid="agent-release-17";alg="ed25519"
Signature: sig1=:BASE64_SIGNATURE_BYTES:
Date: Wed, 01 Jan 2025 00:00:00 GMT

{"version":"2025.01.01","notes":"staging validation"}
```

Приведенное выше значение дайджеста показывает форму данных на проводе, но не является дайджестом JSON из примера. Рабочий клиент вычисляет дайджест по точным байтам, которые отправит. Если после подписания он еще раз сериализует JSON, то сам создает причину для ошибки.

RFC 9421 намеренно оставляет много свободы. Это удобно для посредников и разных версий HTTP, но значит, что в контракте API нужно описать точный профиль. Укажите разрешенный алгоритм, обязательные компоненты, максимальный срок действия подписи, формат `keyid`, принимаемый алгоритм дайджеста и необходимость nonce. Если в контракте лишь сказано, что запросы должны быть подписаны, каждый автор клиента будет исходить из своих предположений.

Ed25519 подходит по умолчанию, если сервис может регистрировать открытые ключи. Сервер хранит открытый ключ проверки, и утрата этой записи не раскрывает секрет подписи. Подписи HMAC работают, когда один доверенный компонент и API используют общий секрет, но этот секрет должен существовать с обеих сторон. В процессах с агентами это часто увеличивает число мест, где может утечь многоразовый секрет.

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

## Связывайте метод, назначение и query-параметры, определяющие действие

Агент должен подписывать каждое поле запроса, которое меняет его направление или вызываемую операцию сервера. Для большинства API минимальный полезный набор состоит из `@method`, `@authority`, `@path` и `@query`. Вместо них можно использовать `@target-uri`, если нужен один компонент для полного целевого URI, но не подписывайте оба варианта без причины, которую разработчики смогут объяснить.

`@method` не позволяет использовать подпись для `GET` повторно как подпись для `DELETE`. Это кажется очевидным, однако самодельные схемы часто пропускают метод, потому что разработчик считает, будто путь уже подразумевает операцию. REST API нередко используют один путь с разными методами. Метод меняет действие.

`@authority` связывает подпись с полномочием хоста и порта. Благодаря этому подпись, выданная для одного источника API, не пройдет проверку у другого источника, принимающего те же учетные данные. Это важно для организаций с хостами preview, staging и production. Личность подписи, предназначенная для staging, не должна получить полномочия production из-за изменения хоста агентом или перенаправлением.

К `@path` и `@query` нужно относиться так же внимательно. Многие API помещают значимые параметры в строку запроса:

```http
POST /v1/invoices/817/refund?amount=2500&currency=USD
```

Если подпись покрывает только путь, злоумышленник, способный изменить запрос при передаче, может поменять сумму или валюту. Если сервер берет из query параметры `dry_run`, `environment`, `force`, `page_size`, `include_deleted` или идентификатор арендатора, они входят в состав действия. Подписывайте `@query`.

RFC 9421 также определяет `@query-param`, который позволяет покрыть конкретный именованный параметр. Он полезен в протоколах, где некоторые параметры query явно не влияют на решение о безопасности, например данные трассировки. Для внутреннего API, используемого агентами, обычно понятнее подписывать всю строку запроса. Каждый параметр становится частью одобренного запроса, и проверяющим не нужно помнить список исключений.

Не относитесь к нормализации URL беззаботно. Проверяющий код должен использовать семантику компонентов из RFC 9421 и выбранной библиотеки. Не декодируйте percent-экранирование и не кодируйте его заново вручную. Не сортируйте повторяющиеся параметры query, если этого не требует определение выбранного компонента. Целевой запрос сначала существует как байты на проводе, а уже потом превращается в удобный объект приложения.

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

## Для подписанного тела нужен дайджест, а не надежда на JSON

Подписывайте `content-digest`, если тело влияет на результат. Это относится почти ко всем JSON-запросам, меняющим состояние, multipart-загрузкам, отправкам форм и массовым операциям. Иногда полезно подписывать и `content-type`, если сервер по-разному разбирает одни и те же байты в зависимости от типа содержимого.

IETF определяет `Content-Digest` в RFC 9530. Он содержит дайджест содержимого HTTP-сообщения в синтаксисе Structured Fields. Подпись покрывает заголовок дайджеста, а не огромное тело напрямую, при этом получатель вычисляет дайджест тела и сравнивает его до принятия подписи. Так подпись получает представление точного содержимого фиксированного размера.

Безопасная последовательность отправки проста, и порядок нужно сохранять:

1. Соберите окончательный объект запроса, включая query-параметры и заголовки, влияющие на интерпретацию.
2. Один раз сериализуйте тело в байты и сохраните эти байты для отправки.
3. Вычислите `Content-Digest` по этим байтам.
4. Создайте `Signature-Input` для выбранных компонентов и подпишите его базу подписи.
5. Отправьте неизмененные байты тела и заголовки.

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

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

Для заголовков действует более узкое правило. Подписывайте заголовок, если получатель или посредник может использовать его для изменения смысла запроса с точки зрения безопасности. `content-type` подходит для этого. Заголовок арендатора тоже, если сервер по нему выбирает учетную запись. Заголовок идемпотентности важен, если имеют значение повторы и дублирующие эффекты. Диагностический заголовок обычно подписывать не нужно.

Подпись `user-agent`, `accept`, идентификаторов трассировки, заголовков соединения и каждого заголовка, который отправляет библиотека, создает хрупких клиентов. Прокси могут добавлять, объединять или переписывать обычные заголовки. В HTTP есть допустимые преобразования. Подпись должна отклонять изменения смысла, а не превращать безобидные изменения транспорта в аварию.

## Окна актуальности должны учитывать расхождение часов, но отклонять залежавшуюся работу

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

Используйте `created` и `expires` в `Signature-Input`. Срок в шестьдесят секунд хорошо подходит для интерактивных вызовов, когда агент подписывает запрос непосредственно перед отправкой. Для ненадежной сети или процесса, который повторяет запрос после временного сбоя, могут подойти несколько минут. API должен документировать один максимальный срок и принудительно его соблюдать. Не позволяйте клиентам выбирать произвольное время истечения только потому, что им неудобно работать с ограничениями.

Проверяющий код должен отдельно рассматривать три случая:

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

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

Не используйте HTTP-заголовок `Date` как единственный механизм актуальности. Он может быть полезен как покрываемый компонент для совместимости и диагностики, но `created` и `expires` находятся непосредственно в параметрах подписи и менее неоднозначны. Если подписываете оба варианта, укажите, какие значения сервер применяет для проверки, когда они расходятся. Проверяющий код, принимающий любое из них, дает атакующему ненужное преимущество.

Очереди задач агентов обнаруживают скрытую ошибку проектирования. Допустим, агент создает подписанные запросы в 09:00, человек одобряет их в 09:20, а рабочий процесс отправляет старый запрос после одобрения. Сервис должен его отклонить. После подтверждения нужно заново подписать запрос, поскольку одобренное действие должно иметь актуальное время и актуальное назначение.

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

## Nonce защищает от повторов только тогда, когда сервер его запоминает

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

Nonce снижает этот риск, если сервер считает каждый nonce одноразовым для конкретной личности подписи. Клиент создает непредсказуемое значение, помещает его в параметры подписи или покрываемый заголовок, а сервер записывает успешное использование до истечения подписи. Второй запрос с тем же подписантом и nonce будет отклонен, даже если его подпись еще действительна.

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

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

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

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

## Проверка должна завершаться отказом до того, как запрос увидит код приложения

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

Проверка должна идти в предсказуемом порядке:

1. Разберите `Signature-Input` и `Signature` как Structured Fields, отклоняя некорректный синтаксис и неоднозначность из-за дубликатов.
2. Выберите разрешенную метку подписи и отклоните неизвестные алгоритмы, отсутствующие обязательные компоненты и запрещенные сочетания компонентов.
3. Сопоставьте `keyid` с активной личностью подписи и получите материал для проверки.
4. Восстановите базу подписи по RFC 9421, используя полученный запрос, а не заново собранный URL приложения.
5. Проверьте криптографическую подпись, дайджест тела, временные ограничения, состояние nonce, если он нужен, и только потом авторизацию.

В журналах разделяйте криптографическую ошибку и ошибку авторизации. Внешние ответы могут оставаться намеренно краткими. Для вызывающей стороны достаточно `401` или `403` со стабильным кодом ошибки. Внутри записывайте, был ли запрос отклонен из-за неизвестного `keyid`, истекшей подписи, неверного дайджеста, неправильного authority, использованного nonce или недостаточных разрешений.

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

Тестовые векторы важнее описаний. RFC 9421 содержит примеры, но для профиля API нужны собственные фикстуры. Храните запросы, которые должны пройти проверку, и варианты, которые обязаны завершиться ошибкой: измененный метод, измененный query, измененный байт тела, истекшая подпись, будущее значение `created`, неправильный authority, измененный `keyid` и повторный nonce. Запускайте их для каждой поддерживаемой реализации клиента.

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

## Держите материал для подписи вне контекста агента

Передача AI-агенту, который пишет код, закрытого ключа подписи или секрета HMAC сводит на нет значительную часть преимуществ подписания. Ключ может попасть в вывод инструмента, историю shell, временные файлы, отчеты об ошибках или в prompt, который просит агента вывести окружение. Даже хорошо работающий агент имеет слишком большую косвенную поверхность для многоразовых учетных данных.

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

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

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

Не путайте шлюз действий со стандартом подписей запросов. RFC 9421 объясняет двум сторонам HTTP, как аутентифицировать выбранные компоненты сообщения. Шлюз действий определяет, где хранятся материалы подписи, когда человек видит запрос на подтверждение и какая запись аудита создается вокруг запуска агента. Можно использовать один механизм без другого, но вместе они особенно полезны, когда автономные инструменты обращаются к важным API.

Если вы используете локальный подписывающий компонент, считайте его локальный интерфейс границей авторизации. По возможности связывайте запросы с вызывающим процессом, отклоняйте произвольные адреса и не позволяйте одному процессу агента незаметно использовать одобренную сессию другого. Локальный сервис, подписывающий любой URL, который ему передает любая локальная программа, всего лишь перенес bearer-доступ за сокет.

## Подтверждение и подпись отвечают на разные вопросы

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

Для важных вызовов показывайте на экране подтверждения поля, которые войдут в подпись: метод, authority, путь, значимые query-параметры, дайджест тела или понятное описание тела, личность подписи и срок действия. Если пользователь одобрил `POST /v1/releases/42?environment=staging`, подписывающий компонент не должен позже подменить staging на production через неподписанный query-параметр.

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

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

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

## Ошибки, которые стоит отрабатывать, в основном связаны с обычной инженерией

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

Одна ошибка возникает, когда команда подписывает `@method`, `@path` и `date`, но не включает `@query`. Обычно API релизов принимает `?environment=staging`. Позже изменение обслуживания добавляет к тому же endpoint `?environment=production`. Ошибка прокси или вредоносный локальный компонент меняет параметр после подписания. Подпись проходит проверку, обработчик видит production, а аудит вводит в заблуждение, утверждая, что агент отправил действительный подписанный запрос. Подпись сделала именно то, что требовал список компонентов. Неполным был список компонентов.

Другая ошибка появляется, когда разработчики принимают десятиминутные подписи, чтобы уменьшить число обращений из-за часов. Агент подписывает запрос на удаление, записывает полные заголовки в отладочный журнал, а разработчик копирует журнал в задачу. Любой, кто имеет доступ к этой задаче, может воспроизвести запрос большую часть рабочего дня. Короткий срок действия не отменит первоначальную утечку, но резко ограничит ее пользу. Обязательный nonce для удаления убирает оставшееся окно повтора после первого принятия запроса.

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

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

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