diff --git a/docs/gateway/protocol.md b/docs/gateway/protocol.md index 3dfc582bea4b..fe17ebee98ff 100644 --- a/docs/gateway/protocol.md +++ b/docs/gateway/protocol.md @@ -780,16 +780,19 @@ rather than the pre-handshake defaults. - `gateway.controlUi.allowInsecureAuth=true` for localhost-only insecure HTTP compatibility. - successful `gateway.auth.mode: "trusted-proxy"` operator Control UI auth. - `gateway.controlUi.dangerouslyDisableDeviceAuth=true` (break-glass, severe security downgrade). - - direct-loopback `gateway-client` backend RPCs authenticated with the shared - gateway token/password. -- Omitting device identity has scope consequences. When a Control UI connection - lacks device identity, `shouldClearUnboundScopesForMissingDeviceIdentity` - clears self-declared scopes to an empty set for token, password, and - trusted-proxy auth. The connection is allowed on explicit trust paths, but - scope-gated methods fail. The exception is local Control UI token/password - sessions with `allowInsecureAuth`, which preserve scopes. For other cases, - set `gateway.controlUi.dangerouslyDisableDeviceAuth=true` only as a - break-glass scope-preservation path. + - direct-loopback `gateway-client` backend RPCs on the reserved internal + helper path. +- Omitting device identity has scope consequences. When a device-less operator + connection is allowed through an explicit trust path, OpenClaw still clears + self-declared scopes to an empty set unless that path has a named + scope-preservation exception. Scope-gated methods then fail with + `missing scope`. +- `gateway.controlUi.dangerouslyDisableDeviceAuth=true` is a Control UI + break-glass scope-preservation path. It does not grant scopes to arbitrary + custom backend or CLI-shaped WebSocket clients. +- The reserved direct-loopback `gateway-client` backend helper path preserves + scopes only for internal local control-plane RPCs; custom backend IDs do not + receive this exception. - All connections must sign the server-provided `connect.challenge` nonce. ### Device auth migration diagnostics diff --git a/docs/gateway/trusted-proxy-auth.md b/docs/gateway/trusted-proxy-auth.md index 9cad73a3386c..6bf249681d8d 100644 --- a/docs/gateway/trusted-proxy-auth.md +++ b/docs/gateway/trusted-proxy-auth.md @@ -53,24 +53,23 @@ Use `trusted-proxy` auth mode when: When `gateway.auth.mode = "trusted-proxy"` is active and the request passes trusted-proxy checks, Control UI WebSocket sessions can connect without device pairing identity. +Scope implications: + +- Device-less Control UI WebSocket sessions connect but receive no operator scopes by default. OpenClaw clears the requested scope list to `[]` so a session that is not bound to an approved paired device/token cannot self-declare permissions. +- If methods fail with `missing scope` after a successful WebSocket connect, use HTTPS so the browser can generate device identity and complete pairing. See [Control UI insecure HTTP](/web/control-ui#insecure-http). +- Break-glass only: `gateway.controlUi.dangerouslyDisableDeviceAuth=true` preserves requested scopes even without device identity. This is a severe security downgrade; revert quickly. See [Control UI insecure HTTP](/web/control-ui#insecure-http). + +Reverse-proxy scope capping: + +- If your proxy sends `x-openclaw-scopes` on the Control UI WebSocket upgrade request, OpenClaw caps the session scopes to the intersection of the requested scopes and the declared scopes. This header does not grant scopes; it only narrows what the session can hold. + Implications: - Pairing is no longer the primary gate for Control UI access in this mode. - Your reverse proxy auth policy and `allowUsers` become the effective access control. - Keep gateway ingress locked to trusted proxy IPs only (`gateway.trustedProxies` + firewall). -**Scope clearing without device identity:** Because the browser over plain HTTP -cannot create the device identity that OpenClaw uses to bind operator scopes, -trusted-proxy WebSocket connections that lack device identity have their -self-declared scopes cleared to an empty set. The connection is allowed, but -scope-gated methods (`operator.read`, `operator.write`, etc.) fail with -`missing scope`. - -To preserve operator scopes on trusted-proxy WebSocket connections without -device identity, set `gateway.controlUi.dangerouslyDisableDeviceAuth: true`. -This is a break-glass flag (`openclaw security audit` reports it as critical). -Use it only when the reverse proxy is the sole path to the Gateway and device -identity cannot be established. +Custom WebSocket clients are not Control UI sessions. `gateway.controlUi.dangerouslyDisableDeviceAuth` does not grant scopes to arbitrary `client.mode: "backend"` or CLI-shaped clients. Custom automation should use device identity/pairing, the reserved direct-local `client.id: "gateway-client"` backend helper path, or the [admin HTTP RPC plugin](/plugins/admin-http-rpc) when an HTTP request/response surface is a better fit. ## Configuration @@ -322,12 +321,9 @@ Loopback trusted-proxy identity headers still fail closed: same-host callers are ## Operator scopes header -Trusted-proxy auth is an **identity-bearing** HTTP mode, so callers may optionally declare operator scopes with `x-openclaw-scopes`. +Trusted-proxy auth is an **identity-bearing** HTTP mode, so callers may optionally declare operator scopes with `x-openclaw-scopes` on HTTP API requests. -Note: `x-openclaw-scopes` applies to HTTP endpoints only. WebSocket scopes are -determined by the Gateway protocol handshake and device identity binding. For -WebSocket scope behavior with trusted-proxy, see -[Control UI pairing behavior](#control-ui-pairing-behavior). +Note: WebSocket scopes are determined by the Gateway protocol handshake and device identity binding. On Control UI WebSocket upgrade requests, `x-openclaw-scopes` is only a cap on the negotiated session scopes, not a grant. For WebSocket scope behavior with trusted-proxy, see [Control UI pairing behavior](#control-ui-pairing-behavior). Examples: @@ -342,6 +338,7 @@ Behavior: - When the header is absent, normal identity-bearing HTTP APIs fall back to the standard operator default scope set. - Gateway-auth **plugin HTTP routes** are narrower by default: when `x-openclaw-scopes` is absent, their runtime scope falls back to `operator.write`. - Browser-origin HTTP requests still have to pass `gateway.controlUi.allowedOrigins` (or deliberate Host-header fallback mode) even after trusted-proxy auth succeeds. +- For Control UI WebSocket sessions, `x-openclaw-scopes` is a scope cap when present on the upgrade request. An empty value yields no scopes. Practical rule: send `x-openclaw-scopes` explicitly when you want a trusted-proxy request to be narrower than the defaults, or when a gateway-auth plugin route needs something stronger than write scope. @@ -427,17 +424,20 @@ The audit checks for: - The WebSocket connects, but `chat.history` or `sessions.list` fails with - `missing scope: operator.read`. + The WebSocket connects, but `chat.history`, `sessions.list`, or + `models.list` fails with `missing scope: operator.read`. - This is expected for trusted-proxy WebSocket connections without device - identity. Connections lacking device identity have their scopes cleared. The - browser cannot generate device identity over plain HTTP. + Common causes: + + - Device-less Control UI session: trusted-proxy auth can admit the WebSocket connection without device identity, but OpenClaw clears scopes on device-less sessions by design. + - Custom backend client: `gateway.controlUi.dangerouslyDisableDeviceAuth` is Control UI scoped and does not grant scopes to arbitrary backend or CLI-shaped WebSocket clients. + - Overly narrow `x-openclaw-scopes`: if your proxy injects this header on the Control UI WebSocket upgrade request, the session scopes are capped to that set. An empty header value yields no scopes. Fix: - - Set `gateway.controlUi.dangerouslyDisableDeviceAuth: true` to preserve operator scopes on trusted-proxy WebSocket connections, or - - Use device identity pairing so scopes are bound to the device token. + - For Control UI, use HTTPS so the browser can generate device identity and complete pairing. + - For custom automation, use device identity/pairing, the reserved direct-local `gateway-client` backend helper path, or [admin HTTP RPC](/plugins/admin-http-rpc). + - Use `gateway.controlUi.dangerouslyDisableDeviceAuth: true` only as a temporary Control UI break-glass path.