Security
What the system guarantees, what it doesn't, and why.
Where the boundary is
At rest — ciphertext only. Values are encrypted with XChaCha20-Poly1305 under the environment's data key; the data key itself is stored encrypted under the root key, which lives in the operator's environment or in an external KMS. A stolen MongoDB dump is useless without the root key.
In flight — depends on the path:
- the machine path is honestly client-side: the server hands the SDK ciphertext and the data key wrapped with x25519 under the specific identity's key. In this scenario the server never produces the plaintext value at all;
- showing a value to a human is decrypted on the server and delivered over TLS.
That is why we don't call the system zero-knowledge. A live process has access to values while processing them, and promising otherwise would be untrue — this is exactly the attempt Infisical stumbled on when its dashboard and integrations needed plaintext.
Machine authentication
Nothing is permanent: a client secret can be revoked, an access token has a TTL of minutes, an enrollment token has minutes and exactly one use.
A repeated exchange of an enrollment token means it has leaked. The system
does more than refuse: the token capture is atomic (compare-and-set), and
the losing attempt raises an enrollment.reuse-detected event and a webhook
to the administrators.
Audit
Two streams: changes and accesses. Every read of a secret — by a human or by a machine at startup — is recorded. Events are linked by a hash chain at the organization level, so an entry cannot be deleted or rewritten unnoticed.
There are no values in the log: only key names and the value's HMAC. That is enough to see "the value changed", and not enough to learn what it is.
Perimeter
- login and token exchange are rate-limited — per IP and per subject;
- a webhook address is validated twice: when saved and before every delivery; internal and loopback addresses are rejected, otherwise the server would become a scanner of its own network;
- request and value sizes are limited both in the application and at the reverse proxy;
- the browser refresh token lives in an httpOnly cookie, the access token only in tab memory: XSS steals 15 minutes, not 30 days;
- CSP, HSTS, and frame denial are configured on static asset delivery.
Root key rotation
The root key is not permanent either:
OLD_ROOT_KEY=... NEW_ROOT_KEY=... bun run db:rekey -- --dry-run
Only environment keys and webhook signing secrets are re-encrypted — values are untouched, so the operation costs on the order of the number of environments, not the number of secrets. The run is resumable and stops if any single item cannot be opened by either key.