Remote daemon (HTTP/WebSocket and relay RPC)
Memories can host the same framed service::Request protocol over:
- Local IPC (default; OS-user authenticated; length-prefixed frames)
- HTTP + WebSocket (optional listen; token auth; same binary JSON frames as relay RPC)
- WSS relay RPC (
/v1/rpc/{room}/{slot})
Direct and relay remote attach share one wire scheme: WebSocket binary frames
carrying AuthFrame then version-1 Wire (service::Request). Vault sync still
uses /v1/channel. The WebUI stays status-only.
Security rules
- Listen token:
--listen-token/MEMORIES_LISTEN_TOKEN, or generated and printed once when HTTP/relay host is enabled. Never put the token in TOML. - Token is wiped when the store locks.
- Remote callers use stable elevation namespaces
ws:remote/relay:remote(separate fromcli:{pid}). Unlock and sudo over a remote transport elevate that namespace so subsequent one-shot connections keep elevation for the idle window (same rules as local: 5 minutes idle / 15 minutes max). - Desktop HTTP/relay hosts expose the full
handle_clientsurface after unlock/sudo (vault, card, memory, file, credential, sync, MCP grant, backup, proxy bind on the host, remote sessions, Run, CryptoJob, etc.), subject to the same elevation checks as local IPC. - HTTP listen defaults to loopback; non-loopback needs
--allow-non-loopback. - Plain HTTP/WS needs no certificates (default for direct LAN).
- Optional TLS:
--tls-cert/--tls-key(PEM) enable HTTPS/WSS on the same listen. Anhttps://listen URL requires those flags. - Off-trust relay must be
wss://with a 43-character room id and token. - Anyone who holds the listen token is as trusted as a local elevated client for that transport namespace. Treat the token like a password; prefer loopback or an authenticated network path.
Residual risks
- Token theft ⇒ full remote control of an unlocked/elevated desktop daemon.
- Non-loopback HTTP exposes the framed API to the network; use
--tls-cert/--tls-key, a reverse proxy, VPN, or firewall when binding beyond loopback. - Relay RPC (
/v1/rpc/{room}/{slot}) is authenticated, not end-to-end encrypted: its application-level JSON frames carry the shared listen token followed by requests and responses. The relay operator or TLS terminator can read the token and RPC payloads. WSS protects each connection to the relay, not against the relay itself; use a trusted relay for remote daemon access. - Vault peer sync (
/v1/channel) is separate and carries end-to-end encrypted payloads. For that channel, relay operators see ciphertext lengths and timing, not asset plaintext. - Host-side proxy listeners still bind loopback on the daemon machine; remote clients can start them when elevated, then reach them only if they can also reach that host loopback (typically via a separate tunnel).
HTTP/WebSocket host and client
# Host (prints a token once if --listen-token is omitted)
memories daemon --listen http://127.0.0.1:7900
# or HTTPS URL when TLS is configured:
memories daemon --listen https://127.0.0.1:8443 \
--tls-cert certs/daemon-tls.crt --tls-key certs/daemon-tls.key --listen-token "$TOKEN"
# Client (another shell / machine that can reach the listener)
export MEMORIES_DAEMON=http://127.0.0.1:7900
export MEMORIES_DAEMON_TOKEN="$TOKEN"
memories status
memories unlock
memories sudo
memories vault list
Direct RPC path is /v1/rpc (WebSocket upgrade). Flags: --daemon,
--daemon-token (same meaning as the env vars).
Client TLS trust
Certificate and hostname verification are enabled by default. The global
--ca-file PATH option adds all certificates from a PEM CA bundle to the normal
trust store. For a self-signed daemon, trust its certificate explicitly (its
DNS/IP subject alternative names must still match the URL):
memories --daemon https://localhost:8443 --daemon-token "$TOKEN" \
--ca-file certs/daemon-tls.crt status
# Equivalent CA setting, also usable with relay and HTTPS clients:
export MEMORIES_CA_FILE=certs/daemon-tls.crt
memories --daemon https://localhost:8443 --daemon-token "$TOKEN" status
# DANGEROUS: development only; prefer --ca-file instead.
memories --daemon https://localhost:8443 --daemon-token "$TOKEN" --insecure status
--insecure disables certificate-chain and hostname verification. TLS
encryption remains enabled, but an attacker can impersonate the server and read
daemon tokens, credentials, or request/response data. Never use it in production
or on an untrusted network. It is CLI-only (no MEMORIES_INSECURE environment
variable). An explicitly supplied CA file must be readable and contain valid PEM
certificates even with --insecure; invalid files fail before connecting. Files
are loaded once at startup; restart the process after changing the bundle.
Both options apply to outbound connections made by the current process: direct daemon HTTPS/WSS attach, relay RPC host/client, vault-sync relay clients, cloud OAuth and Drive HTTP, RAG API/model-download HTTP, credential-authenticated HTTPS, automation webhooks, and database TLS sessions/proxies. For operations executed by a daemon, pass the options when starting that daemon; client flags are not sent over RPC and cannot change the daemon's upstream TLS policy:
memories --ca-file /path/internal-ca.pem daemon
# Development only: affects every outbound TLS connection in this daemon.
memories --insecure daemon
Server --tls-cert / --tls-key and shared-token authentication are unchanged.
Database --tls-ca CA_ID still resolves a PEM credential in the vault; its trust
is combined with the daemon's --ca-file bundle. Neither option disables TLS or
SSH host-key pinning. These flags are not TOML settings, are not persisted as
vault secrets, and do not configure mobile/FFI callers or TLS clients in external
programs/browsers or dependency-managed downloads outside these HTTP builders.
Relay RPC host and client
# Generate a room id (43 URL-safe characters)
memories sync room
# Host joins slot a
memories daemon \
--relay-host wss://relay.example.com \
--room "$ROOM" \
--listen-token "$TOKEN"
# Client joins slot b
export MEMORIES_DAEMON_RELAY=wss://relay.example.com
export MEMORIES_DAEMON_ROOM="$ROOM"
export MEMORIES_DAEMON_TOKEN="$TOKEN"
memories status
Local development may use ws://127.0.0.1:8787 with memories-relay.
For a relay with a private CA, both host and client can use the global flags:
memories --ca-file /path/relay-ca.pem daemon \
--relay-host wss://relay.example.com --room "$ROOM" --listen-token "$TOKEN"
memories --ca-file /path/relay-ca.pem --daemon-relay wss://relay.example.com \
--daemon-room "$ROOM" --daemon-token "$TOKEN" status
Mobile Share (foreground host)
The Flutter app can start a Share session that joins relay RPC as host
(slot a) while unlocked and in the foreground. Backgrounding or lock tears the
session down. Mobile keeps the FFI denylist (no Run/CryptoJob/MCP grant/
remote sessions/proxies, and Share also refuses credential plaintext copy and
backup dumps). Phones do not open an HTTP listener. See mobile/README.md.
Local-only / platform exceptions
| Surface | Desktop remote (HTTP/WS / relay) | Mobile FFI / Share |
|---|---|---|
| Vault / memory / card / file / sync / status / unlock / sudo | Full (elevation where required) | Full where FFI allows |
| MCP grant/revoke, Backup, CredentialCopy | Full when elevated | Denied on Share; FFI allows copy/backup locally |
| Run, CryptoJob, Remote, Proxy | Full when elevated (runs on host) | Denied (no desktop runtime) |
| HTTP/WS listen | Optional (--listen) |
Not available |
Persona automation webhook
When HTTP listen is enabled, inbound automation triggers are accepted at:
POST /v1/persona/{vault}/{persona}/automation/trigger
Authorization: Bearer <persona world.automation.webhook_token>
Content-Type: application/json
{"hook":"HOOK_ID"}
The daemon must be unlocked (same as other write RPCs). The listen token enables the
HTTP surface; the automation webhook_token (set via persona automation put
--webhook-token) authorizes the hook. A relay host receives the same call at
POST /v1/persona/{room}/{vault}/{persona}/automation/trigger on the relay (same body and webhook bearer); the room segment is the only path difference, and the relay forwards one existing persona_automation_trigger wire frame to the connected host instead of serving it locally or posting outbound.
Equivalent CLI:
memories persona automation trigger VAULT PERSONA --id HOOK_ID \
--source webhook --token "$WEBHOOK_TOKEN"