Безопасность
Где ключ на каждом хопе
Эта страница написана для того, кто подписывает решение поставить боевой кред за сервис. Здесь сказано, что держит конструкция и что решает ваш собственный каталог и политика. И то, и другое проверяется на вашей машине, и эта страница объясняет как.
Архитектура
Три хопа и одно поле
Две роли из одного бинарника. Control держит базу, хранилище и журнал. Executor делает исходящий вызов и живёт в сети, откуда нет маршрута ни к базе, ни к хранилищу. Кред покидает хранилище один раз на вызов, внутри процесса, который прямо сейчас будет его использовать.
Граница
Что держит конструкция и что решает ваш каталог
Первый список структурный: он верен из-за формы кода, и есть тесты, которые падают, когда это перестаёт быть правдой. Второй список ведёте вы, и консоль показывает его целиком для вашего арендатора.
- Держится конструкцией
- Ни один ответ агенту не несёт значения креда. Ни один маршрут платформы не читает его обратно: ни владельцу, ни поддержке, ни через API. Executor заменяет точные внедрённые байты и их base64-форму в теле, заголовках и Location каждого ответа. Исходящие байты сканируются на наши префиксы токенов и на внедрённое значение, и совпадение отклоняет вызов.
- Решает ваш каталог
- Агент не получает секрет от нас. Он может получить секрет от апстрима, если вы разрешили ему путь, который его выдаёт. Такие пути известны и названы: installation-токены GitHub, CI-переменные GitLab, env Vercel с decrypt, kv у Vault. Рецепт их помечает, класс — destructive, по умолчанию отказ, и консоль показывает полный их список в вашем арендаторе.
- Держится двумя ролями в базе
- Control подключается ролью, которая ничем не владеет и не может обойти row-level security. Изоляция двухуровневая, арендатор и воркспейс, и ставится внутри каждой транзакции. У партиционированной таблицы политика есть на каждой партиции, и тест по системному каталогу роняет сборку, если одной не хватает.
- Держится сетью
- Executor'ы сидят в одной сети с control и больше нигде: ни базы, ни объектного хранилища, ни ключей. Они получают один кред на один вызов по взаимному TLS. Для SSH приватный ключ им не достаётся вовсе: каждый challenge подписывает control.
Точный список
Пути, которые выдают собственный кред
Некоторые эндпоинты отвечают чужим секретом, и хватает GET: GitLab отдаёт все переменные проекта, Vercel по запросу расшифровывает окружение, AWS выпускает сессионные креды. Каждый такой путь помечен в каталоге, получает класс destructive независимо от глагола и отклоняется, пока вы его не разрешите. Эта таблица собрана из каталога, который едет вместе с сайтом, то есть это тот же список, по которому классифицирует движок.
| Сервис | Путь | Что происходит |
|---|---|---|
| Amazon Web Services | ^sts(\.[a-z0-9-]+)?\.amazonaws\.com$ | destructive, по умолчанию отказ |
| Amazon Web Services | X-Amz-Target: (?i)(^|[._])CreateAccessKey$ | destructive, по умолчанию отказ |
| Amazon Web Services | ?Action=^CreateAccessKey$ | destructive, по умолчанию отказ |
| Amazon Web Services | Action=^CreateAccessKey$ | destructive, по умолчанию отказ |
| Amazon Web Services | X-Amz-Target: (?i)(^|[._])GetSecretValue$ | destructive, по умолчанию отказ |
| Amazon Web Services | X-Amz-Target: (?i)(^|[._])GetParameters?$ $.WithDecryption ~ ^true$ | destructive, по умолчанию отказ |
| GitHub API | POST ^/app/installations/[^/]+/access_tokens$ | destructive, по умолчанию отказ |
| GitHub API | POST ^/(repos/[^/]+/[^/]+|orgs/[^/]+)/actions/runners/(registration|remove)-token$ | destructive, по умолчанию отказ |
| GitLab API | ^(/api/v4)?/(projects|groups)/[^/]+/variables | destructive, по умолчанию отказ |
| GitLab API | POST ^(/api/v4)?/(projects|groups)/[^/]+/(deploy_tokens|access_tokens|runners) | destructive, по умолчанию отказ |
| Sentry API | GET HEAD POST ^/api/0/projects/[^/]+/[^/]+/keys/ | destructive, по умолчанию отказ |
| Slack Web API | ^/api/(oauth\.(v2\.)?access|apps\.connections\.open|tooling\.tokens\.rotate)$ | destructive, по умолчанию отказ |
| Vercel API | /env ?decrypt=^true$ | destructive, по умолчанию отказ |
| Vercel API | GET HEAD ^/v[0-9]+/projects/[^/]+/env/[^/]+$ | destructive, по умолчанию отказ |
Консоль показывает тот же список для вашего арендатора, отфильтрованный по binding’ам, которые у ваших агентов действительно есть.
Хранилище
Конвертное шифрование и ни одного пути прочитать значение
Четыре шага, каждый уже предыдущего. Root-wrapper на хосте открывает ключ арендатора. Тот открывает доменный ключ: один для кредов, один для журнала, один для тел и записей SSH-сессий, которые вы решили хранить. Из него через HKDF-SHA256 выводится подключ записи, и запись запечатывается ChaCha20-Poly1305 под аутентифицированным заголовком из арендатора, домена, записи и версии. Поменяйте любое из четырёх, и запись не откроется.
- Один путь unwrap, и он открывается на одном состоянии
- Движок разворачивает значение для вызова, который уже перешёл в dispatching. Второго вызывающего нет: ни экспорта, ни инструмента поддержки, ни экрана администратора, и тест обходит пакет, чтобы так и оставалось.
- У root-ключа вторая копия есть с минуты рождения
- При генерации он делится на три доли, любые две из которых его собирают: доля в менеджере секретов владельца, доля офлайн, доля во внешнем KMS. Обращение к доле — событие в платформенном журнале.
- Ежедневный самотест, чтобы первым сказал монитор
- Раз в сутки джоба разворачивает тестовый ключ и публикует результат метрикой с алертом. Это ловит именно тот случай, когда всё отвечает, а любой вызов с кредом падает.
Журнал
Пишется до вызова, запечатывается от собственного писателя
Строка намерения и обновление цепочки идут в одной транзакции с решением об отправке, так что запись существует раньше побочного эффекта. Номера берутся из строки-счётчика, а не из последовательности базы: последовательность выдаёт номера, которые переживают откат, и необъяснимый разрыв аудитор читает как удаление.
- Запечатано age на получателя, которого у писателя нет
- Тело каждого события шифруется на X25519-получателя арендатора до того, как его хешируют. У процесса, который пишет журнал, есть только публичная половина.
- Checkpoint’ы остаются у вас, подпись Ed25519
- Корень Меркла над цепочкой, подписанный, доступен по API и уходит в вашу SIEM вместе с номером и хешем строки. Ваша SIEM становится свидетелем для своего сегмента. Формат цепочки несёт собственную версию, spc-chain/1, поэтому верификатор, написанный сегодня, читает экспорт, снятый завтра.
- Верификатор без единого нашего ключа
- Наведите открытый инструмент на любой экспорт. Он пересчитает цепочку, проверит подпись и скажет, где первый разрыв. Испорченная строка в эталонном экспорте — часть нашего собственного набора тестов.
Ваши данные, а не только ключи
Тела, которые несут ваши вызовы
Через нас едет не только кред. Ответ Stripe с карточкой клиента, дифф из GitHub, вывод SSH-команды — всё это проходит через executor. Вот что с этим происходит.
- Захват выключен, пока вы его не включите
- Binding хранит тела запросов и ответов, только если вы об этом попросили. До этого в журнале лежит форма вызова и его результат, но не содержимое.
- То, что хранится, запечатано как журнал
- Сохранённые тела и записи SSH-сессий лежат под своим доменным ключом, зашифрованные по записи, со ссылкой из журнала. Они живут по сроку вашего плана и своему TTL.
- Уведомления не несут содержимого никогда
- Карточка в Telegram или письмо содержат цель вызова и ссылку на аутентифицированный экран. Решение принимается только на поверхности, куда вы вошли.
- Удаление арендатора уничтожает ключи, которыми они открываются
- Crypto-shredding вместо джобы удаления, которой нужно верить. После него ни одна запись этого арендатора больше не открывается, ни у нас, ни где-либо ещё.
Executor держит тело ровно столько, сколько идёт вызов. У него нет ни базы, ни объектного хранилища, ни маршрута к ним.
Egress
Агент не может навести шлюз на шлюз
Список запретов вкомпилирован в бинарник, и конфигурация умеет только добавлять. Приватные диапазоны, loopback, link-local, CGNAT, деплой-сеть и наши собственные имена отклоняются независимо от того, есть ли на них binding.
- Имя резолвится заново на каждом исполнении
- В том числе на исполнении после ожидания одобрения. Хост, который был публичным, когда человек отвечал на карточку, к моменту вызова может указывать на метадата-адрес, поэтому адрес резолвится и пинится при отправке, и вызов идёт на тот адрес, который проверили.
- Редиректы с кредом останавливаются
- 3xx возвращается агенту как результат. Шлюз не идёт по нему с вашим ключом.
- Лимит отправляет вызов в одобрение
- Одновременно работают три лимитера: на агента, на binding и на арендатора. Когда один срабатывает, вызов уходит в одобрение, а не в тупик, и лимит скорости отвечает временем повтора словами, по которым модель может действовать.
Спецификация
Ревизия MCP от 2026-07-28 требует именно такой формы и запрещает противоположную
Сервер, который держит токены, привязанные к пользователю, не должен передавать их клиенту, а проброс клиентского токена в апстрим запрещён теми же словами. Каждый вендор, который отдаёт агенту сырой токен, стоит по другую сторону этой фразы.
- Обе живые ревизии, по хендлеру на каждую
- Ревизия 2026-07-28 stateless: без сессионного заголовка, без серверного стрима, discover вместо initialize. Ревизия 2025-11-25 сохраняет сессию. Один хендлер на обе неправ для обеих.
- Список инструментов приватен для binding'а
- Ваш список отличается от чужого, поэтому он помечен приватным. Клиент, кеширующий его как общий, показал бы одному арендатору инструменты другого.
- Сервер авторизации с флагами, которые нужны коннекторам
- Метаданные защищённого ресурса, client id metadata documents, динамическая регистрация для совместимости, PKCE, индикатор ресурса и параметр issuer. Реестр согласий на пользователя и клиента и страница согласия, написанная по чек-листу самой спецификации для прокси со статическим client id.
Когда лежим мы
Вы продолжаете работать без нас
В каждом воркспейсе есть печатная справка в консоли, которую продукт держит актуальной: каждый binding, сервис, кред, провайдер, у которого он выпущен, ссылка на перевыпуск и ссылка на отзыв. Команда, которая не может работать, пока лежим мы, не поставит нас на критический путь.
- Ожидающие одобрения переживают рестарт
- Состояние лежит в базе. Деплой control не роняет ждущий вызов, и агент продолжает по тому же call_id.
- Неизвестный исход остаётся неизвестным
- Если реплика умерла после того, как апстрим принял запрос, вызов помечается исходом, которого никто не знает, и автоматически не повторяется. Двойное списание хуже пропавшей строки.
- Кража токена агента — обнаруживаемый, отзываемый, ограниченный инцидент
- Новый источник поднимает сигнал, агент ставится на паузу, токен ротируется с перекрытием, а журнал показывает ровно те вызовы, где он использовался.
Сначала прочитайте верификатор, потом доверяйте журналу
Инструмент открытый, экспорт — файл, а проверка идёт на вашей машине без единого нашего ключа.
10 000 вызовов в месяц бесплатно. Журнал выгружается на любом плане, и checkpoint'ы есть на любом плане.