Security controls — ZevCloud¶
The high-level security posture. This page covers the technical controls; people-and-process controls (access reviews, on-call rotation, incident response) sit with the platform's security architecture and incident response pages.
Encryption at rest¶
| Data | Mechanism |
|---|---|
| Database storage (all of ZevCloud's tables) | Neon-on-AWS encrypts the underlying storage volumes |
Service environment variables marked isSecret: true |
AES-256-GCM via the field-crypto service; ENCRYPTION_KEY env var. Plaintext is write-only after the flag flips — every read path masks regardless of caller |
| Domain registrant contacts + EPP auth codes | AES-256-GCM via the same field-crypto service |
| API keys | Stored as bcrypt-style hashes; plaintext shown once at creation, never persisted |
Encryption in transit¶
- All inbound traffic terminates at Cloudflare with TLS 1.2+
- Cloudflare → origin (Hetzner Ashburn backend) is TLS-encrypted
- Backend → external vendors (Neon, NameSilo, NIRA EPP, Cloudflare API, Forward Email, ZeptoMail, Better Stack) is TLS by default; the EPP channel to NIRA uses mTLS
Authentication + authorisation¶
- Customer auth — ZevID OAuth. ZevCloud never holds customer passwords. MFA enforcement is ZevID's responsibility (the customer's per-account security settings on
accounts.zevop.comapply). - Admin auth — same ZevID OAuth flow, additionally gated by a server-side RBAC permission list. Permissions enumerate at
admin-permissions.ts. Every admin endpoint declares its required permission via the@RequireAdminPermission(...)decorator. - Programmatic (API key) auth — per-key scope check. Hashed keys; plaintext shown once.
- Cross-product (ZPIP) auth — service+user tokens issued by ZevID with per-scope user consent. Inbound ZPIP endpoints are host-isolated to
service.zevcloud.net(theINTEGRATION_DOMAIN), separate from the public dashboard host. Wrong host returns 404, not 403 — the route's existence is not leaked.
Tenant isolation¶
- Every customer-resource row is scoped by
team_idat the query layer. The team-membership check happens in middleware before the controller runs. - Customer workloads (the apps they deploy) run in per-service Coolify containers on Hetzner Falkenstein. Each container has its own filesystem + env namespace.
- Customer DNS zones are separate Cloudflare zones; cross-tenant DNS reads/writes are blocked by the zone-id boundary.
Platform self-protection¶
- A hardcoded
PROTECTED_DOMAINSlist refuses any management operation (attach / register / DNS write / email-forwarding) on internal Zev infrastructure domains (zevop.comand subdomains today). The gate is checked at every write entry point AND in the DNS reconciler — a row that somehow exists can't bypass it. - A foreign-MX conflict guard refuses to enable email forwarding on a customer domain whose MX records already point at another mail service (Microsoft 365, Google Workspace, etc.) without explicit "yes, replace" confirmation listing the existing service.
- Forward Email domain registration sends
catchall: falseto prevent the platform's auto-catchall behaviour silently routing every customer's mail to our internal inbox. A defence-in-depth sweep deletes any auto-created aliases on first toggle-on and alerts ops if it ever catches one.
Audit logging¶
- Admin actions against customer resources write to
admin_audit_logs(actor admin id, target resource, before/after where relevant). - Inbound ZPIP requests + outbound ZPIP webhook deliveries are recorded for replay-protection + audit.
- Customer-facing security-event logging (login, MFA, etc.) is owned by ZevID — see ZevID's section.
Secret handling¶
- All secrets are passed via environment variables; nothing is checked in.
.env.examplelives in the repo as a key catalogue (no values). - Once a setting carries
isSecret: true, the plaintext is write-only end-to-end. The admin UI shows masked / "set" indicators; the API never returns plaintext, regardless of caller.
What's not yet in place¶
Honest about gaps:
- No automated retention purge cron today. Data is retained for the lifetime of the team/service. See
retention.md. - No formal pen-test schedule. We rely on internal review + targeted ultrareview passes when shipping security-sensitive surface. The DPO will work with engineering on a formal schedule when needed.
- No SIEM beyond Better Stack's log aggregation. Detection-and-response is reactive against logs + Sentry events.
These gaps are expected at our current stage; they're listed so the DPO knows what to ask about as the company grows.