Dock Docs

Threat model

What Dock protects, what a Dock can and cannot reach, and where the known gaps are.

This page describes what the code does today. It is not a certification and not an audit. Where a protection is planned but not shipped, the page says so. To report a vulnerability, see SECURITY.md.

Assets and actors

AssetWhere it livesWho should reach it
Dock workloadsA Firecracker microVM or a gVisor container on a hostUntrusted: the code inside a Dock is assumed hostile
Dock disks and SnapshotsOn the host; Snapshots in the object store (MinIO or any S3-compatible bucket)The organization that owns the Dock
API keysSHA-256 hashes in Postgres; the raw value is shown onceThe user who created the key
Dock-bound keysSame table, tied to one DockThe Dock they were issued to
Provider keys, Environment secretsEncrypted at rest (AES-256-GCM) with SECRETS_ENCRYPTION_KEYOrganization admins, and Docks the Environment passes them into. New Environments limit secrets to admins by default. If an Environment allows members, any member can create a Dock with it, so members can read its secrets from inside that Dock
Connector credentials (GitHub App private key and webhook secret, OAuth secrets)Control plane environment (.env)The control plane only
Hosts and dockletsYour machines; each docklet holds a per-host credential in its state directoryThe operator
Control planeAPI, Postgres, Redis, Restate, object storeThe operator

Actors:

  • Dock workload. Untrusted code, including whatever an agent installs or runs.
  • Organization member, admin, owner. Authenticated users. A member works with Docks. Admins also manage members, webhooks, secret reveal and who may use an Environment's secrets. Owners also manage other owners.
  • Outside caller. Anyone with network access to the public origin but no credential.
  • Operator. Runs the control plane and hosts. Trusted by design: the operator can read everything on the machines they run.

What a Dock can reach

Its own disk.

  • microvm: the whole root filesystem is the Dock's own virtual disk and persists across stop and resume.
  • gvisor: only /home/user/work persists. It is a sparse ext4 image of diskGiB, loop-mounted on the host, so one Dock cannot write more than its size.
  • Both disks are sparse files capped at diskGiB per Dock. Host disk is not reserved for them, so the sum of all Docks' sizes can exceed the host's free space. See the known gaps.

Its own CPU and memory. The docklet sets cpu.max and memory.max per microVM (cgroup v2) and --cpus and --memory per gVisor container, from the Dock's size.

The network. Docks get a private address on a host-local subnet and are NATed out through the host. The host firewall applies these rules to every Dock:

  • Traffic to the Dock subnet is dropped, so one Dock cannot reach another on the same host.
  • Traffic to 169.254.0.0/16 (cloud metadata and link-local), 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10 and 127.0.0.0/8 is dropped. An operator can opt specific ranges back in with --egress-allow-private (DOCKLET_EGRESS_ALLOW_PRIVATE); that opens them for every Dock on the host.
  • Traffic from a Dock to the host itself is dropped, except replies to host-initiated connections, ICMP echo, and the egress proxy port on the gateway address.
  • IPv6 is disabled on the host side of each Dock interface (best effort).

Everything else on the public internet is reachable. An ordinary Dock has open outbound internet access; Dock does not filter ordinary Docks by destination.

Confined Docks. GitHub-triggered runs are provisioned with an allowlist. Their host firewall drops all forwarded traffic, and the only way out is a CONNECT proxy on the host that admits HTTPS (port 443) to allowlisted names. The default list is GitHub, npm, Yarn, PyPI, the Go proxy and crates.io, plus the control plane. The proxy resolves each name once and dials only a public address, so DNS rebinding cannot reach a private target. Setting RUN_EGRESS_ALLOW=* turns this confinement off.

Anything you pass in. Whatever an Environment passes into a Dock is readable by anyone inside that Dock. By default an Environment passes agent credentials: the organization's provider keys and, if the Dock's creator signed in with ChatGPT, a Dock-scoped token that spends that sign-in through the control plane. Linked GitHub repositories get a read-only clone token. The Dock API key is off by default. See Environments.

Hosted ports. An exposed port is reachable by anyone who has its URL until you remove it. The hostname has a random label, but it gets its own public TLS certificate, so it shows up in Certificate Transparency logs. Treat the URL as public, not as a secret.

What a Dock cannot reach

  • The host. A microVM runs under the Firecracker jailer, dropped to an unprivileged user, in a chroot and cgroup. Every microVM on a host runs as the same user (DOCKLET_UID/DOCKLET_GID, default 1000); on many distributions that is the first login account, so set a dedicated user. A gVisor container runs as a non-root user with all capabilities dropped and no-new-privileges, inside runsc. Escape is the main risk; see the isolation section.
  • Other Docks. Separate disks, one TAP or veth per Dock with no shared bridge, and the subnet-drop rule above. MicroVMs do not share a kernel; each gVisor Dock is its own runsc sandbox.
  • Control plane credentials. The GitHub App private key, webhook secret, OAuth secrets and SECRETS_ENCRYPTION_KEY are not sent to hosts or Docks. The only connector-derived token a Dock receives is a short-lived, read-only GitHub installation token for the repositories an Environment links, minted at apply time.
  • The control plane database. Postgres, Redis and Restate are not published by the self-host stack, and private ranges are dropped from Docks. A Dock reaches the control plane only through its public origin.
  • The fleet API. It requires a host credential, which a Dock does not hold. The host credential sits in the docklet's state directory with owner-only permissions, outside the Dock.

Control plane

