capsocket — one narrow socket per capability

Updated · View the entry on sijie.xyz ↗

Sandboxed builtin plugins (summarize / retrieval / booker / mail-sender; ask-visitor is sandboxed too but declares no host ops, so it gets no socket at all) run network-isolated (--unshare-net) and reach host data through one unix socket per capability (/run/standmeet/<id>.sock, derived by hostop.SocketPath — internal/infra/hostop/op.go), speaking a newline-JSON wire protocol (internal/capabilities/capsocket/server.go). The point is the NARROWNESS: each capability sees exactly its own tiny API surface, not a general backend — the blast radius of a compromised plugin is one socket's verbs. Contrast: a general HTTP loopback would hand every plugin the whole admin surface.

Since 2026-08-01 (5cb8d8464) the verbs are no longer hand-wired per capability: the manifest orders them by name (host_ops, e.g. conversation.read, connector.invoke, capstore.*) from a fixed vocabulary, and the inbound convergence point internal/routes/hostdesk serves each socket by that declaration — an unknown name fails at startup, not when the owner clicks.

Cross-links: sandbox-js-hardening, mcp-capability-plugins.

flowchart LR
  SB["sandboxed plugin
(bwrap, --unshare-net)"] -- "newline-JSON over
/run/standmeet/cap.sock" --> H["host: that capability's
few verbs ONLY"]
  SB x--x FULL["general backend API"]

Related notes