openclaw - 💡(How to fix) Fix Gateway strips operator scopes on WebSocket connections with missing Origin header, bypassing dangerouslyDisableDeviceAuth [3 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#50022Fetched 2026-04-08 01:00:13
View on GitHub
Comments
3
Participants
3
Timeline
5
Reactions
0
Timeline (top)
commented ×3closed ×1locked ×1

Root Cause

This means dangerouslyDisableDeviceAuth: true and allowInsecureAuth: true have no effect because both require isControlUi === true, which depends on origin matching allowedOrigins.

Fix Action

Workaround

Currently no reliable workaround exists. Tested:

  • auth.mode: "none" → auth passes but sharedAuthOk=false → device identity still enforced → scopes wiped
  • auth.mode: "token" + dangerouslyDisableDeviceAuth + allowInsecureAuth → origin=n/a → not Control UI → scopes wiped
  • Raw TCP pipe proxy (replacing http-proxy) to forward Origin → untested (gateway restart catch-22)
  • MC v2.0.1's registerMcAsDashboard() auto-registers origin but doesn't fix the proxy stripping issue

Code Example

closed before connect origin=n/a host=127.0.0.1:18789 code=1008 reason=pairing required

---

res ✗ status 0ms errorCode=INVALID_REQUEST errorMessage=missing scope: operator.read
res ✗ system-presence 0ms errorCode=INVALID_REQUEST errorMessage=missing scope: operator.read
res ✗ config.get 0ms errorCode=INVALID_REQUEST errorMessage=missing scope: operator.read
RAW_BUFFERClick to expand / collapse

Environment

  • OpenClaw: 2026.3.13
  • OS: Ubuntu 24.04.1 LTS (WSL2)
  • Node: v22.22.0
  • Gateway config: bind: "loopback", auth.mode: "token", controlUi.dangerouslyDisableDeviceAuth: true
  • Access: Mission Control v2 dashboard via http://localhost:3005 through /ws-proxy reverse proxy

Problem 1: Missing Origin treated as mismatched Origin

When a WebSocket connection reaches the gateway through a reverse proxy that strips the Origin header (e.g., Tailscale Serve, http-proxy npm package, or any L7 proxy), the gateway logs origin=n/a and does not recognize the connection as a Control UI client.

This means dangerouslyDisableDeviceAuth: true and allowInsecureAuth: true have no effect because both require isControlUi === true, which depends on origin matching allowedOrigins.

Expected: A missing origin (n/a) from a loopback connection with a valid auth token should be treated differently from a mismatched origin (https://evil.com). When dangerouslyDisableDeviceAuth is enabled, missing origins from trusted/local connections should be accepted.

Gateway log:

closed before connect origin=n/a host=127.0.0.1:18789 code=1008 reason=pairing required

Problem 2: clearUnboundScopes() strips scopes even with valid token auth

When a WebSocket connection succeeds the auth check (auth.mode: "token" + valid token) but is not recognized as Control UI (due to Problem 1), the gateway calls clearUnboundScopes() which wipes all scopes including operator.admin and operator.read.

The decision tree in the handshake:

  1. sharedAuthOk = true (token validated) ✅
  2. roleCanSkipDeviceIdentity(role, sharedAuthOk)true when role === "operator" && sharedAuthOk
  3. But evaluateMissingDeviceIdentity() is only reached for Control UI connections. For non-Control-UI connections (because origin=n/a), the device identity check takes a different path that calls clearUnboundScopes().

Result: Connection succeeds but with empty scopes. Every subsequent request fails:

res ✗ status 0ms errorCode=INVALID_REQUEST errorMessage=missing scope: operator.read
res ✗ system-presence 0ms errorCode=INVALID_REQUEST errorMessage=missing scope: operator.read
res ✗ config.get 0ms errorCode=INVALID_REQUEST errorMessage=missing scope: operator.read

Problem 3: dangerouslyDisableDeviceAuth doesn't bypass scope binding

The flag dangerouslyDisableDeviceAuth: true successfully skips device pairing verification, but does not prevent clearUnboundScopes() from wiping scopes when device identity signing fails or the device is unrecognized.

The flag should bypass the entire device-auth pipeline including scope binding, not just the pairing check.

Suggested Fixes

  1. Distinguish missing vs mismatched origin: When origin is absent (n/a) and the connection comes from a loopback/trusted address with a valid auth token, treat it as a Control UI connection (or at minimum, don't reject it).

  2. Don't strip scopes when dangerouslyDisableDeviceAuth is true: The flag name implies "disable device auth entirely." It should prevent clearUnboundScopes() from running regardless of device identity state.

  3. Consider allowing "*" or "" in allowedOrigins: To explicitly accept connections with no Origin header, which is common behind reverse proxies.

Workaround

Currently no reliable workaround exists. Tested:

  • auth.mode: "none" → auth passes but sharedAuthOk=false → device identity still enforced → scopes wiped
  • auth.mode: "token" + dangerouslyDisableDeviceAuth + allowInsecureAuth → origin=n/a → not Control UI → scopes wiped
  • Raw TCP pipe proxy (replacing http-proxy) to forward Origin → untested (gateway restart catch-22)
  • MC v2.0.1's registerMcAsDashboard() auto-registers origin but doesn't fix the proxy stripping issue

Reproduction

  1. Set gateway.auth.mode: "token" with gateway.controlUi.dangerouslyDisableDeviceAuth: true
  2. Connect to the gateway WebSocket through any reverse proxy that strips the Origin header
  3. Send a valid connect frame with auth: { token: "<valid>" }, role: "operator", scopes: ["operator.admin"], clientId: "openclaw-control-ui"
  4. Observe: connection succeeds but all operator requests fail with missing scope: operator.read

extent analysis

Fix Plan

To address the issues, we need to modify the gateway's authentication and authorization logic. Here are the steps:

  1. Distinguish missing vs mismatched origin:

    • Update the isControlUi check to consider a missing origin (n/a) from a loopback connection with a valid auth token as a Control UI connection.
    • Add a new configuration option to explicitly allow connections with no Origin header.
  2. Don't strip scopes when dangerouslyDisableDeviceAuth is true:

    • Modify the clearUnboundScopes function to check for the dangerouslyDisableDeviceAuth flag and skip scope stripping when it's enabled.
  3. Allow "*" or "" in allowedOrigins:

    • Update the allowedOrigins validation to accept "*" or "" as valid values, allowing connections with no Origin header.

Example Code Changes

// Update isControlUi check
function isControlUi(origin, host, token) {
  if (origin === 'n/a' && host === '127.0.0.1' && token !== null) {
    return true; // Treat as Control UI connection
  }
  // Existing logic...
}

// Add new configuration option
const allowNoOrigin = true; // Allow connections with no Origin header

// Modify clearUnboundScopes function
function clearUnboundScopes(scopes, dangerouslyDisableDeviceAuth) {
  if (dangerouslyDisableDeviceAuth) {
    return scopes; // Don't strip scopes when flag is enabled
  }
  // Existing logic...
}

// Update allowedOrigins validation
function validateAllowedOrigins(allowedOrigins) {
  if (allowedOrigins.includes('*') || allowedOrigins.includes('')) {
    return true; // Accept "*" or "" as valid values
  }
  // Existing logic...
}

Verification

To verify the fixes, follow these steps:

  1. Apply the code changes and restart the gateway.
  2. Set gateway.auth.mode to "token" and gateway.controlUi.dangerouslyDisableDeviceAuth to true.
  3. Connect to the gateway WebSocket through a reverse proxy that strips the Origin header.
  4. Send a valid connect frame with a token, role, scopes, and clientId.
  5. Observe that the connection succeeds and operator requests are processed correctly.

Extra Tips

  • Ensure that the dangerouslyDisableDeviceAuth flag is used with caution, as it bypasses device authentication and scope binding.
  • Consider implementing additional security measures, such as IP whitelisting or rate limiting, to prevent unauthorized access to the gateway.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING