Files
galaxy/specs/APP-3801/PRODUCT.md
T

8.6 KiB

Remote Server Authentication

Linear: APP-3801

Summary

The remote server gains a daemon-wide authentication layer so that handlers running on a remote host can make authenticated upstream calls to Warp services on behalf of the Warp user driving the daemon. The initial credential rides on the existing Initialize handshake as a new auth_token field (no extra round-trip on connection setup); mid-session rotation uses a dedicated new Authenticate message. The credential lives in daemon memory for the daemon's lifetime and is cleared only on process exit — there is no explicit protocol message to clear it mid-life.

Problem

Today the remote server has no notion of user identity. Any handler that needs to call Warp services (app.warp.dev APIs, upstream LLM routing, telemetry attribution, Drive-backed features) from the remote host has no credential to present. At the same time, APP-4068 makes the server a long-running daemon shared across multiple client connections from the same user's tabs, with a 10-minute grace period after the last disconnect. Any credential model for this system has to work within that architecture: the daemon is started by whichever proxy got there first, serves multiple concurrent connections, and may outlive any single SSH session.

Goals

  • Give the daemon a way to receive the current Warp credential for the user that owns its socket path.
  • Let handlers running on the daemon use that credential for upstream calls.
  • Support mid-session credential rotation so short-lived tokens (Firebase ID tokens) can refresh without tearing down the connection.
  • Keep the credential in daemon memory only — never on disk, never in process arguments or environment.
  • Minimize protocol surface: one new auth_token field on Initialize for the initial credential; one new Authenticate message for mid-session rotation; no new error codes in this PR, no explicit clear message.

Non-goals

  • Multi-user authentication on a shared daemon. All connections on a given daemon belong to the same Warp user by construction — socket-path partitioning by identity key in APP-4068 enforces this at the file-system level.
  • Validating credentials locally on the remote host. Validity is determined by the upstream service that receives them.
  • Persisting credentials across server restarts or across the daemon's grace-period expiry.
  • Securing the server against adversarial co-located Unix users on the remote host. SSH is assumed to be the trust boundary.
  • Authenticating the Initialize/InitializeResponse handshake itself. The handshake remains anonymous.
  • Remote MCP spawn or server-side MCP credential handling. MCP is assumed client-side.

Behavior

Proxy and daemon topology

The remote server runs as two distinct process roles on the remote host:

Daemon (remote-server-daemon): a long-lived process scoped to a single user identity. The first proxy to connect for a given identity spawns the daemon; subsequent proxies join the already-running daemon. The daemon listens on a Unix socket whose path is partitioned by an identity key — the Warp canonical user UUID for logged-in users, or a per-install persistent UUID for anonymous users. All authentication state lives exclusively in the daemon, as a single credential shared by all of its connections.

Proxy (remote-server-proxy): a short-lived process scoped to a single SSH session. Each Warp tab that connects to a remote host spawns its own proxy. The proxy byte-bridges SSH stdin/stdout to the daemon's Unix socket and is auth-unaware — it does not inspect, hold, or forward credentials. One user can have multiple proxies connected to the same daemon simultaneously (one per open tab).

The lifecycle of auth state maps to process events as follows:

  • Proxy connects: a new connection is registered with the daemon. The client calls initialize(auth_token) on that connection; the daemon stores (or overwrites) its singleton credential as part of handshake processing. No follow-up message is needed.
  • Proxy exits (SSH drop, tab close, user logout): the daemon deregisters that connection. The singleton credential is retained. Handlers on any remaining connections continue to see it.
  • Last proxy exits: the daemon enters its up-to-10-minute grace period. The credential is still held in memory but has no active consumer. A new proxy arriving during this window joins the existing daemon and sees the credential already populated; it nonetheless carries the current token on its own Initialize to keep the protocol path uniform.
  • Daemon exits (SIGTERM, panic, or grace-period expiry): all in-memory state, including the credential, is lost. The next proxy to connect starts a fresh daemon with no credential.

The daemon never mutates its credential in response to connection events. The singleton is only written by Initialize (carrying auth_token) or Authenticate, and only cleared by process exit.

Connection authentication

  1. The client carries its current bearer token on the Initialize handshake itself (as the new auth_token field). The daemon stores it as its single credential as part of handshake processing. No follow-up message is required to complete initial authentication.
  2. The credential is daemon-wide, not per-connection. All connections on a given daemon share one credential because the socket path guarantees they all belong to the same Warp user.
  3. For mid-session rotation, the client sends a fire-and-forget Authenticate message. It carries only a new bearer token; no acknowledgement is sent. Initialize retains its existing request-response structure and is only used for new connections.
  4. Any connection may carry a refresh Authenticate; the client picks one arbitrarily and does not fan out. Initial authentication and refresh are distinct on the wire (the former rides on Initialize), though both write to the same daemon singleton.

Server-side credential usage

  1. When a handler needs an upstream credential, it reads the daemon's single credential. The originating connection's identity is irrelevant.
  2. If the daemon has no credential stored, handler behavior is defined by the PR that introduces the first such handler (see TECH.md §7). It is out of scope for this PR; no handler in this PR reads the credential.
  3. Local-only handlers (filesystem operations, local command execution, repo metadata indexing) behave identically whether or not the daemon has a credential.

Lifecycle

  1. The credential exists only in daemon memory. It is never written to disk, environment variables, process arguments, or any on-disk artifact of any oz remote-server* binary.
  2. Deregistering a connection does not clear the credential. The daemon deliberately retains it across connection teardown so that other connections (and any future reconnects to the same daemon) continue to work without re-auth machinery.
  3. The credential is cleared only on daemon process exit — SIGTERM, panic, or APP-4068's grace-period expiry after the last proxy disconnects. There is no intermediate cleanup path.
  4. A reconnected client (after SSH drop or explicit teardown) rejoins the existing daemon if it is still alive and sees the same credential. If it arrives after a daemon restart, it observes a fresh daemon with no credential and must authenticate.

Client-side responsibilities

  1. RemoteServerClient exposes two methods: initialize(auth_token) carries the initial credential during handshake, and authenticate(token) refreshes the credential mid-session. The client is responsible for calling initialize with the current bearer token when establishing a new connection, and authenticate only when the local token rotates.
  2. On rotation, the manager picks one arbitrary Connected session and sends authenticate on it. No fan-out; the daemon's singleton propagates the new value to every handler.
  3. On logout, the client tears down its remote connections. The daemon's credential is cleared only when the daemon process exits at grace-period expiry. Mid-life clearing is not part of the protocol.

Security invariants

  1. The credential is transmitted only over the already-encrypted client-to-server byte stream (SSH stdin/stdout for the per-SSH topology; the local Unix socket for the APP-4068 daemon topology, whose file-system permissions are owned by APP-4068 and scoped to the owning user). It never appears in process arguments, environment variables, or on-disk artifacts of any oz remote-server* binary.
  2. The credential is never written to logs. Server-side log statements redact the credential field whenever Initialize or Authenticate messages are traced.