Security
How Obacht routes traffic, executes commands, and handles keys. A description of the trust boundaries, written for people who evaluate them.
Obacht is designed to store the minimum required to operate the service. The capability that would be most damaging if disclosed, the key material that authorises commands on devices, is never held on Obacht's servers in usable form. The following describes the actual mechanisms.
Traffic
Inbound requests to a custom domain reach the Obacht proxy first. On port 443 the proxy runs nginx in stream mode with ssl_preread: it reads the SNI server name from the TLS ClientHello to select an upstream and forwards the encrypted TLS connection to that device's WireGuard address. It performs no TLS handshake and does not decrypt the payload. The certificate is held by the device and TLS terminates there, in the device's own web server (Caddy). Port 80 is forwarded equivalently for ACME HTTP-01 and redirects.
client
TLS →
obacht proxy
SNI read · no decrypt
wireguard
encrypted →
device
TLS terminates
Client → Obacht proxy (SNI routing via ssl_preread, no decryption) → WireGuard → device (TLS terminates here)
Network
Each device initiates a WireGuard tunnel to the gateway and maintains it. There are no inbound connections, no port forwards on the local router, and no publicly reachable listener on the device. Return traffic to a bound domain traverses the tunnel the device established.
Control plane
State-changing actions on a device (template install, domain binding, opening a shell) use one of two transports. Both are authorised by the same Ed25519 key pair, held by the user.
SSH
For an interactive shell and file access, the dashboard establishes an SSH session to the device through the SSH gateway. The user's private key is decrypted in the browser for the session and used to authenticate the SSH connection. The key is not persisted on the gateway.
WS · signed
For structured mutations (domains, bindings, instances) the dashboard constructs a mutation object (device_id, op, params, nonce, iat, exp), canonicalises it (JCS) and signs it with the user's private key (Ed25519) in the browser. On this path the key does not leave the browser, not even to the gateway. The API relays the signed envelope to the device over its WebSocket without inspecting it. The agent verifies the signature against a locally pinned public key, the device binding, the expiry window and nonce freshness, then dispatches the operation. Unsigned upsert/delete events on the WebSocket are permanently denied.
Key handling
Obacht stores the user's key as a passphrase-encrypted ciphertext (salt, IV, algorithm) and the corresponding public key, which devices pin. The passphrase and the decrypted private key exist only in the browser, for the session, entered by the user. Neither reaches Obacht.
A compromise of Obacht's servers yields encrypted key ciphertext and public keys. It does not yield the ability to sign a mutation or authenticate an SSH session. Issuing commands to the fleet would require each user's passphrase, which Obacht does not possess.
Templates
A template install carries the registry's minisign signature over the manifest and the user's signature over the mutation. The agent verifies both: the registry signature against an embedded trust bundle, the user signature against the pinned public key. A compromised backend can forge neither, so it cannot deliver a modified manifest or install in the user's name.
Device defaults
Devices operate in restricted mode by default: signed templates only, no arbitrary command execution. Power mode is an explicit unlock and can be revoked. Mutations and command executions are recorded in an append-only audit log stored on the device.
These choices follow one principle: retain as little as possible, and keep the capability that could compromise a fleet, signing commands, off Obacht's infrastructure. The result is a remote management plane in which no single server holds the means to act on devices.
Docs