# Threat model The initial deployment is for one operator organization and trusted developers. Namespaces and NetworkPolicies are authorization boundaries for ordinary container traffic; they are not a guarantee against hostile tenants, kernel exploits, host-network bypasses, or compromised cluster administrators. `managed-cloud` mode disables public TCP exposure; it does not change this trust boundary or supply account-level resource budgets. See [product modes](product-modes.md) for the shared Cloud service's launch requirements. ## Credential boundaries API keys contain a random 256-bit secret and lookup ID. PostgreSQL stores a SHA-256 verifier and a short nonsecret prefix, not the key. This is appropriate for uniformly random tokens; it is not a password-hashing scheme. Human session tokens are encrypted inside a server-authenticated, HttpOnly, host-scoped session cookie. The UI server validates session origin and forwards requests to one configured API. It does not implement orchestration. Do not share dashboard origin with untrusted application origins. The CLI requires verified HTTPS except loopback development. Environment credentials take precedence; missing CI context fails without prompting. Current human login uses browser consent and an explicitly documented mode-0600 file fallback. OS keychain integration remains release work. Installation proof is required before claiming the first owner, and the claim closes transactionally. The installer supplies their own identity. Passwords use bcrypt with bounded concurrency. GitHub/Google logins require verified provider identity and bound state; TOTP/recovery or verified WebAuthn ceremonies protect enrolled accounts. TOTP keys use separate authenticated encryption, recovery codes are consumed once, and provider MFA challenges have short lifetimes. Teams grant project roles; disabled users, role reductions and revoked sessions are rechecked on requests and before subsequent deployment effects. The installation GitHub token is a shared privileged integration. A global administrator must approve each application's repository before project deployers can read its source configuration. Switching repositories requires renewed approval. Writing a generated build workflow requires explicit browser administrator review; changing build settings invalidates installation approval before dispatch. Machine keys are named, expiring and scoped to project/environment, optionally application. Deployment/log keys cannot administer nodes or keys and cannot read secret values. The owning identity is rechecked, including disabling and permission reductions. Executing application code can expose that application's injected secrets: developers with deployment permission remain trusted for those bindings. Stored Kubernetes Secrets are copied only through explicit authorized service bindings; values are never returned by management listing endpoints. ## Workload boundaries Containers run without service-account token mounts, privilege escalation, host networking or host paths. Capabilities are dropped; seccomp defaults apply. Resource requests and limits, rollout deadlines and a bounded management work pool reduce accidental exhaustion. Image resolution rejects private/link-local registry destinations, validates digest payloads and checks Linux architecture. Registry credentials are scoped to project/environment and exact registry hosts. Token-realm restrictions and stripped cross-host redirect authorization prevent credential forwarding to arbitrary endpoints. Unknown fields are rejected. DNS and declared network ports are explicitly allowed. External egress excludes private, management and cloud-metadata ranges. `internal=true` removes ordinary external egress for services that have only internal network membership. Ordinary NetworkPolicies have host-traffic limitations; see ADR 0002. Image-pull, node administration and cluster API traffic are platform operations, not workload egress privileges. There is no claim of encrypted east-west application traffic. ## Kubernetes patches [Kubernetes patches](kubernetes-patches.md) are off unless an installation administrator enables them, and adding or changing one needs `kubernetes:patch`, so the workload boundaries above hold for every service by default. With them on, a patch is applied to the generated object and compared with it; anything that would weaken those boundaries is refused, including service-account tokens, host namespaces and paths, security contexts, images, added containers, Secret, ConfigMap and projected volumes, Secret-backed environment, Hakopod labels (which carry network membership), node-originated probe hosts, external Service exposure and ingress routing or TLS. What a patch can do is plain too: tolerations, node selectors and affinity steer pods onto any node whose taints the patch tolerates, other than Hakopod's own pools, and `haproxy.org/` ingress annotations change how HAProxy treats that one service's traffic. An administrator who enables patches accepts that people holding `kubernetes:patch` make those placement and proxy choices. Disabling the setting stops new and changed patches but leaves existing ones applied until they are removed. ## Custom hostnames [SaaS custom hostnames](custom-hostnames.md) prove control of a domain only by its DNS. A CNAME to a service's target names that service, so anyone holding `hostnames:manage` on the application can attach any hostname that already CNAMEs to its target, including a domain whose owner meant it for another tenant of the same SaaS. Hakopod cannot tell one SaaS's tenants apart; the SaaS must check that the customer asking owns the domain before calling the API, and its service should answer only for hostnames its own records know. Addresses do not name a service: every application shares the ingress addresses, so a domain whose A or AAAA records point at the installation could otherwise be claimed by whichever application asked first. A hostname that reaches the target only by address therefore also needs a TXT record at `_hakopod-challenge.` holding a random 32-byte token issued for that one hostname record and shown only to callers who can read that application. A certificate is requested only after these checks pass, and HTTP-01 succeeds only for a domain whose traffic reaches the installation, so a hostname pointing elsewhere is never routed or issued. A hostname is unique across the installation and cannot also be an application domain. Uniqueness applies from the moment a hostname is attached, before it verifies, so an application can hold a name it cannot prove, blocking others from attaching it, until it removes the hostname. ## Container daemon access `container_daemon` does not change the boundaries above. No host path is mounted, no privileged container is created, no capability is added, and no cluster credential is handed to an application. The daemon is reached over the network with mutual TLS using material the operator supplies, and the endpoint validator refuses sockets and filesystem paths, plaintext schemes, hostnames, loopback, link-local and metadata addresses, the installer's pod and Service networks, and the Kubernetes API server address read from the server's own configuration. Egress is opened to that one address and port, as a `/32`, and to nothing else. What it does mean is equally plain. A granted service can create containers on a daemon that is effective root on its own host, and what those containers do is governed by that daemon, not by Hakopod. Two services granted the same daemon reach it with equal authority and can see, stop and delete each other's containers. The platform cannot enforce any isolation inside that daemon: the daemon's own API is the boundary, and nothing in this platform sits inside it. Choosing which host runs the daemon, what it will accept, and which services may reach it is the operator's responsibility, and approving a binding accepts it. The capability is available self-hosted, and on managed cloud only behind the dedicated customer-owned BYO node; a shared workload runtime is refused. It is refused on previews, service transfers, actions pools, serverless services, jobs and scheduled jobs. Revocation takes effect on the next deployment, because the grant is resolved again every time and nothing is cached; already-running pods keep their projected certificate until they are replaced, so urgent revocation must happen at the daemon. See [container daemon access](container-daemon-access.md). None of this has been exercised against a real daemon on a real cluster. ## Remaining release risks Local K3s kubeconfig is an operator credential; keep `.local` out of version control. Local application ingress binds HTTP/HTTPS only on loopback. Public DNS, ACME renewal, custom-domain ownership, real private-registry pulls and external secret synchronization are not claimed operational by local tests. Arbitrary application logs can contain secrets; users with logs:read must be trusted accordingly. Audit and deployment retention, backup restoration, disk exhaustion, host installation and platform upgrades require dedicated verification before a production release.