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.
| Action | What it does | Risk | Preview |
|---|---|---|---|
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.useOpen the editor and manage own extensionseditor.admin.viewSee editor instances, policy and settingseditor.instance.manageStop any editor instanceeditor.policy.manageChange the extension policyeditor.adminInstall 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>.serviceand returns a ticket. The unit runs as the account user inrc-acct-<user>.slice,NoNewPrivileges,PrivateTmp,ProtectSystem=strict,TemporaryFileSystem=/home+BindPaths=<own home>(other accounts' homes do not exist in its mount namespace), optionalMemoryMax. 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=noneare enforced on every start, plus the admin's locked settings),--idle-timeout-seconds(default 30 min) so it stops itself. Extensions:EXTENSIONS_GALLERYpoints at Open VSX only.editor.session.open {app_id}opens~/apps/<app>/currentand returns the matchingsuggested_pack;file+linedeep-link into the workbench. Nothing is installed without an expliciteditor.pack.install.- Extensions:
editor.extension.install/uninstall/list,editor.pack.list/install(16 vetted packs, every id checked on Open VSX), policyeditor.policy.get/set/check(open | allow_list, allow/denypublisher.nameorpublisher.*, 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) orask; written as locked settings on every start. Reasoning indocs/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.startcarries 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 athttps://<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).openmints 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 inrc-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 seesHost: 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.opennow only records the folder and returns the ticket; nothing starts. The gateway's first request finds no socket, callseditor.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.socketunit was not used: systemd would be the listener, so the agent'sSO_PEERCREDcheck (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
MemoryMaxand slice; a preview is a process of the account's own slice).