SECURITY.md: Sentin8 Platform — Security Model & Post-Quantum Architecture
This document is canonical and identical across every repository of the Sentin8 tree. It describes the security posture of the platform, not of any single service: every service — platform or vertical — inherits this model by construction, not by configuration.
Audience: security engineers, auditors, compliance officers, architects, and anyone evaluating what Sentin8 guarantees. For per-service architecture read that service's
README.md; for wire-level detail read itsDESIGN.md.
1. Executive Summary — The Security Model in One Page
Sentin8 is a post-quantum, zero-static-secret, live-authorization platform. It was designed for regulated, multi-tenant, multi-vertical operation (finance, hospitality, health) where the threat model includes nation-state harvest-now-decrypt-later collection, insider privilege escalation, and credential theft — not just opportunistic web attacks.
The model rests on five pillars:
- Post-quantum cryptography, exclusively. Every TLS connection in the platform — client→ingress, proxy→service, service→service, service→data — negotiates X25519MLKEM768 key exchange with ML-DSA-65 certificate signatures, anchored by an SLH-DSA-SHA2-128s (hash-based) root CA. The default TLS mode is exclusive: no classical fallback exists. Recorded traffic is quantum-resistant today; there is nothing to "migrate later".
- One identity plane. Sentin8:Auth is the only token issuer, the only JWKS authority, and the only authentication authority in the platform. No service mints its own sessions, tokens, or credentials — so there is no second issuer to compromise and no fragmented trust to reconcile.
- Live authorization — Shield. Authorization is decided per request by one embedded policy engine (Shield) that unifies RBAC, ABAC, and ReBAC. Roles are never embedded in tokens: they are resolved live from the identity plane on every request. A demoted administrator loses access on the next request — not at token expiry, not at midnight.
- Mandatory tenant isolation in the database itself. Every tenant table
carries
ENABLE+FORCE ROW LEVEL SECURITY; no role on the cluster carriesBYPASSRLS— not the event relays, not the schedulers, not anyone. Services connect as OAuth-authenticated per-service roles scoped to their own database and schema. Isolation is not an application convention; it is enforced by PostgreSQL on every row read. - Certificate-only workload identity — zero static secrets. A workload's
only identity is its Acme-issued client certificate (
CN=m2m-{workload}) presented in the TLS handshake. There are no client secrets, no static API keys, no password files anywhere in the service-to-service plane. A stolen configuration file grants nothing; a stolen database dump decrypts nothing.
Wrapped around the pillars is a fail-closed discipline: an unresolvable
hostname is a 421, not a guess; an unparsable policy set at boot is a
refused startup, not a warning; a role-resolution outage is a deny, not an
allow; an unauditable action is an action that never happened.
2. Security Architecture at a Glance
Every request into the platform walks the same eight-step pipeline. The steps are mandatory and ordered; no handler may skip one:
The properties this buys:
- The proxy never sees application plaintext. It is an L4 SNI router; TLS terminates inside the owning service, in-process.
- Context headers cannot be spoofed. The TLS layer strips every
client-supplied
X-Sentin8-*header before injecting its own authoritative values; an unresolvableHostis rejected with421 Misdirected Request. - The decision is never stale. Steps 5–6 consult live identity state and live policy data on every request.
- The database is the last wall, not the only one. Every handler also
carries explicit
WHERE env_id = $Nclauses — RLS is the backstop, defense-in-depth is primary.
3. Pillar I — Post-Quantum Cryptography
3.1 The Algorithm Suite
| Layer | Algorithm | Standard | Security basis |
|---|---|---|---|
| TLS 1.3 key exchange | X25519MLKEM768 (hybrid: classical X25519 ⊕ ML-KEM-768) | FIPS 203 | Lattice KEM + classical ECDH; breaking the session requires breaking both |
| Service & client certificate signatures | ML-DSA-65 | FIPS 204 | Lattice signatures (Category 3) |
| Root CA signature | SLH-DSA-SHA2-128s | FIPS 205 | Hash-based — secure even if lattices fall |
| Token / claims signing (JWKS) | PQ signing key families, coordinated rotation | FIPS 204 lineage | JWTs, JAR request objects, JARM responses, SD-JWT credentials |
| Symmetric envelope encryption | AES-256-GCM (key-versioned) | FIPS 197 / NIST SP 800-38D | Secrets store, BFF session store |
| Disclosures & bindings | SHA-256, PKCE S256, RFC 9449 DPoP | — | Selective-disclosure digests, code challenges, proof-of-possession |
| MFA (TOTP) | HMAC-SHA1, RFC 6238 (30s step, 20-byte secrets, stored encrypted) | — | The RFC standard; secrets never stored in the clear |
Two deliberate design choices deserve emphasis:
- The trust anchor is hash-based, not lattice-based. SLH-DSA-SHA2-128s (the SPHINCS+ family) derives its security from hash functions alone. Even a future cryptanalytic breakthrough against lattice assumptions leaves the root CA sound. The root is signed rarely, so its slower signing cost is irrelevant — the conservative choice costs nothing.
- The key exchange is a true hybrid. X25519MLKEM768 combines classical
X25519 with ML-KEM-768 such that an attacker must defeat both to recover
session keys. In
exclusivemode this hybrid is the only group offered — there is no classical-only downgrade to attack.
3.2 Exclusive by Default — No Classical Fallback
The platform's TLS posture is controlled by a single variable,
SENTIN8_TLS_PQ_MODE:
| Mode | Key exchange | TLS versions | Purpose |
|---|---|---|---|
exclusive (default) | X25519MLKEM768 only | TLS 1.3 only | Full PQ; no fallback group exists to negotiate down to |
hybrid | X25519MLKEM768 first, classical groups offered | TLS 1.3 only | PQ-preferred, legacy-client compatibility |
classical | Classical groups only, no ML-KEM | TLS 1.2 + 1.3 | Emergency rollback only — explicit, loud, temporary |
The default posture means the classic downgrade attack — get the handshake to agree on a classical group, record, decrypt later with a quantum computer — has no surface. A client either speaks post-quantum or fails the handshake.
Harvest-now-decrypt-later is dead on arrival against Sentin8 traffic.
3.3 The Certificate Authority — Sentin8:Acme
The platform runs its own RFC 8555 ACME certificate authority, because
public CAs cannot sign {service}.{env}.{org}.{tld} internal hostnames —
and because the post-quantum certificate ecosystem is young enough that the
platform refuses to wait for it:
- Two-tier hierarchy — SLH-DSA root → ML-DSA intermediate → leafs, for both server certificates (HTTPS on every domain) and client certificates (workload identity, below).
- Trust distribution — the root CA is installed into the OS trust store and the Chromium/NSS store at bootstrap; every process in the tree, and every headless browser test, verifies the same anchor.
- External domains — proxied to a public ACME provider; internal names never leave the boundary.
- Admission control — mint endpoints are shed-load protected: a fleet-wide
cold start answers
503 + Retry-Afterinstead of silently timing out. Saturation is loud, deterministic, and self-draining — never a soft security failure. - No
danger_accept_invalid_certs, anywhere. Verification failure is a failure; there is no test-only bypass switch to leave switched on.
3.4 Memory-Safe Cryptography
The entire TLS stack is pure Rust — rustls over the Graviola provider,
no C, no assembly blobs, no cmake-built vendor code. The historical lesson of
OpenSSL-class CVEs (Heartbleed-class memory disclosure in the handshake
path) is addressed by language, not by patching discipline: the crypto layer
cannot have a buffer overflow because the language forbids it. unsafe
blocks in the security path would be a review-blocking defect.
4. Pillar II — The Identity Plane (Sentin8:Auth)
4.1 One Issuer or None
Sentin8:Auth is the platform's only issuer of tokens, the only JWKS authority, and the only authentication authority. Every other service verifies what it issues; no service mints sessions, API keys, service credentials, or JWKS documents. The compromise question "which issuer was forged?" has one answer and one place to harden, rotate, and audit.
4.2 Token Families — the Right Credential for the Right Job
| Family | Use | Security properties |
|---|---|---|
| User access token | Any authenticated human access | Shield-decided; scopes are a coarse pre-filter only |
| ID token | OIDC authentication | Issued by the single authority |
| Refresh token | Session continuity | Rotated and revocable; held server-side by the BFF |
| M2M (workload) | Machine-to-machine | Certificates are the workload identity; tokens, where used, are certificate-bound (RFC 8705 cnf) — a stolen token without the private key is useless |
| API-client | Third-party integrations | Scoped, registered, auditable |
| Actor token | Administrative impersonation | Time-boxed and audited; support actions are attributable |
| Delegated token | Cross-context trust via the Delegation Gateway | Carries an act chain claiming the originating user; downscoped per target service by the BFF — never broad-scope passthrough |
Two structural rules govern all families:
- No realm tag. A user is a user. What distinguishes a platform operator from a bank teller from a hotel guest is RBAC assignment, not a token flavor — so there is no "realm confusion" class of vulnerability.
- No roles in the JWT. The token carries identity and coarse
scopeonly. Roles are resolved live at authorization time (§5). Thescopeclaim is a pre-filter and an RLS context feed — never the decision.
4.3 Security Profiles — FAPI-Grade OAuth by Default
Sentin8:Auth enforces per-client security profiles — a named bundle of
required mechanisms, evaluated at the authorize, token, and introspection
endpoints. Profiles are data (rows in the auth store), not build flags, so a
regulated tenant can run fapi_2_message_signing while a partner runs a
baseline profile — on the same deployment.
| Profile | PAR | JAR | DPoP | mTLS | JARM | iss | Signed introspection (RFC 9701) |
|---|---|---|---|---|---|---|---|
standard / fapi_1_baseline | — |