Desk is a single-user local-trust tool. The stock server has no built-in authentication. Anyone who can reach the server can use the UI and API as the local operator.

Default trust boundary

By default Desk binds to:
Keep that default. Do not expose Desk directly on a network. If Desk runs on your own remote development box, keep it bound 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 git and gh with 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
Treat the Desk port as equivalent to local operator access for the selected workspace and running agent fleet.

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:
Pick editor roots carefully. The root you choose is the filesystem trust boundary for editor file operations.

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 codex or claude processes found by process scan
The process sweep is intentionally broader than the active manifest. Use the control only as an emergency stop.

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 /api router
  • routes: extra /api route handlers tried after core routes and before the 404
  • upgradeGuard: a central predicate for WebSocket upgrades
  • channels: providers that replace or wrap parts of the Channels subsystem
  • setup: 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:
The six ports are 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:
The typed helper 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

Set DESK_PLUGINS to a comma-separated list of module specifiers:
Each module must export either:
  • a DeskPlugin
  • a default DeskPlugin
  • a factory that returns a DeskPlugin
Unset 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
All plugin guards must allow the request. If any guard rejects, Desk closes the upgrade before the subsystem sees it.

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. Runtime DESK_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