Security & secrets
No stored secrets — references only, keys hashed, TLS terminated in front, the air-gap consent gate, and the dashboard auth modes. The security posture that lets an audit pass in one read.
Last updated
On this page
TokenTriage’s security posture follows from two invariants: no stored secrets and no heuristic identity. The result is a system where a leaked config file leaks nothing, and every attribution is defensible.
Secrets are references, never values
- Virtual keys and management tokens are stored as SHA-256 hashes, shown once at mint, and the key prefix is stripped before the upstream forward.
- The Helm chart renders zero
Secretobjects. Provider keys flow viasecretKeyRefto canonical env names; the plaintext exists only on your terminal, and only its hash is written to the pod’s PVC. The Terraform analog uses ARNs, never values; the Compose analog keeps the value in a git-ignored.envwhile the config names it.
TLS terminates in front
The data plane serves plaintext HTTP by design — terminate TLS in front of it (bearer
virtual keys belong on TLS). The governance.keys.tls_terminated_upstream field is an
operator attestation that TLS terminates in front (not a probe); running with bearer keys
and no attestation is warned.
The air-gap consent gate
Every phone-out is gated. External state (Redis, Postgres, an L3 cache), an external
embeddings API, and OTLP export are all refused unless their config block carries external: "allow". CI asserts the dialed-host set is a subset of the configured upstreams — so no
deployment layer can silently un-gate the air-gap. The default posture dials nothing.
Dashboard auth
The dashboard supports three auth modes (dashboard.auth.mode):
off— requires a loopback admin bind (else it’s a validation error).token— bearer tokens:tta_management tokens (scoped, minted via the API),ttk_bootstrap bearers (role-based, minted on the host withtokentriage token new), andtts_share tokens (read-only, scoped views).users— local users with argon2id password hashes (tokentriage user add), with a session cookie and a CSRF token bound to the session.
Content capture (the verbatim prompt/response bytes) is off by default (dashboard.capture.*);
when enabled, it sits behind a dedicated privileged scope with one audit row per read
attempt.
Everything is auditable
Probes report process-up only (GET /health). Errors are RFC 9457
application/problem+json with a request_id. The audit trail is
append-only — one row per attempted mutation and per denied request, carrying the actor, the
action, a content hash of any diff, and the config generation. A credential that appeared in
a request path is redacted before the row is written.