Skip to content

Search docs

Search the Skiffly documentation

Security

How tenant code is isolated, how secrets are stored, what the network allows, and how shell access and API tokens are controlled.

Isolation#

Every service runs in its own sandbox on shared nodes:

  • gVisor (default runtime) intercepts syscalls in user space, so the container never talks to the host kernel directly.
  • Kata Containers run a service in a lightweight microVM with its own kernel. Builds always run in Kata; pick it for services that need a full kernel (runtimeClass: kata).
  • Each project environment is a Kubernetes namespace with a resource quota. Memory is never overcommitted; CPU can be shared up to 3×; disk I/O is capped per namespace.
  • Only our system components run with a plain container runtime.

Nodes run in Hetzner data centres in the EU (Finland and Germany); more regions later. Data does not leave the EU.

Network#

  • Namespaces are isolated with a default-deny network policy: services in different projects cannot reach each other; services in different environments of the same project cannot either.
  • Inbound traffic reaches a service only through its public domains (TLS terminated at the edge) or a TCP proxy you created explicitly.
  • Outbound traffic is open except port 25 (SMTP): use an email API or a provider's submission port (587) with authentication.
  • Nodes accept inbound connections only on 80, 443, SSH for operators and the TCP proxy range.

Secrets and variables#

Variable values are encrypted at rest with AES-256-GCM; the key lives only in the control plane's environment, not in the database. Values are decrypted when a deployment is rendered and passed to the node over an authenticated WebSocket the node opened itself (nodes have no inbound API port). The dashboard shows values to workspace members; the CLI and the MCP server mask them by default.

Build logs and runtime logs are stored as your process wrote them: do not print secrets.

Accounts and tokens#

  • Sign-in is GitHub OAuth only; there are no passwords to leak.
  • Workspace roles: owner, admin, member. Billing, tokens and members require admin.
  • API tokens are hashed at rest, shown once, scoped to one workspace with read or write, and can be given an expiry. Project tokens are scoped to one environment and cannot read billing or other projects.
  • Every mutation is written to the workspace audit log with actor, target and IP.

Two-factor authentication and sessions#

  • Email. The address comes from GitHub and is verified automatically when GitHub has verified it. If you change it in Settings → Security, we email a 6-digit code (valid for 15 minutes, 5 attempts). Until the new address is confirmed no notifications are sent to it, and trial and referral credit stay on hold.
  • 2FA (TOTP). Turn it on in Settings → Security: scan the QR code with any authenticator app (Google Authenticator, 1Password, Authy…) and enter the code it shows. You get 10 recovery codes, shown once — download the .txt or store them in a password manager; each one signs you in a single time if you lose the device. Enabling or disabling 2FA signs out every other session. Platform administrators must keep 2FA on.
  • Signing in with 2FA. After GitHub you land on a second step that asks for the current code (or a recovery code). Until it is entered the session cannot use the API.
  • Sensitive actions ask again. With 2FA on, creating API or CLI tokens, authorising an MCP client, changing the email address, turning 2FA off, deleting a workspace or a project with volumes, and changing the spend limit or auto top-up ask for a fresh code once every 10 minutes ("sudo mode"). Tokens issued to the CLI and MCP clients are not affected.
  • Sessions. Settings → Security lists every browser signed in to your account with IP, device and last activity. Revoke one, or use Sign out everywhere to keep only the current device. The session cookie is HttpOnly, Secure and SameSite=Lax, and expires after 30 days.

Shell access#

skiffly ssh and the Terminal tab open a shell inside a running container of your service. The session goes from your client to the API over WebSocket with your token, and from the API to the node over the node's own connection; the container is never exposed to the internet. Sessions require the member role (and the write scope for tokens), are limited to five per user and 30 minutes idle, and are recorded in the audit log as service.exec.

GitHub#

Private repositories are cloned with short-lived installation tokens of the Skiffly GitHub App; no personal access token is stored. Pull requests from forks never deploy into preview environments.

Backups#

Volumes are snapshotted daily to encrypted, EU-hosted object storage with a 7/4/3 retention (daily/weekly/monthly); Postgres and MySQL get logical dumps in the same run. A backup can be restored in one click from the dashboard, with skiffly backups restore or MCP restore-backup; a safety backup is taken first. See Volumes & databases.

Reporting#

Found something? Write to security@skiffly.dev. Please do not test against other customers' services.