# Security model

seekrit's guarantee is simple: **the server never has the information needed to read your
secrets.** This page states that precisely.

## What the server stores

- Secret **ciphertext** (AES-256-GCM blobs).
- Environment DEKs, but only **wrapped** to principals' public keys — never in the clear.
- Users' **public keys**, and their **private keys only in passphrase-encrypted form**.
- For service tokens: a **SHA-256 hash** of the token and the token's **public key**.
- Metadata: org/app/environment/secret **names**, versions, timestamps, roles, and the audit log.

## What the server never sees

- Plaintext secret values.
- Any DEK in the clear.
- Any user's private key in the clear.
- Any passphrase.
- The full service token string (only its hash).

## Threat model

**A full database compromise** yields ciphertext and metadata. An attacker learns that
`STRIPE_KEY` exists in `acme/storefront/production` and when it changed, but not its value. To
decrypt, they would additionally need a principal's private key, which the database does not
contain.

**A compromised or malicious server** can deny service, tamper with metadata, or serve altered
ciphertext — but AES-GCM authentication means tampered ciphertext fails to decrypt rather than
producing wrong plaintext silently. It still cannot read secret values, because it never holds
the keys.

**A leaked passphrase** exposes that user's private key (and therefore every environment granted
to them). Treat passphrases like the master credential they are.

**A leaked service token** grants whatever environments were wrapped to it. Revoke it; rotate the
affected environment keys.

> **Note:** Names are metadata, not secrets. Don't encode sensitive information in org, application, environment, or secret **names** — only in secret **values**.

## Audit trail

Every write, grant, revocation, and token action is recorded in an append-only audit log,
attributed to the acting user or service token, with contextual metadata (never secret material).
The API exposes no update or delete path for audit entries. This gives you an after-the-fact
record of who touched what, even though the server can't read the secrets themselves.

High-volume environment resolves (`GET /v1/resolve`, called on every app or CI
startup) are the deliberate exception: they are metered for usage and billing
rather than written to the audit log per call, so the log stays a signal of
change and access-control events, not a firehose of identical machine reads. A
*denied* resolve — a principal without a key grant — is still audited.

## Transport and authentication

- All API traffic is authenticated: a Stytch session JWT (verified locally against Stytch's
  JWKS), a service token, or a local dev header.
- Session JWTs are validated for issuer, audience, and expiry.
- Service tokens are matched by hash and checked for revocation and expiry on every request.

## Responsible disclosure

seekrit is early-stage software. If you find a vulnerability, please report it privately through
the repository's security policy rather than opening a public issue.
