Безопасность
Что система гарантирует, чего не гарантирует и почему.
Где проходит граница
В покое — только шифртекст. Значения зашифрованы XChaCha20-Poly1305 на data key окружения, сам data key лежит зашифрованным под корневым ключом, который живёт в окружении оператора или во внешнем KMS. Украденный дамп MongoDB без корневого ключа бесполезен.
В работе — зависит от пути:
- машинный путь честно клиентский: сервер отдаёт SDK шифртекст и data key, обёрнутый x25519 под ключ конкретной identity. Открытого значения сервер в этом сценарии не производит вовсе;
- показ значения человеку расшифровывается на сервере и отдаётся по TLS.
Поэтому мы не называем систему zero-knowledge. Живой процесс имеет доступ к значениям в момент обработки, и обещать обратное было бы неправдой — именно на такой попытке в своё время споткнулся Infisical, когда дашборду и интеграциям понадобился plaintext.
Аутентификация машин
Ничего вечного: у client secret есть отзыв, у access-токена — TTL в минуты, у enrollment-токена — минуты и ровно одно использование.
Повторный обмен enrollment-токена означает, что он утёк. Система не просто
отказывает: захват токена атомарный (compare-and-set), проигравшая попытка
поднимает событие enrollment.reuse-detected и вебхук администраторам.
Аудит
Два потока: изменения и доступы. Каждое чтение секрета — человеком или машиной при старте — записано. События связаны хеш-цепочкой на уровне организации, поэтому незаметно удалить или переписать запись нельзя.
Значений в журнале нет: только имена ключей и HMAC значения. Этого хватает, чтобы увидеть «значение изменилось», и не хватает, чтобы узнать какое.
Периметр
- вход и обмен токенов ограничены по частоте — по IP и по субъекту;
- адрес вебхука проверяется дважды: при сохранении и перед каждой доставкой, внутренние и loopback-адреса отклоняются, иначе сервер стал бы сканером собственной сети;
- размер запроса и значения ограничены и в приложении, и на обратном прокси;
- refresh-токен браузера живёт в httpOnly-cookie, access-токен — только в памяти вкладки: XSS уносит 15 минут, а не 30 дней;
- CSP, HSTS и запрет фреймов настроены на отдаче статики.
Ротация корневого ключа
Корневой ключ тоже не вечен:
OLD_ROOT_KEY=... NEW_ROOT_KEY=... bun run db:rekey -- --dry-run
Перешифровываются только ключи окружений и секреты подписи вебхуков — значения не трогаются, поэтому операция стоит порядка числа окружений, а не числа секретов. Прогон возобновляем и останавливается, если хоть один элемент не открывается ни одним из ключей.