Перейти к документации
ДокументацияМодель безопасности

Как Sallyport защищает ваши ключи

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

На этой странице

Ключ проходит один отрезок: от Sallyport до сервиса

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

  • Ни один маршрут не возвращает ключ. Ни владельцу, ни поддержке, ни через API. Ключ можно заменить или удалить, показать его нельзя.
  • Ключ уходит только на адреса, с которыми его сохранили. Новое подключение, новый адрес или новый адрес выдачи токенов для сохранённого ключа Sallyport не примет: для другого адреса ключ вводят заново. Ключ из каталога доходит и до API-хостов своего сервиса.
  • Ключ открывается в трёх случаях: для вызова агента, допущенного к отправке, для проверки, которую вы запускаете сохранением ключа или кнопкой «Проверить снова», и для чтения списка инструментов подключённого вами MCP-сервера. Каждое открытие попадает в журнал до того, как ключ использован.
  • В памяти ключ остаётся байтами и стирается после вызова. Строку Go стереть нельзя, поэтому тест роняет сборку, если ключ где-то превращается в строку.

У каждой сохранённой записи свой ключ

Корневой ключ открывает ключ аккаунта. Из ключа аккаунта через HKDF-SHA256 выводится ключ для каждого вида данных: ключи сервисов, продление OAuth, запросы агентов, секреты вебхуков. Из него выводится ключ записи.

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

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

Аккаунты не видят данных друг друга

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

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

Агент не может навести шлюз на сам шлюз

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

  • Что бы ни было написано в подключении, отклоняются частные диапазоны, loopback, link-local и адреса облачных метаданных, CGNAT, multicast и все имена и сети Sallyport.
  • Имя определяется заново, когда вызов выполняется, даже если вызов ждал человека. Адрес проверяется и закрепляется в момент отправки.
  • Ключ не уходит по редиректу. Ответ 3xx возвращается агенту как результат. Если агенту нужен новый адрес, он делает новый вызов, и все проверки проходят снова.

Агенты входят со своими токенами

У каждого агента свои токены, их показывают один раз при создании. В базе хранится только HMAC-SHA-256 токена с секретом, который лежит вне базы. Копия базы не содержит ни одного рабочего токена.

  • Отключить агента можно одной кнопкой. «Приостановить агента» на его странице отклоняет следующий вызов, с каким бы токеном или входом он ни пришёл. Отзыв токена в разделе «Токены» останавливает только этот токен. «Удалить агента» внизу его страницы убирает агента насовсем.
  • MCP-клиент, который входит через OAuth, получает токен Sallyport после того, как человек одобрит клиента на экране согласия. Ключа сервиса клиент не видит.
  • Платформа, которая запускает агентов на своих машинах, подключается как доступ к управлению агентами, а не как агент. Её собственный токен ничего не вызывает: MCP-адрес и прокси его отклоняют. Часовой токен на каждую машину она получает только после согласия человека. Каждый такой агент начинает без доступа, и дать его может только человек. Каждый токен действует как собственный агент машины и заканчивается вместе с подключением.
  • Список инструментов у каждого агента свой, поэтому он помечен как приватный. Клиент не может закешировать инструменты одного агента и показать их другому.

Человек решает там, где вы попросили, а два действия ждут его всегда

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

  • Карточка показывает запрос, который отправит Sallyport: метод, хост и путь. Слова самого агента стоят отдельным блоком с пометкой «не проверено».
  • Кнопка «Разрешить вызов» действует на один конкретный запрос. Такое одобрение привязано к дайджесту запроса, и перед отправкой сохранённый запрос сверяется с ним. Кнопка разрешения на карточке покрывает больше: тот же агент, подключение, класс и путь на заданное время и число вызовов.
  • DELETE не пройдёт под видом GET. Заголовки и параметры подмены метода удаляются до классификации вызова и до расчёта дайджеста.
  • Лимит частоты или параллельности отвечает временем для повтора. Карточку он не создаёт: решать тут нечего.

Каждый вызов записан до того, как выполнится

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

  • Каждая запись ссылается на предыдущую. Её хеш покрывает схему, аккаунт, номер, хеш предыдущей записи и само событие. Схема называется spc-chain/2.
  • Каждая запись подписана Ed25519. Публичные ключи, текущий и выведенные из работы, опубликованы по адресу https://mcp.sallyport.cloud/.well-known/sallyport-audit-keys.json, и выведенный ключ остаётся в этом списке после смены.
  • Шлюз не может править собственный журнал. Его роль в базе добавляет и читает записи, а права изменять их у неё нет.
  • События хранятся читаемым каноническим JSON, и цепочка считается ровно по этим байтам. Любой, кому вы дадите файл, прочтёт то же, что и вы.
  • Кнопка «Скачать журнал» на экране «История вызовов» сохраняет весь журнал в JSON Lines, каждую запись с её хешем и подписью. Она для того, кто аудирует все рабочие пространства: владельца либо администратора или аудитора всей организации без более узкой роли в каком-либо пространстве.
  • Каждая запись журнала относится к рабочему пространству того, о чём она. Вкладка «Журнал» и вебхук показывают человеку записи пространств, где он владелец, администратор или аудитор, и записи самой организации. Администратор организации, который в пространстве клиента только участник, записей этого клиента не читает.

Что происходит с ответами, которые приносят вызовы

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

  • Сохранённый ответ может прочитать агент, чей вызов его получил, и люди вашего аккаунта, которым открыт журнал. Каждое чтение попадает в журнал.
  • Сохранённые ответы, ответ каждого вызова и тела запросов больше 1 МиБ удаляются через 30 дней. Большое тело хранится зашифрованным своим ключом.
  • История вызовов, запрос каждого агента и журнал хранятся 180 дней по умолчанию. Срок меняется в «Настройки», «Организация», «История вызовов и журнал (дни)».
  • Уведомление в браузере называет агента, вид действия и сервис, а идентификаторы в пути заменены. Значений аргументов, тела запроса и ключей в нём не бывает.

Перезапуски и сбои не теряют и не повторяют вызовы

  • Вызов, который ждёт человека, хранится в базе. Выкладка его не теряет, и агент продолжает с тем же id вызова.
  • Если процесс остановился после того, как запрос ушёл к сервису, вызов помечается как вызов с неизвестным исходом. Sallyport сам его не повторяет, поэтому платёж не уйдёт дважды.