Skip to main content
Version0.1.0
Purityserver-only
PurposeSame-origin WS proxy (/api/gateway/ws) that injects the upstream auth token and a persistent Ed25519 device signature into connect frames server-side, plus a token-cookie access gate for HTTP/WS.
Workspace deps@clawboo/config
External deps@noble/ed25519, ws
The proxy is what keeps the browser from ever seeing the Gateway token: the SPA opens a WebSocket to the same origin, and the proxy fills in auth.token and a device signature on the connect frame before forwarding upstream. The device identity is a persistent keypair on disk (~/.clawboo/proxy-device-identity.json), so preview / incognito / fresh-machine browser contexts all connect without managing their own keys. Its device-auth primitives are also re-used by the server-side AgentSource connection (a non-browser GatewayClient) via the gateway-client signConnect hook.

Public API

Functions

ExportSignatureContract
createGatewayProxy(options: ProxyOptions) => GatewayProxyHandleBuilds the WS proxy. Loads the proxy device identity eagerly, opens the upstream connection per browser WS (before the first browser message, to catch the spontaneous connect.challenge), injects token + device signature into connect frames, and forwards bidirectionally with a bounded connect-buffer and an application-level keepalive.
createAccessGate(options?: AccessGateOptions) => AccessGateBuilds a token-cookie access gate. Disabled when no token is given (enabled: false, all requests pass). When enabled: a ?access_token=… query sets an HttpOnly cookie and 302-redirects; /api/* requests then require that cookie. Token comparison is constant-time (SHA-256 + timingSafeEqual).
getProxyDeviceIdentityPath() => stringAbsolute path of the proxy’s Ed25519 identity file, resolveClawbooDir()/proxy-device-identity.json (holds the private key).
loadOrCreateProxyDeviceIdentity() => Promise<DeviceIdentity>Loads the persisted identity (re-hardening perms to 0600 on every load), or generates + persists a fresh one (0700 dir, 0600 file). Persistence is best-effort; an in-memory identity still works for the session if the write fails.
signConnectParams(identity: DeviceIdentity, params: Record<string, unknown>, nonce: string | null) => Promise<ProxyDeviceFields>Extracts client.id / client.mode / role / scopes / auth.token from connect params, builds the device-auth payload (v2 when a nonce is present, else v1), and Ed25519-signs it. Returns the { device: { id, publicKey, signature, signedAt, nonce? } } field to merge into the connect frame.

Types & interfaces

ExportKindContract
ProxyOptionsinterfacecreateGatewayProxy input: { loadUpstreamSettings: () => Promise<UpstreamSettings>; allowWs?; log?; logError?; keepaliveIntervalMs?; maxPendingFrames? }. Defaults: allowWs matches /api/gateway/ws, keepalive 30_000 ms, buffer cap 512 frames.
UpstreamSettingsinterface{ url: string; token: string }, the upstream Gateway WS URL + bearer token, resolved per connection by loadUpstreamSettings.
GatewayProxyHandleinterfacecreateGatewayProxy return: { handleUpgrade: (req, socket, head) => void; wss: WebSocketServer }. Pass handleUpgrade to server.on('upgrade', …).
AccessGateOptionsinterface{ token?: string; cookieName?: string; queryParam?: string }. Defaults: cookie clawboo_access, query param access_token. Empty/undefined token disables the gate.
AccessGateinterface{ enabled: boolean; handleHttp: (req, res) => boolean; allowUpgrade: (req) => boolean }. handleHttp returns true when it has written the response (caller must not write again).
DeviceIdentitytype{ deviceId: string; publicKey: string; privateKey: string }, public/private keys base64url-encoded; deviceId is the SHA-256 fingerprint of the public key.
ProxyDeviceFieldstype{ device: { id: string; publicKey: string; signature: string; signedAt: number; nonce?: string } }, the signed device field merged onto connect params.
The barrel exposes no classes or runtime constants. WebSocketServer surfaces only as a field type on GatewayProxyHandle.wss (re-exported from ws, not from this package). The keepalive interval and frame-cap are config options on ProxyOptions, not exported constants.

Used by

  • apps/web/server/index.ts, boots the proxy (createGatewayProxy) for the /api/gateway/ws upgrade and the createAccessGate for HTTP/WS authorization.
  • apps/web/server/lib/agentSource/registry.ts, re-uses loadOrCreateProxyDeviceIdentity + signConnectParams (+ the DeviceIdentity type) so the server-side OpenClawAgentSource’s non-browser GatewayClient signs its own connect frames with the already-paired proxy identity.
  • apps/web/server/lib/bootProbe.ts, calls getProxyDeviceIdentityPath to check the vault/identity file location during the boot health probe.

Source

Barrel: packages/gateway-proxy/src/index.ts. Implementation: proxy.ts (the WS proxy + token/device injection + keepalive), access-gate.ts (the token-cookie gate), proxy-device-auth.ts (Ed25519 identity load/generate + connect-frame signing).

See also