Security model

Under the hood

Security model

Cloudly runs coding agents on repositories that may contain prompt injections, so it assumes the agent can be turned against you and limits what it could do.

Keys and plan tokens

Your API keys and plan tokens never enter a run container. The agent gets a placeholder and an API address that points at Cloudly's key gateway. The gateway adds the real key or token on the way to the vendor, so a malicious repository has nothing to steal.

The agent can still make model calls on your account while its turn runs, through the gateway. It just can't take the credential with it.

Agents you add under Settings follow the same rule by a different route. Their keys are encrypted with your other settings, and a run gets a placeholder for each. The egress proxy swaps in the real key only on HTTPS requests to that key's hosts. Agents that only sign in through a browser or a subscription login can't be added, because that sign-in would have to live inside the sandbox.

MCP tokens and secrets

MCP servers work like model keys: a run sees each server as an address on the key gateway, and the gateway adds the token. Secrets for runs reach the sandbox as placeholders. For the hosts a secret may go to, the egress proxy terminates TLS with a certificate from Cloudly's own CA, puts the real value into the request's headers and URL, and sends it on. It sends the request only to the host the tunnel was opened for, whatever the request's Host header says, so a placeholder can't be turned into the value for any other host. The CA's private key stays in the worker's data directory and is never mounted into a container.

Sessions that talk to each other

A session's agent sees your other sessions only as files the worker writes into its sandbox before the turn: titles, branches and recent conversations, limited to the same repository unless you choose All your sessions. Messages it sends are written to a file the worker reads back after the turn, the same careful way it reads the patch, and checked again against the session's setting before they become turns. Nothing in the sandbox reaches the API or the database. See Sessions that talk to each other.

Shared context

Editor tokens reach the shared-context MCP endpoint and nothing else; only their SHA-256 is stored, and they can be revoked in Settings. Notes an agent adds come back from the sandbox the way the patch does (a regular file, never a symlink, with size and count caps), and are stored as plain text. They are text later agents read, so a misled agent could leave a misleading note: each entry names who added it, and you can remove it.

Research councils

A council without a repository may reach any public HTTPS site, so its agents can read their sources. Its turns get no repository, no secrets and no kept files, so there's nothing in the sandbox to leak beyond what the agent writes itself. Each turn gets its own credential for the egress proxy, revoked when the turn ends; without one the allowlist applies, so a repository's run can't switch itself to the open web. Even with one, the proxy resolves the name itself, refuses it if any of its addresses is private, loopback or link-local (the VM's own services, a cloud's metadata endpoint), and connects to the address it checked.

Desktops and previews

These are the one deliberate way in to a sandbox. The browser never connects to a container: it gets a short-lived token from the API, signed for one container and one purpose, and Cloudly's relay checks it before connecting to that container's VNC port (desktop) or preview port (app). The relay has no other routes. A desktop's VNC server also has its own password per turn, so other containers on the sandbox network can't watch it.

A preview runs code from the agent's branch, so it opens on its own origin (the same host, port 8443 or 3141), never on Cloudly's. The relay strips Cloudly's cookies from requests to it and refuses to let the app set them, and the API refuses writes from any origin but its own, so the app's code can't act as you in Cloudly. A preview container gets no model keys. To keep a preview's code out of your browser altogether, view it on the sandbox's desktop instead.

GitHub credentials

The sandbox never sees GitHub credentials. The worker does every git operation (commit, push, pull request) itself, from outside the container, with a short-lived GitHub App token. The agent's own clone has no real remote, so a push from inside the sandbox has nowhere to go.

Network

Run containers sit on an internal network with no route out and no outside DNS. Traffic leaves only through Cloudly's proxy, to the model APIs, the main package registries, GitHub and any hosts you add yourself.

The container

  • Runs as a non-root user, with all Linux capabilities dropped and no-new-privileges.
  • Has memory and process limits, so a runaway build can't take the VM down.
  • Sees only the repository and the agent's runtime, mounted read-only where it can be. Never the install directory, .env or other runs.
  • Is removed when the turn ends.

The agent's output

Everything the agent wrote is treated as untrusted. Its changes are exported in a separate container with no network and no keys, validated, and committed in a fresh clone the agent never touched. The host never runs git inside a directory the agent could write to.

Your instance

  • Invite-only. Only the GitHub logins you list can sign in.
  • One-time setup. /setup needs the token the installer printed, and closes itself once setup is done.
  • Encrypted settings. Keys and tokens saved in the browser are encrypted with AES-256-GCM and never shown again.
  • Separated services. The internet-facing web app and API run as a user with no Docker access. Only the worker can start containers.
  • HTTPS by default, with or without a domain.

Known limitations

  • A preview shares your instance's host name. Browsers keep cookies per host, not per port, and the relay strips Cloudly's own ones, but a preview's app can still set and read cookies of its own on that host.
  • Docker is the isolation boundary. Containers share the host's kernel, so only connect repositories you'd be comfortable running on the VM, and only let people you'd trust with a shell on it sign in.
  • A container escape lands as the worker's user, who can run Docker.
  • Cursor holds a session token while it runs. Its API key stays out of the sandbox, but Cursor trades it for a short-lived session token, and that token is inside the sandbox for the turn.
  • Plain HTTP is possible. If you decline HTTPS at install time, the session cookie and anything typed into Settings cross the network unencrypted.

Reporting a vulnerability

Please report privately through GitHub: open the repository's Security tab and choose Report a vulnerability. Don't open a public issue for anything that could put existing installs at risk. You should get a reply within 7 days. See SECURITY.md.