Hosted Security Model
Status: design. Target: ADR-0025 at hosted launch.
Threat model summary
| ID | Threat | Likelihood | Impact | Mitigation |
|---|---|---|---|---|
| H1 | Adapter key exfiltrated from server disk | Medium | High | KMS-wrapped DEK; plaintext key never on disk |
| H2 | Tenant volume read by another tenant | Low | Critical | Physical isolation: 1 container, 1 volume per tenant |
| H3 | Compromised provider key used to drain founder's bank | Medium | Critical | Adapter keys are read-only by vendor convention; founder rotates on alert |
| H4 | Cockpit XSS injects edit on proposal branch | Low | High | Frontend escapes; backend re-validates path + policy; CSP strict |
| H5 | Backup bucket read by hosted operator | Medium | Critical | Founder owns the bucket; we hold a write-only role; restores are founder-initiated |
| H6 | Insider with prod access reads tenant repo | Low | Critical | Repo encrypted at rest with per-tenant DEK; operator access logged + signed |
Likelihood/impact are coarse — refine post-launch.
Encryption at rest
- Each tenant volume is backed by a per-tenant Data Encryption Key (DEK).
- The DEK is wrapped by the tenant's Key Encryption Key (KEK) in AWS KMS (or GCP Cloud KMS — cloud-agnostic via envelope encryption).
- The wrapped DEK lives in the tenant control-plane row; the unwrapped DEK lives only in container memory after KMS Decrypt.
- KMS Decrypt is gated on a tenant-id IAM condition; one tenant's IAM role cannot decrypt another tenant's DEK.
Secret store for adapter keys
- Founder enters keys via the cockpit Settings page or
lycato keys set(which forwards to a hosted endpoint over TLS). - Keys are encrypted with the tenant DEK before write.
- Stored at
secrets/<name>.encin the tenant volume — never on control-plane storage. - Decrypted in-memory at adapter-run time only; never logged.
- Rotation: founder writes a new value; old is overwritten. Audit
log records
{ name, action: rotated, at, by }— never the value.
Network posture
- All inbound is TLS 1.2+ via Caddy on the edge.
- The cockpit Fastify server binds 127.0.0.1 inside the container; only Caddy can reach it.
- Inter-container traffic on a Docker network with
--internalflag (no host network bridge). - Egress: per-tenant container can reach vendor APIs (Mercury, Stripe, …) and the LLM provider only. Outbound firewall enforces an allowlist.
Authentication boundary
- See
AUTH.md. Summary: passkey + magic-link, no passwords. - Session cookies are
HttpOnly; Secure; SameSite=Lax. - Sessions are bound to a tenant id. Cross-tenant requests get a 404, not a 403, to avoid tenant-existence leaks.
Audit
- Every
POST /api/filewrite, every approval, every secret rotation appends to.founder/audit/YYYY-MM.jsonlin the tenant volume. Founder-readable, never operator-rewritable. - Operator actions (container restart, backup restore) append to a SEPARATE control-plane audit log + emit to the tenant's audit log so the founder sees them too.
What's intentionally absent
- HSM-grade key custody. KMS is sufficient for the founder segment; HSM enters scope at enterprise tier (post v1.0).
- Per-document encryption. Volume-level DEK is the right granularity for the cost / complexity ratio.
- Customer-managed keys (BYOK). Possible in v1.x; not at launch.