Tinycloud’s threat model assumes the application code is untrusted. It was generated by an assistant from a prose description, and nobody read it line by line. Every guarantee below is designed to hold anyway.

Boundaries

Gets: its own artifact, its own state directory, its declared secrets, its granted capabilities, its own database, its own storage namespaces.Cannot: read another app’s anything. It runs as UID 1000 inside a Firecracker microVM with its own /30 TAP network, and MMDS is blocked before tenant code starts. Guest-originated traffic reaches only the host’s control-plane port; all other forwarding is dropped.Cannot: mint a token. The guest holds only the platform’s Ed25519 public verification key.Cannot: discover another namespace or database, even by name. The workload token resolves to exactly one binding.
Gets: a disposable VM with a firewall hole to the package registry.Cannot: reach the broker, the data service, a private network, or any neighbour. MMDS is firewalled off before build code executes. The VM is destroyed after every build.Cannot: attack the host kernel’s filesystem parser — output leaves as one tar file read with debugfs, never by mounting an untrusted ext4 image.Cannot: smuggle a credential into the artifact — output is re-scanned and credential-shaped files are stripped.
Cannot: assert an identity. Client-supplied Tiny-* platform headers are stripped at the gateway before proxying, and identity comes from a short-lived token the gateway mints and the guest verifies.Cannot: replay a token across apps. Every token carries an audience, so one minted for one boundary is rejected at another.
Gets: that deployment’s database and namespaces, for as long as it is live.Cannot: outlive the route. A superseded deployment’s token stops working the moment traffic moves — so a rollback also revokes.Cannot: name another environment’s resources. The token is the binding selector.
Cannot: decrypt it elsewhere. Secrets are sealed with AES-256-GCM under a key derived per context, so a ciphertext cannot be moved between organizations.Cannot: enumerate across tenants. Every organization-scoped read takes an organizationId and filters in the query; a cross-organization ID returns 404, not 403.
Cannot: widen an egress allowlist. The host resolves each declared hostname once, at deploy time, and pins the addresses into that VM’s forward chain.Cannot: point egress at the host’s own network. A hostname resolving into a private range is refused outright — that would be lateral movement, not egress.

Defense in depth in the guest

Firecracker itself does not filter network traffic. Removing or bypassing the nftables setup is a security failure, not a degraded mode — the backend fails the deployment if TAP or nftables creation fails.

Defaults that have to be turned off deliberately

Two independent acts are required for the first two. That is the pattern: nothing that widens exposure happens because one person made one choice.

Approval gates on blast radius

Deploys are ungated by default; approval attaches to change that widens blast radius in production — a new capability, a new raw secret, a new outbound host, widened visibility, or a destructive migration. The reasoning is in ADR 0004, and the mechanics in Approvals.

Auditability

State changes and their audit events are written in the same transaction, so the audit log cannot disagree with the state it describes. Policy decisions return a code and a reason rather than a boolean, so the why reaches both the log and the caller. Anonymous application requests are metered but not audited one row per request — there is no identity to record, and a public app’s volume is not bounded by headcount. Denials, deploys, and every control-plane action are audited.

Supply chain

The workspace has zero runtime dependencies. SQLite, crypto, HTTP, and the test runner all come from Node’s standard library; the YAML and JSON Schema implementations are hand-written and deliberately partial. A platform whose job is to run other people’s code should be hard to compromise through its own supply chain. See ADR 0003.

Known gaps

Stated plainly, because a security model that hides its gaps is not one:
  • Secrets are sealed under a local key, not a KMS. Losing TINY_SECRET_KEY loses everything sealed under it; leaking it compromises all of it.
  • Artifacts are not signed and there is no SBOM.
  • Objects and databases are not replicated. A lost host disk loses both.
  • Sleep is a pause, not a snapshot — isolation-preserving, but it retains guest memory rather than being true scale-to-zero.
  • Alert delivery is not implemented. Alert intent is recorded, not acted on.
  • The control-plane store is SQLite, without row-level security.