Authentication. One global guard accepts two credentials:

  • Authorization: Bearer dock_... API keys. Only a SHA-256 hash is stored. Keys can be revoked and can expire.
  • A Better Auth cookie session, used only when no Bearer header is sent.

A closed account can only reopen or erase itself; an erased account is refused everywhere. There are no JWT access tokens.

Dock-bound keys. When an Environment passes Dock credentials, the control plane issues one key for that Dock, acting as the Dock's creator. Each apply replaces it and withholding deletes it. A dock-bound key can call only GET /v1/docks/:id, the Dock's three port routes, and GET /v1/docks/:id/stats, and only for its own Dock. Every other route returns 403, and the key is confined to the Dock's organization. These keys do not appear in the user's key list.

Roles and tenant scoping. Roles are member, admin and owner. Docks, Environments, Snapshots and webhooks belong to an organization, and the service layer checks membership before reads and writes. A Dock in another organization returns 404, not 403. The last owner of an organization cannot be removed or demoted.

Webhooks. Endpoint URLs must be https. At dial time the hostname is resolved, every returned address is checked against private, loopback, link-local, CGNAT, multicast, reserved and IPv6-embedded-IPv4 ranges, and the socket connects only to the vetted address. Redirects are never followed. Deliveries are signed with HMAC-SHA256 over the timestamp and body. Endpoint secrets are encrypted at rest.

Fleet. Hosts connect outbound only and need no inbound port. A host enrolls with a shared token once and receives its own credential; the control plane stores a hash, compares in constant time, and ties every fleet call to that host. Snapshot object access is scoped to the calling host: it can read generations of Docks placed on it or named in a command sent to it, and cannot overwrite recorded Snapshots. FLEET_LEGACY_SHARED_TOKEN=true re-enables the shared token for every call; leave it off.

Rate limits. Redis counters limit API key and SSH key creation, Dock exec (per Dock, per caller and concurrent commands per Dock), terminal tickets, SSH gateway authentication (per client address), ChatGPT sign-in, GitHub run triggers (per repository), the run LLM gateway, webhook deliveries (per endpoint) and the public contact form. They fail closed: if Redis is down the request is refused (503 on HTTP routes). Request body sizes are bounded. Other routes have no limit of Dock's own.

Secrets at rest. Provider keys, Environment secrets, ChatGPT sign-in tokens and webhook secrets are sealed with AES-256-GCM under SECRETS_ENCRYPTION_KEY. Whoever holds that key and the database holds the secrets.

Isolation by runtime

RuntimeBoundaryAssessment
microvmFirecracker on Linux KVM, under the jailerStrongest. Each Dock has its own guest kernel behind hardware virtualization. An attacker needs a hypervisor or VMM escape.
gvisorrunsc on Linux with DockerStrong without a hypervisor. A user-space kernel handles the Dock's system calls, so the host kernel sees far fewer of them. The attack surface is larger than a VM's: the sandbox is a complex program, and a bug in it exposes the host.
libkrun on Windows and macOSPlannedNot shipped. It is not equal to Firecracker until it has been reviewed.

Prefer microvm for hostile or multi-tenant workloads. Use gvisor when KVM is unavailable and you accept the larger attack surface.

No third-party audit yet

Dock has not been independently audited. Do not run Docks that hold secrets for people you do not trust on hosts that hold anything else of value.

Supply chain

  • Docklet release binaries are signed with cosign (keyless, from the docklet release workflow). The host installer downloads the binary, checksums.txt and their signature bundles, and stops unless both signatures come from the docklet release workflow on a version tag and the binary matches its checksum. If cosign is missing, the installer fetches a pinned, checksum-verified build.
  • The control plane images (ghcr.io/usedock/dock-api, dock-api-migrate, dock-dashboard) are built and published to GHCR for each release and pinned by DOCK_VERSION. They are not signed with cosign today. Pin the version and check the digest yourself if that matters to you.

Out of scope and known gaps

  • Noisy neighbours. CPU and memory are capped per Dock. The docklet sets no disk I/O limit, process-count limit or network bandwidth limit on either runtime, so one Dock can degrade disk and network for others on the same host.
  • Host disk overcommit. Each Dock's disk is capped at diskGiB, but the files are sparse and nothing reserves host space for them. The docklet compares a new gVisor Dock's size with free disk when it is created; microVM Docks get no disk check. Docks that together grow past the host's free disk fill that filesystem, and every Dock stored on it sees write errors. Watch host disk usage.
  • One jailer user for all microVMs. Every Firecracker process on a host runs as the same user, so a VMM escape lands as the user that runs every other microVM on that host.
  • Open outbound internet for ordinary Docks. A Dock can send anything it can read to any public host, and can attack the internet from your IP. Confinement exists only for GitHub runs.
  • Secrets inside a Dock are readable by the Dock. Use scoped, revocable keys.
  • Snapshots. Dock does not encrypt Snapshot data in the object store itself. Use encryption at rest on your bucket or volume. A Snapshot of a Dock that held secrets contains them on disk.
  • A malicious operator or a compromised control plane. The control plane commands every host, and the operator can read everything. Dock does not defend against its own operator.
  • Host compromise. A sandbox escape or a host kernel bug is outside what Dock can prevent. The docklet runs as root on the host.
  • Denial of service on the public API beyond the limits above. Put a rate-limiting proxy or firewall in front of a public instance.
  • Side channels between Docks on shared hardware, such as CPU cache attacks, are not mitigated.

Reporting

Email security@usedock.io. Do not open a public issue. The policy, disclosure window and scope are in SECURITY.md.

On this page