env-master
ENСоздать аккаунт
Разделы документации

Безопасность

Что система гарантирует, чего не гарантирует и почему.

Где проходит граница

В покое — только шифртекст. Значения зашифрованы XChaCha20-Poly1305 на ключ данных окружения, сам ключ лежит зашифрованным под корневым ключом, который живёт в окружении оператора или во внешнем KMS. Украденный дамп MongoDB без корневого ключа бесполезен.

В работе — зависит от пути:

  • машинный путь честно клиентский: сервер отдаёт SDK шифртекст и ключ данных, обёрнутый x25519 под ключ конкретной identity. Открытого значения сервер в этом сценарии не производит вовсе;
  • запись и показ значения человеку проходят через сервер открытым текстом по TLS.

Поэтому мы не называем систему zero-knowledge. Живой процесс имеет доступ к значениям в момент обработки, и обещать обратное было бы неправдой — именно на такой попытке в своё время споткнулся Infisical, когда дашборду и интеграциям понадобился plaintext.

Аутентификация identity

Ничего вечного: у client secret есть отзыв, у access-токена — TTL в минутах, у enrollment-токена — десять минут и ровно одно использование.

Повторный обмен enrollment-токена означает, что он утёк. Система не просто отказывает: захват токена атомарный (compare-and-set), проигравшая попытка поднимает событие enrollment.reuse-detected и вебхук администраторам. Разбор механизма и действий по тревоге — на странице Сервисы и identity.

Аудит

Два потока, видимых клиенту: изменения (activity) и доступы (access). Каждое чтение секрета — человеком или identity при старте — записано. События связаны цепочкой на уровне организации, поэтому незаметно удалить или переписать запись нельзя.

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

Проверка — команда audit:verify. Она идёт от первого события по звеньям и сверяет каждое, попутно замечая то, чего сверка хешей по отдельности не увидит: развилку, обрыв хвоста и события, до которых от начала цепочки не добраться. Свойство, которое нельзя проверить, свойством не является.

Голова цепочки тоже подписана — переписать её, удалив последние события, без ключа нельзя. Остаётся один случай, который изнутри базы не отличить: откат к состоянию, которое когда-то было подлинным, из старой резервной копии. Против него сервер раз в час пишет якорь — снимок головы — в свой лог, а он уезжает с хоста. Передайте накопленные якоря проверке (audit:verify --anchors <файл>), и она убедится, что каждое зафиксированное звено осталось на своём месте — и что журнал не стёрт целиком. Без якорей проверка об откате не знает и говорит об этом прямо.

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

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

Значений в журнале нет: только имена ключей и HMAC значения. Этого хватает, чтобы увидеть «значение изменилось», и не хватает, чтобы узнать какое.

Ограничения по частоте

Вход и обмен токенов ограничены — по IP и по субъекту; при отказе сервер отвечает 429 RATE_LIMITED с заголовком Retry-After:

ТочкаЛимит
вход пользователя10 попыток за 15 минут
вход identity (/machine/login)30 за 15 минут
обмен enrollment-токена (/machine/enroll)5 за 15 минут
регистрация и письма подтверждения3 в час
обновление токена60 за 15 минут

Периметр

  • адрес вебхука проверяется дважды: при сохранении и перед каждой доставкой, внутренние и loopback-адреса отклоняются, иначе сервер стал бы сканером собственной сети;
  • размер запроса и значения ограничены и в приложении, и на обратном прокси;
  • refresh-токен браузера живёт в httpOnly-cookie, access-токен — только в памяти вкладки: XSS уносит 15 минут, а не 30 дней;
  • API отвечает с Content-Security-Policy: default-src 'none', X-Content-Type-Options: nosniff, запретом фреймов и Referrer-Policy: no-referrer; HSTS — забота обратного прокси, который терминирует TLS.

Ротация корневого ключа

Корневой ключ тоже не вечен:

OLD_ROOT_KEY=... NEW_ROOT_KEY=... bun run db:rekey -- --dry-run

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