Security model
What COMA can reach, what it changes, and what it never exposes on the network.
COMA connects your computer to machines you control. It reaches them over SSH only, verifies every host key, keeps its local Docker API private to you, and publishes container ports on the machine's loopback interface. Nothing it runs listens on the network.
SSH is the only transport
COMA reaches a machine over one authenticated SSH connection. The engine's API, port mirrors and sync all travel over SSH. COMA never enables Docker's TCP API, and detection warns if a machine already exposes it.
- The SSH client is built into COMA. It does not run your system
ssh. - Authentication uses ssh-agent, then keys you pass with
--identity, then~/.ssh/id_*. COMA never asks for or stores key passphrases; passphrase-protected keys go through ssh-agent. - From
~/.ssh/config, COMA readsHostName,User,PortandIdentityFile. AProxyJumpfails withssh_proxy_failedinstead of being ignored.
Host keys are verified strictly
COMA never trusts a host key silently (Machines).
- A key already in your
~/.ssh/known_hostsis trusted. COMA reads that file and never writes it. - A new key needs your confirmation of its fingerprint, or
--host-key SHA256:…when there is no terminal. Otherwise the command fails withssh_host_key_unknown. - Accepted keys go into COMA's own known-hosts file.
- A changed key is fatal (
ssh_host_key_mismatch). COMA does not retry and does not offer to replace it.
The sync helper connects through COMA with the same keys and host-key store. It never uses your ~/.ssh configuration.
The local Docker API is private to you
coma connect serves the machine's engine API on a Unix socket on your computer. There is no TCP listener.
- Each endpoint socket is mode
0600in a directory with mode0700, owned by you. comad, COMA's background process, takes commands on its own Unix socket in a0700directory. It has no network listener.- COMA does not replace
/var/run/docker.sock. The Docker CLI reaches the machine through the Docker contextcoma, whichcoma disconnectswitches back.
The endpoint changes only what it must
The endpoint forwards Docker API traffic byte for byte. It reads each request's headers, and acts only on a short, fixed list of requests: creating a container, starting one, and their Podman equivalents. Everything else, including exec, attach, logs -f and BuildKit streams, passes through untouched.
For those requests, the effects are:
- port publications on all interfaces are rewritten to
127.0.0.1(Ports); - bind sources inside a synced directory are mapped to the machine's copy, and other bind sources are refused (Sync);
- a bind of the Docker socket is mapped to the engine's socket on the machine;
- a container start is held, for at most 2 seconds, until its ports are mirrored to
localhost; - a few Podman requests whose publications or machine-side paths COMA cannot check yet, such as
podman kube play, are refused with an explanation.
After a rewrite, COMA checks the result again and refuses to forward a request that still has a wildcard publication or an unmapped bind. coma endpoint doctor <endpoint> --explain-last shows what COMA changed in the last intercepted request.
Nothing is published to the network
On remote machines, COMA publishes container ports on 127.0.0.1 only. You reach them on your own localhost through SSH. COMA never creates public ingress, and coma.yaml rejects visibility: public.
Containers started without COMA's endpoint keep the engine's default. That includes containers started with coma docker and coma podman, which run the engine's CLI on the machine. coma port list flags their ports as all-interfaces and warns. coma machine bootstrap --engine docker --harden-publish makes 127.0.0.1 Docker's default for every container.
What COMA changes on a machine
coma machine addandcoma machine discoverchange nothing on the machine. They read inventory with read-only commands.coma machine removeforgets the machine in COMA. The server is not changed.coma machine bootstrapprints a plan with every command it would run, marks the steps that need sudo, and asks before it changes anything.--planstops after the plan. It installs engines from the distribution's own packages, with no third-party repository and nocurl | sh. Adding your user to thedockergroup is marked as root-equivalent access.- Sync writes only inside the workspace's directory under
~/.coma/workspaces/on the machine. coma workspace delete --remoteremoves only that directory, after showing what it will remove and asking.
Sync keeps what changed on the machine
In the default mode, sync never deletes or overwrites a file that changed on the machine. A file changed in both places is a conflict, and the machine's copy stays until you choose with coma sync resolve. The two modes that can delete or overwrite need explicit opt-in, and a repository's coma.yaml cannot enable bidirectional sync on its own (Sync).
Bind mounts outside a synced directory are refused, so the engine cannot mount an arbitrary path on the machine through COMA.
Secrets and logs
- COMA stores no secrets in its state database, config file or logs. COMA creates its config file with mode
0600and its state directory with mode0700. - Logs pass through one redaction step that masks tokens, passwords, API keys, authorization headers, private keys and credentials in URLs.
- Docker API request and response bodies are never logged, even with
--debug. Arguments tocoma dockerandcoma podmanare never logged. - When COMA switches the Docker context, it changes only the current-context setting in Docker's config. Credentials there are left as they are.
- Sync copies files you have not ignored, including
.envfiles.coma workspace planwarns about them; add them to.comaignoreif they should stay on your computer.
No telemetry
COMA sends no telemetry and needs no account. The self-hosted path makes no network call to coma.sh. Update checks are not implemented. COMA itself connects only to the machines you add.
What COMA does not protect against
- Your account on your computer. Any process running as you can use the endpoint socket, and so the engine on the machine. That includes coding agents you run. Docker API access is usually equivalent to root on the machine.
- Your SSH key. Anyone with your key and your machine's address can do what you can, with or without COMA.
- Root on the machine. Anyone with root on the machine can read the synced copy of your source and everything your containers hold.
- What containers do. COMA does not restrict privileged containers, capabilities or images. A container you mount the Docker socket into controls the engine.
- Host networking. A container with
--network hostlistens on the machine's interfaces directly; COMA has no publication to rewrite. - Old Docker engines. On Docker Engine before 28, ports published on
127.0.0.1can be reached from other hosts on the same network segment. Detection warns about it; upgrade the engine.
Found a problem? See Reporting a vulnerability.