Skip to the page
Conch
DocsGitHub

Decisions

0011 — Authenticated reverse-proxy deployment

Status: accepted · 2026-09-29

  • Status: accepted
  • Date: 2026-09-29
  • Amended by: ADR 0064 (Conch can serve an address of its own over HTTPS itself; this proxy is the way for people who already run one), ADR 0067 (conch setup sets the proxy's name up and checks the way in through it; the hello link replaces the access key for people)

Decision

Serve the built web app from the loopback gateway behind an authenticated HTTPS reverse proxy. Keep the proxy login and Conch's independently revocable access keys. Configure deployment with CONCH_ALLOWED_HOSTS, CONCH_HOST, CONCH_PORT, CONCH_HOME and CONCH_WEB_DIST; no deployment hostname belongs in source.

The proxy preserves Host (including port), Origin and Fetch Metadata for HTTP and WebSockets. It discards client forwarding headers and sets its own trusted values. Conch must not trust arbitrary X-Forwarded-Host or disable origin checks. Host-rewriting routers need an explicit per-app preservation option.

The outer login covers HTML, assets, APIs and upgrades. Conch exchanges a key entered in its sign-in form for a host-only HttpOnly session cookie. No URL tokens, proxy-injected shared credentials, or legacy CONCH_TOKEN. Stripping Authorization at the outer proxy is compatible with browser key sign-in (JSON body then cookies).

Threat model

Internet clients, hostile browser origins (including sibling subdomains), leaked outer sessions and misconfigured forwarding must not gain Conch authority. Regression tests cover proxy-local confusion, unknown hosts, foreign-origin writes, key revocation, logout and HTTPS cookie attributes.

Conch still runs as its OS user, not inside a filesystem sandbox. The authenticated owner can change trust and workspace settings. Use Ask first, no active routines, no trusted custom integrations, a deliberate workspace and a minimal launch environment without unrelated host credentials. Serve built files through the gateway so its CSP covers the document; do not publish raw Vite source endpoints.

Sources and acceptance

Tests without a provider are necessary but not sufficient: verify both login gates, API writes, WebSockets, logout and revocation across the deployed proxy. See the deployment runbook.

Rejected upgrade connections

Live verification exposed a connection-reuse failure after rejected upgrades. Node detaches a socket from its HTTP parser when it emits upgrade. Conch's security hook can reject before the websocket plugin marks request.ws, so the plugin's normal rejection cleanup does not close that socket. An HTTP rejection advertising keep-alive then allows a proxy to reuse a connection that can no longer parse the next request, stalling subsequent upgrades.

An onSend hook now marks every HTTP response to a WebSocket upgrade Connection: close; an onResponse hook ends the detached socket after the response is flushed. Successful upgrades bypass these hooks via hijacking and remain unchanged. A real TCP regression verifies response delivery and EOF for 401, 403, and 421; the authenticated socket lifecycle test still verifies successful upgrade, ping and immediate revocation.

Sources: Node HTTP upgrade events and Fastify WebSocket hooks.