> ## Documentation Index
> Fetch the complete documentation index at: https://docs.tofunmiadewuyi.com/custos/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> How the control plane, the per-host daemon, and sshd fit together.

## The pieces

**Control plane (`custoscp`)** — an HTTP API backed by Postgres. It is the source of truth for
users, hosts, SSH keys, grants, credentials, and secret sets. It signs authorized-key snapshots and
seals secret bundles.

**Daemon (`custosd`)** — one per host. It does three jobs:

1. Answers sshd's `AuthorizedKeysCommand` on every login attempt, over a local Unix socket.
2. Holds a long-lived WebSocket to the control plane and applies what it receives.
3. Serves bound secret sets to local apps over a second Unix socket, for `custosd exec`.

**sshd** — unmodified. Custos writes a drop-in at `/etc/ssh/sshd_config.d/70-custos.conf` and never
edits the main config.

## Direction of travel

The daemon dials *out* to the control plane. Hosts need no inbound ports and no public address. The
control plane cannot reach a host that is not connected; it pushes to the ones that are and
reconciles the rest on reconnect.

```
                      ┌──────────────────────────┐
                      │   control plane          │
                      │   custoscp + Postgres    │
                      └────────────┬─────────────┘
                                   │  wss (daemon dials out)
                      ┌────────────┴─────────────┐
                      │   host                   │
                      │                          │
   sshd ──unix sock──▶│  custosd                 │
                      │     │                    │
   app  ──unix sock──▶│     └── secret sets      │
                      └──────────────────────────┘
```

## The SSH login path

On each login attempt, sshd runs `custosd authkeys`, which queries the running daemon over
`/run/custos/custosd.sock` and prints the authorized keys for the requested account. The daemon
holds no local policy: it serves whatever was in the last snapshot the control plane signed.

If the daemon is unreachable, `authkeys` retries, then falls back to the last-known-good cache file
so a control-plane outage does not lock everyone out of every host.

Because sshd bypasses the API's auth middleware entirely, the snapshot query is what enforces user
state — a suspended user's keys are excluded from the snapshot, which is what makes suspension
actually cut SSH.

## Delivery model

Everything the control plane sends is either a **full snapshot** or a **sealed bundle**, never a
delta:

* **Snapshots** carry the complete authorized set for a host. The daemon *replaces* its cache with
  each one. Bulk revoke is just a fresh snapshot; revoking a host entirely is an empty snapshot
  followed by a forced disconnect.
* **Secret sets** are sealed as one blob to the host's X25519 key, so set names and bindings are
  inside the ciphertext. The daemon keeps them in memory only and never writes them to disk.

Both are Ed25519-signed by the control plane and carry a monotonic per-host sequence number, so
authenticity and replay protection hold even past a TLS-terminating proxy. The daemon verifies the
signature before it decrypts.

## Trust layers

| Layer | Protects | Scope |
| - | - | - |
| WSS / TLS | every message hop-by-hop; authenticates the server | may terminate at a proxy |
| App-level sign / seal | authenticity, replay, and confidentiality | end-to-end, control plane ↔ host |

App-level crypto exists exactly where an end-to-end guarantee is required. Full message-by-message
breakdown in the [daemon protocol](/custos/custos/internals/daemon-protocol).

## Secrets at rest

Credentials and secret-set values are envelope-encrypted in Postgres: a per-secret AES-256-GCM data
key, wrapped by a master key held behind a key-wrapper interface (`CUSTOS_MASTER_KEY` in the simple
deployment, a KMS or HSM in production).

<Note>
  This is not zero-knowledge. The control plane can decrypt stored secrets — it has to, in order to
  seal them to a host or return them to an authorized API caller. The threat model is a stolen
  database, not a malicious operator.
</Note>

## Enrollment and host identity

A host joins once, with an admin-issued single-use token. During enrollment the daemon generates its
own Ed25519 identity key and X25519 encryption key and registers only the public halves; the private
halves never leave the machine. From then on the host authenticates by signing a challenge nonce —
there is no bearer credential on the host to steal.

Custos enforces one active host per machine, keyed on a hash of `/etc/machine-id`. Revoking a host
frees the machine to re-enroll, fresh, with no inherited grants.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.