Security & Tenant Isolation¶
A synthesis page. Security in Starform is not one feature — it's the sum of five mechanisms, each owned
by a canonical section elsewhere. This page ties them together; the linked sections remain
authoritative. (Design record: specs/2026-07-05-compute-substrate-and-isolation-design.md.)
The five pillars:
- Network isolation — namespace-per-project + label-selector NetworkPolicies (§20.4).
- Query isolation — a server-side tenant filter on all telemetry the client cannot override (v2 Read & query / FR-065).
- Encryption at rest — AES-256-GCM on all sensitive Postgres fields (FR-060), and Cloudflare Origin Certs in transit with no cert-manager (§5).
- Ingress auth — per-cluster bearer tokens on telemetry ingress, even over private VPC peering (FR-066 / FR-071).
- Workload & kernel isolation — gVisor sandbox for customer code + a hardened PodSpec (FR-082 / FR-083).
Network isolation — namespace + NetworkPolicy¶
Tenant isolation is namespace-per-project plus label-selector NetworkPolicies — not the VPC.
Clusters in a region share one /16 VPC (§4.4); isolation
is enforced inside the cluster, so sharing the regional VPC is safe.
For each environment, Shuttle creates a label-selector NetworkPolicy in the project namespace — the policy template and apply rules are owned by §20.4. The posture it enforces:
- All external egress allowed on any port; the entire private estate denied by one
except 10.0.0.0/8; the environment's own dedicated DB endpoints allowed back as per-/32rules rendered from desired state. - Same environment means same environment, same cluster — there is no private networking across regions (FR-078).
- Cross-environment traffic stays denied unless the project opts in via
allow_cross_env_networking(FR-079, warned when a protected environment is involved). - The
starform.io/environmentlabel is therefore load-bearing for isolation — see the label catalog §24.1.
Failure mode → loud, not silent (pre-launch-blocking)
If Shuttle fails to apply the correct starform.io/environment label on a pod, that pod loses its
NetworkPolicy protection. Mitigation: a Kyverno admission policy (post-MVP) rejects any pod without
the required label set, turning a silent failure into a loud one
(§20.4; enforcement tracked in
§39.2).
Query isolation — the server-side telemetry filter¶
Every customer metric and log query is scoped by a tenant filter injected server-side from the authenticated session, as bound parameters the client cannot supply or override. Starbase acts as the telemetry-query broker: it routes each query to the region(s) hosting the environment's services (fan-out + merge when an environment spans regions, FR-071) and injects the filter before the store ever sees the request (v2 Read & query).
- FR-065 — all customer metric/log queries MUST be scoped by a server-side tenant filter; the client MUST NOT supply or override it.
- SC-016 — customer queries never return another tenant's data (verified by an isolation test in CI).
The filter keys off the tenant tuple — project_id + environment + service_id, with workspace_id
as the billing boundary — the same identity every series and log line is attributed to
(§24.1).
Encryption — at rest and in transit¶
At rest (AES-256-GCM). All sensitive data in Postgres is encrypted with AES-256-GCM using a single
key from the ENCRYPTION_KEY env var; key rotation is a post-MVP batch job. The encryption-at-rest
catalog (§39.1 item 18) covers every encrypted field:
| Encrypted field | Requirement |
|---|---|
| Var Group entry values (§38.2) | FR-036 |
| Database credentials | FR-028 |
| Storage (bucket) credentials | FR-033 |
| GitHub installation tokens | FR-060 |
| Cloud-provider credentials / kubeconfigs Starform holds for its own clusters | FR-060 |
FR-060 mandates encryption for all sensitive data at rest; SC-011 requires those credentials never appear in logs or API responses.
Customer-cluster etcd (at rest). Shuttle renders Var Group values into K8s Secrets, which live in each customer cluster's etcd. Because those clusters run untrusted co-tenant code:
- etcd is encrypted at rest via Talos LUKS2 disk encryption (STATE + EPHEMERAL, AES-XTS-256, key sealed to the node) enabled in the bootstrap machine-config — a distinct mechanism from the app-level AES-256-GCM above (which protects the Postgres source-of-truth). It closes stolen-disk / DO-volume-snapshot / offline-etcd exposure.
- secretbox
EncryptionConfiguration(encrypting the Secret objects within etcd, so even a live etcd read returns ciphertext) is pre-launch defense-in-depth (§39.2 #68). - Neither stops a live gVisor-escape from reading a secret mounted into a running pod — that is the sandbox's job (isolation pillar above).
In transit (Cloudflare, no cert-manager). TLS terminates at the Cloudflare edge; Cloudflare → origin
is encrypted with a free Cloudflare Origin Certificate (15-year validity, stored as a K8s Secret on
the Gateway listener). *.starform.app subdomains use Cloudflare's wildcard edge cert.
No cert-manager — at MVP or after — see Networking › Traffic & TLS §5.
(Custom domains arrive post-MVP via Cloudflare for SaaS custom hostnames — Cloudflare issues and
renews those certs too, §39.3 #27.)
Ingress auth — per-cluster bearer tokens¶
Customer telemetry ships over the private network (intra-region VPC), authenticated with a per-cluster bearer token validated at the regional OTel Collector gateway — both ingest doors, app logs and access logs — even on the private link. This is defense in depth: a single compromised cluster cannot spoof another's data.
The telemetry store is VPC-private (no public IPs). Starbase reads it only through the
authenticated front door — a read-only ClickHouse user (SELECT only; the single front door) —
over the peered link, and always injects the server-side tenant filter on that read path.
- FR-066 — ship customer telemetry over the private network, authenticated with a per-cluster bearer token at the telemetry ingest (v2 Collect).
- FR-071 — serve cross-region telemetry reads over the private network through an authenticated store front-door (a read-only ClickHouse user), never a public endpoint, applying the server-side tenant filter on that read path.
Workload & kernel isolation — gVisor + hardened PodSpec¶
Customer code is untrusted. Two layers contain it, on the self-managed Talos substrate (Kubernetes Runtime):
gVisor sandbox (FR-082). Customer web and worker
pods run under the gVisor RuntimeClass (runtimeClassName: gvisor) — a user-space kernel that intercepts
syscalls, so a container escape hits gVisor, not the host kernel. Platform components and Starform-rendered
Mininova database pods run under runc.
Hardened PodSpec (FR-083), applied to every customer pod as defense in depth:
securityContext:
seccompProfile: { type: RuntimeDefault }
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
add: ["CHOWN", "SETUID", "SETGID", "FOWNER", "DAC_OVERRIDE", "NET_BIND_SERVICE"] # NET_RAW dropped
automountServiceAccountToken: false # pods get no Kubernetes API credentials
# no hostNetwork / hostPID / hostIPC / hostPath; root-in-container allowed (image compat, declawed by the above)
Enterprise tier — dedicated nodes. Enterprise customers get dedicated Talos node groups (taints + tolerations), so their pods share a kernel only with their own workloads (post-MVP).
Shared-kernel risk register
Even with gVisor, tenants share a host kernel. A container-escape 0-day/N-day in gVisor or runc could reach co-tenants on the same node (not the fleet — NetworkPolicy, no K8s token, and blocked metadata egress bound lateral movement). gVisor makes escape much harder; DO patches node kernels; dedicated nodes remove co-tenancy for those who pay. The public untrusted tier also depends on abuse controls (payment gate + CPU/memory limits + mining detection, FR-084) — the defense against resource abuse, which sandboxing does not address.
Authentication & authorization (who can act)¶
Dashboard login is SSO via GitHub + Google at MVP — distinct from the deferred customer auth primitive (a feature Starform offers customers' apps, post-MVP — §39.3 #30). Authorization within the dashboard is the two-tier RBAC model §15: workspace roles + project roles + the environment-protection flag.
Cross-references
Network isolation → §20.4 · isolation labels → §24.1 · server-side query filter → v2 Read & query / FR-065 / SC-016 · encryption-at-rest catalog → §39.1 #18 / FR-060 · TLS / Origin Certs → §5 · ingress + cross-region read auth → FR-066 / FR-071 (v2 Collect) · workload/kernel isolation (gVisor + hardened PodSpec) → Kubernetes Runtime / FR-082 / FR-083 · RBAC → §15. Canonical map: Canonical Sources.