Skip to content
Draft, pre-release documentation

The runtime is one process inside the session container, running as the session user. This page covers what starts it, who can talk to it, and what the process itself can touch. None of it depends on a model.

The runtime has the session user’s rights. It can read and write anything that user can, including the home directory, a mounted persistent profile and other applications’ state, and it can open network connections to anywhere the container’s network allows. It applies no Landlock, seccomp or other kernel-level confinement to itself, and nothing wraps it in a sandbox.

A reviewer should plan for these consequences.

  • The runtime handles screenshots of whatever is on the session’s screen, and sends them to KASM_RUNTIME_VISION_ENDPOINT when one is set. A wrong endpoint sends them to the wrong place; the runtime does not restrict egress.
  • Scenario-provided credentials (KASM_RUNTIME_SECRET_*) are in the process’s environment and memory.
  • A bug in the runtime or a library it uses, triggered by hostile content on screen or in a page, would run with the session user’s rights.

The isolation barrier is the session container that Kasm manages. Process confinement is on the roadmap.

On images that include the runtime, it starts only when the session’s environment sets KASM_RUNTIME_SESSION_TOKEN. Without the variable there is no process and no listening port. The same value is the credential callers must present, so switching it on and choosing the token are one step. It is normally set through the Kasm API’s request_kasm environment map. See Enable the runtime on a workspace.

A scripted run (kasm-session-runtime run) is started by whoever launches it, inside the session. It listens on a port only if KASM_RUNTIME_OBSERVE=true, and then serves the read-only observe routes and, with a token set, the inbox routes, never the methods below.

With a session token set, kasm-session-runtime mcp serves HTTP on KASM_SESSION_RUNTIME_PORT (default 9434), bound to KASM_RUNTIME_BIND_ADDR (default 0.0.0.0, which Kasm’s port-map proxy needs). Without a token it serves MCP on stdio to the process that started it, with no authentication, because that process is the caller.

Every credential check is a constant-time comparison of the X-Kasm-Runtime-Session-Token header. The header is not Authorization because Kasm’s port-map proxy overwrites that header.

Route What it does Credential
/mcp MCP over Streamable HTTP: every method, including shell Session token
POST /v1/call/{method} The same methods over plain HTTP Session token
/v1/files/..., /v1/dirs/... File transfer Session token only; the messaging token is refused
/cdp/ Raw Chrome DevTools Protocol proxy, only when KASM_ENABLE_CDP is set Session token
GET /events, GET /artifacts/<file> Read-only: the live trace and the run’s output directory None by default; see below
Inbox write routes Messages, attachments, stop, finish_handoff Messaging token (KASM_RUNTIME_INBOX_TOKEN) or session token
Inbox request routes, ack Typed questions to the person, delivery acknowledgement Session token only
GET /v1/openapi.json The OpenAPI description of this listener None

Limits:

  • The observe routes (/events, /artifacts/) and inbox reads are open by default, because the in-session sidebar is a static page with no way to hold a token. Kasm’s reverse proxy limits the port map to the session’s owner, but anything else that can reach the port directly, such as another container on the same network, can read the run’s screenshots, trace and report. Set KASM_RUNTIME_OBSERVE_TOKEN_REQUIRED=true to require a token on them; with that set and no token configured they refuse every request. Pair it with KASM_RUNTIME_BIND_ADDR=127.0.0.1 behind a same-host proxy where you can.
  • KASM_RUNTIME_OBSERVE_INPUT records keystrokes into the trace and forces the token requirement on.
  • The messaging token is meant for a sidebar. It cannot call tools, the DevTools proxy, the shell or file transfer, and cannot create requests or acknowledge delivery.
  • There is one session token per session and no per-method scopes. A holder can call every method.
  • KASM_RUNTIME_MCP_LOCALHOST_PROTECTION (DNS-rebinding guard on /mcp) is off by default because Kasm’s proxy never sends a localhost Host header. Turn it on only when /mcp is served directly to a browser on the same machine.
  • Fixed listener limits: 10 s to send request headers, 2 min idle keep-alive, 8 MiB per /mcp or /v1/call/ request body.

An outside caller is treated as an agent with its own reasoning, so the MCP and HTTP methods are broad:

  • shell runs a command line to completion and returns its output. launch and proc.spawn start processes; proc.kill stops them.
  • input.click clicks raw pixel coordinates; input.type and dom.fill type literal text the caller supplies.
  • dom.query and dom.click take a CSS selector; dom.wait evaluates a JavaScript expression the caller writes; dom.navigate goes to any URL.
  • With KASM_ENABLE_CDP, /cdp/ gives the full DevTools protocol, which includes running script in the page and reading its cookies and storage.

Each call is recorded as a step in the report with backend rpc. Holding the session token is equivalent to a shell as the session user. The full list is on MCP methods.

A scenario-provided value referenced with ref is resolved inside the session and never returned to the caller, so a caller can type a credential it cannot read.

The runtime’s own DevTools connection (cdp: true scenarios, the dom.* methods, and the /cdp/ proxy) goes to KASM_RUNTIME_CDP_ENDPOINT, default http://127.0.0.1:9222. The runtime refuses to start with an endpoint that is not loopback (127.x, ::1, localhost) unless KASM_RUNTIME_CDP_ALLOW_REMOTE=true. It also:

  • refuses a discovery response that redirects;
  • refuses a debug target the browser advertises on a different host from the endpoint;
  • refuses a WebSocket handshake that redirects away from that target.

The last two checks still apply with KASM_RUNTIME_CDP_ALLOW_REMOTE set.

Limits: this controls where the runtime connects for DevTools. It does not stop another process in the session from connecting to the browser’s debugging port, and it does not restrict any other outbound connection.

The file methods (file.get, directory.get) and streaming routes (/v1/files/, /v1/dirs/) reach two roots: the session user’s home directory and /tmp. A path outside them is refused. They are offered to outside callers in mcp mode, never to the runtime’s own loop.

How it is enforced:

  • Each root is opened once as a root handle, and every open, create and rename goes through it. That refuses .. climbing out, absolute components, and symlinks pointing outside the root, including one swapped in between a check and the open.
  • Reads accept only a regular file, checked after the open, so a FIFO or device node at the path is refused.
  • A write goes to a temporary file first, then is linked into place without replacing an existing file, or renamed over it with overwrite. Setuid, setgid and sticky bits are never set.
  • Uploaded zip archives are validated whole before anything is written: relative clean names only, regular files and directories only, at most 10,000 entries, and the declared and actually decompressed sizes capped by KASM_RUNTIME_EXTRACT_MAX_BYTES (default 1 GiB). One upload is capped by KASM_RUNTIME_UPLOAD_MAX_BYTES (default 1 GiB). At most four transfers run at once, and one idle for 60 s is cut off.

Limits:

  • The roots stop a caller’s path from leaving them. They do not isolate a hard link already inside a root that points elsewhere, a bind mount under a root, or anything the session user’s filesystem already exposes there.
  • /tmp is writable by every user in the container.
  • The roots bound the file methods only. The shell method can move files anywhere the session user can write.