Default trust boundary
By default Desk binds to:127.0.0.1 there and reach it through SSH
port forwarding.
What a connected client can do
A client that can reach Desk can:- view and type into agent terminals
- start, restart, and delete configured sessions
- operate
gitandghwith the host user’s credentials - read and write files under the active explorer root
- use notes under
~/.config/desk/notes - post and read channel messages
- upload channel files
- run the emergency kill switch
Filesystem boundary
The file API is constrained to the active explorer root. Desk resolves client paths and rejects path escapes. The manifest file is a special trusted file so the UI can open it even when it is outside the explorer root. The notes subsystem uses its own pinned root:Channel uploads
Channel uploads are stored under the channel files directory. Desk serves uploaded files with restricted headers, including a sandboxing content security policy and forced download behavior for active content. Treat uploaded files as untrusted, especially when they were produced by an agent.Emergency kill switch scope
The kill switch retires configured sessions and performs a host-wide sweep for supported agent processes. It can terminate:- every Moor session in the active manifest
- remaining host
codexorclaudeprocesses found by process scan
Plugin extension points
Desk exposes a small backend plugin interface for local embedders and downstream builds. It is an extension surface, not a replacement for built-in user accounts or request authentication. A plugin can provide:middleware: Connect middleware mounted before the core/apirouterroutes: extra/apiroute handlers tried after core routes and before the 404upgradeGuard: a central predicate for WebSocket upgradeschannels: providers that replace or wrap parts of the Channels subsystemsetup: lifecycle code that runs when Desk installs the plugin
Channels providers
channels takes one provider per Channels port. Each receives a factory for
the implementation Desk would otherwise use and returns the one to use instead,
so a plugin can wrap rather than reimplement:
store (where conversations live, their reactions and stars,
and how a finalised message is noticed), files (where attachments live),
views (saved view filters), router (who a message is for), delivery (what
reaches an agent), and renderer (what an agent sees). Providers apply in plugin order, each wrapping the previous result; the factory
is memoised, so calling it twice yields the same instance.
A provider that never calls the factory replaces the part outright, and Desk
does not build the stock implementation at all — no watcher is started for a
store that pushes its own changes. A provider that returns base() unchanged is
a no-op, and declaring none leaves Channels exactly as it ships.
Every store operation is asynchronous, so an implementation may be backed by
anything reachable over a network, not only a local disk. The contracts are in
src/server/channels/ports.ts.
A Channels provider is the most intrusive thing a plugin can do: it sits on the
path that types text into a live agent session. Treat it with the same care as
upgradeGuard.
Runtime plugin modules can export a plain plugin object:
defineDeskPlugin exists at src/server/plugin.ts. Source-backed
installs retain the application source and locked dependencies, so local
embedders can build against the same release tree.
Runtime plugins
SetDESK_PLUGINS to a comma-separated list of module specifiers:
- a
DeskPlugin - a default
DeskPlugin - a factory that returns a
DeskPlugin
DESK_PLUGINS means stock local-trust behavior.
WebSocket guards
upgradeGuard runs before any WebSocket bridge handles a socket. It is useful
for embedded builds that need a local runtime policy, but it does not make the
stock Desk server safe to expose publicly.
It covers:
- terminal WebSocket connections
- filesystem watch sockets
- LSP sockets
Embedded plugins
The private Bun build has a build-time seam for embedding plugins directly into a downstream runtime. Desk’s own runtime embeds no plugins. RuntimeDESK_PLUGINS works with both desk serve and desk serve --dev. The
private Bun runtime loads runtime plugins first, then appends any embedded
plugins supplied by the build.
Error surfaces
Public API routes return JSON errors. Unexpected route failures are collapsed to terse messages instead of exposing stacks. Plugins should follow the same pattern and avoid returning secrets or stack traces.Next steps
- Follow Run Desk securely for the localhost and SSH tunnel model.
- Read API and runtime reference for the routes that plugins can extend.
- Read Troubleshooting and FAQ for startup and connection symptoms.