Editor

VS Code in the browser, per account, with an Open VSX extension policy and framework packs.

In preview editor version 1.0.0

Actions

19 actions, callable from the panel, the command palette and the API as POST /api/v1/a/<id>. Internal actions used between modules are not listed.

ActionWhat it doesRiskPreview
editor.install Install code-server on a server high
editor.session.open Open the editor medium No dry run
editor.preview.open Preview a port medium No dry run
editor.preview.list Open previews (read) low No dry run
editor.preview.close Close a preview low No dry run
editor.status Editor status of an account (read) low No dry run
editor.instance.stop Stop the editor low No dry run
editor.instance.list Running editor instances (read) low No dry run
editor.instance.admin_stop Stop any editor instance low No dry run
editor.extension.install Install an extension medium No dry run
editor.extension.uninstall Uninstall an extension low No dry run
editor.extension.list Installed extensions (read) low No dry run
editor.pack.list Recommended extension packs (read) low No dry run
editor.pack.install Install a recommended pack medium No dry run
editor.policy.get Show the extension policy (read) low No dry run
editor.policy.set Set the extension policy high
editor.policy.check Test an extension id against the policy (read) low No dry run
editor.settings.get Show editor settings (read) low No dry run
editor.settings.set Change editor settings high

Permissions and limits

Permissions

  • editor.use Open the editor and manage own extensions
  • editor.admin.view See editor instances, policy and settings
  • editor.instance.manage Stop any editor instance
  • editor.policy.manage Change the extension policy
  • editor.admin Install code-server and change editor settings

Engineering notes

Generated from modules/editor/docs.md at build e0cdf300. These are the notes the engineers keep next to the code: precise, technical, and honest about what is not done yet.

Declared in module.yaml; agent driver internal/agent/editor.go; op types sdk/agentop/editor.go; policy and packs in modules/editor/policy.

Model

  • editor.install (admin): downloads the pinned code-server 4.141.0 deb (MIT), verifies its sha256, installs it.
  • editor.session.open: starts (or reuses) rc-editor-<user>.service and returns a ticket. The unit runs as the account user in rc-acct-<user>.slice, NoNewPrivileges, PrivateTmp, ProtectSystem=strict, TemporaryFileSystem=/home + BindPaths=<own home> (other accounts' homes do not exist in its mount namespace), optional MemoryMax. It listens only on /run/rc-editor-<user>/code.sock (runtime dir 0700, socket 0600), auth none behind the socket, telemetry and update checks off (telemetry.telemetryLevel=off, update.mode=none are enforced on every start, plus the admin's locked settings), --idle-timeout-seconds (default 30 min) so it stops itself. Extensions: EXTENSIONS_GALLERY points at Open VSX only.
  • editor.session.open {app_id} opens ~/apps/<app>/current and returns the matching suggested_pack; file + line deep-link into the workbench. Nothing is installed without an explicit editor.pack.install.
  • Extensions: editor.extension.install/uninstall/list, editor.pack.list/install (16 vetted packs, every id checked on Open VSX), policy editor.policy.get/set/check (open | allow_list, allow/deny publisher.name or publisher.*, deny wins). The control plane checks and the agent checks again.
  • Admin: editor.settings.get/set (idle minutes, memory, max_running_per_node, default + locked settings), editor.instance.list/admin_stop.

The gateway (core, not built here)

Same pattern as the terminal (docs/security/terminal.md). This module provides the ticket side: editor.ticket.redeem (internal, system actor only) consumes the ticket atomically if it is unused, unexpired, opened by that principal from that browser session (web_session<>'', so API-token tickets never redeem), and returns {node_id, username, socket, url_path}. What core must add (internal/api, outside this card's paths): a GET /api/v1/editor/open?ticket= + proxied /editor/... HTTP and WebSocket route that checks, in order, Origin (as terminalGW.originAllowed), ticket shape, session cookie only (AuthenticateStream), redeems via the system actor, then reverse-proxies to the unix socket (same node) or over the bus (other nodes), re-validating the session every 15 s and on every WebSocket upgrade, with a per-principal/global cap and audit editor.stream.opened|closed|denied. See the handover for the exact request.

Not done

Queueing instead of a refusal at max_running_per_node (the refusal is now shown on the progress page), a systemd .socket unit (see "Start on first request": it is done at the gateway instead), debug-mode toggle, Git credential helper, collaboration (AC-editor-10/12/13/17).

w11-30

  • workspace_trust (editor.settings.set): trusted (default) or ask; written as locked settings on every start. Reasoning in docs/security/editor.md.
  • The workbench's marketplace is a node-local gallery that enforces the allow / deny policy on the server (internal/agent/editor_gallery.go); editor.instance.start carries the policy.

w11-57: previews of a port, start on first request

  • Previews (editor.preview.open|list|close, Previews tab of the editor page). A development server the account started in the editor's terminal (say on 3000) opens in a tab at https://<handle>.<RC_EDITOR_DOMAIN>/: an origin of its own, apart from the panel and from every editor. The only input is a port number (1024-65535). open mints a single-use ticket (30 s) bound to the browser session; /api/v1/editor/preview/open?ticket= redeems it (editor.preview.redeem, system actor only; an editor ticket never redeems as a preview and the reverse), the gateway keeps a grant for it, and the module row (previews) is what keeps the grant alive: close, the end of the panel session, suspension and account deletion end it. One preview per port per browser session (opening again replaces it); 10 per account.
  • Which port is allowed is decided by the node's agent, never by the browser or the gateway: tunnel kind preview-<port> reaches only a LISTEN socket on this node's loopback (or the wildcard address) that belongs to the account's uid AND is held by a process in rc-acct-<user>.slice, re-checked after the connect. Another account's server, a root service, a database or the panel's own loopback listeners are refused with a reason. No address, no host name, no Host header of the browser's is ever forwarded: the server sees Host: localhost:<port>.
  • Needs separate editor addresses (RC_EDITOR_DOMAIN). In port mode (one shared editor origin) a preview is refused with an explanation: a dev server serves from / and one origin must never hold two accounts' code.
  • Start on first request. editor.session.open now only records the folder and returns the ticket; nothing starts. The gateway's first request finds no socket, calls editor.session.wake (system actor) and answers with a self-reloading "Starting your editor" page; one wake runs per grant, a refusal (for example the server's editor cap) is shown once, the next load tries again. The unit stops itself when idle (--idle-timeout-seconds), so an account that never opens its editor costs no memory, and one that comes back to an old tab gets progress instead of an error page. A systemd .socket unit was not used: systemd would be the listener, so the agent's SO_PEERCRED check (the account's uid, in the editor's cgroup) could no longer tell the real code-server from a squatter.
  • Limits come from the plan as before (the same MemoryMax and slice; a preview is a process of the account's own slice).