Skip to content

fix(gateway): allow app:// origins in control-ui allowlist checks#35057

Closed
bmendonca3 wants to merge 1 commit into
openclaw:mainfrom
bmendonca3:bm/issue-35035-control-ui-app-origin
Closed

fix(gateway): allow app:// origins in control-ui allowlist checks#35057
bmendonca3 wants to merge 1 commit into
openclaw:mainfrom
bmendonca3:bm/issue-35035-control-ui-app-origin

Conversation

@bmendonca3

@bmendonca3 bmendonca3 commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: Control UI origin checks rejected Electron origins like app://localhost even when explicitly listed in gateway.controlUi.allowedOrigins.
  • Why it matters: ClawControl desktop app connections over remote gateways (for example Tailscale Serve) were blocked unless users used wildcard "*" origins.
  • What changed: Origin parsing now normalizes custom-scheme URL origins with host components (for example app://localhost) instead of treating them as opaque null origins.
  • What changed: Added regression coverage proving allowedOrigins: ["app://localhost"] is accepted.
  • What did NOT change (scope boundary): No auth, pairing, host-header fallback, or wildcard origin behavior was changed.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

gateway.controlUi.allowedOrigins entries like app://localhost now work as expected for Control UI/WebSocket origin checks.

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node v22.22.0, pnpm v10.23.0
  • Model/provider: N/A
  • Integration/channel (if any): Gateway Control UI WebSocket handshake path
  • Relevant config (redacted): gateway.controlUi.allowedOrigins=["app://localhost"]

Steps

  1. Configure gateway with gateway.controlUi.allowedOrigins=["app://localhost"].
  2. Attempt a Control UI/WebSocket handshake with Origin: app://localhost.
  3. Observe origin check result.

Expected

  • Origin check accepts the request by allowlist match.

Actual

  • Before fix: rejected with origin not allowed because parsed origin was normalized to null.
  • After fix: accepted via allowlist.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios:
    • Added regression test that failed before and passes after for app://localhost allowlisting.
    • Existing origin-check and browser-hardening auth tests pass unchanged.
  • Edge cases checked:
    • Existing wildcard allowlist behavior remains passing.
    • Existing host-header fallback behavior remains passing.
  • What you did not verify:
    • Live remote gateway handshake against a real ClawControl desktop app.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly:
    • Revert this PR commit.
  • Files/config to restore:
    • src/gateway/origin-check.ts
    • src/gateway/origin-check.test.ts
  • Known bad symptoms reviewers should watch for:
    • Unexpected acceptance/rejection changes for non-HTTP origins in Control UI/WebSocket checks.

Risks and Mitigations

List only real risks for this PR. Add/remove entries as needed. If none, write None.

  • Risk:
    • Non-standard schemes with host components are now normalized into comparable origin strings.
    • Mitigation:
      • Behavior is constrained to exact allowlist comparison and covered by regression tests.

@greptile-apps

greptile-apps Bot commented Mar 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where custom protocol origins (e.g., app://localhost used by Electron/Tauri apps) could never be matched against the allowedOrigins allowlist in the gateway's origin check logic. The root cause is a WHATWG URL spec behavior: url.origin returns the string "null" for any non-special scheme (anything that isn't http, https, ws, wss, ftp, or file), so allowlist lookups using that value would always fail.

The fix in parseOrigin() adds a normalization step that reconstructs the origin as ${url.protocol}//${url.host} when url.origin === "null" but url.host is non-empty, producing the expected app://localhost string. The existing guard if (!trimmed || trimmed === "null") (line 14) continues to correctly reject raw null origin headers before any URL parsing occurs, so this change does not weaken the protection against opaque-origin bypass attempts. blob: and data: URLs are also unaffected because their url.host is empty, keeping them in the url.origin === "null" path unchanged.

  • src/gateway/origin-check.ts: adds a one-line normalization inside parseOrigin to recover a meaningful origin string for non-special-scheme URLs.
  • src/gateway/origin-check.test.ts: adds a regression test confirming that an app://localhost origin is accepted with matchedBy: "allowlist" when it appears in allowedOrigins.

Confidence Score: 5/5

  • This PR is safe to merge — it is a minimal, well-targeted fix with a direct regression test and no changes to security-critical logic paths.
  • The change is a single-line normalization in parseOrigin that only activates for URLs where url.origin === "null" AND url.host is non-empty. This is a narrow, well-understood edge case (non-special WHATWG URL schemes). The existing opaque-origin null guard is untouched, blob:/data: URLs are unaffected, and the new behavior (matching custom-protocol origins against the allowlist) is exactly the intended fix. The accompanying regression test validates both the success path and the matchedBy discriminant.
  • No files require special attention.

Last reviewed commit: 1d9cc9b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gateway Gateway runtime size: XS

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClawControl app: 'origin not allowed' for app://localhost even when in allowedOrigins

2 participants