Skip to content

fix(gateway): support non-standard protocol origins in allowedOrigins check#35396

Closed
Sid-Qin wants to merge 1 commit into
openclaw:mainfrom
Sid-Qin:fix/35035-clawcontrol-origin-allowed
Closed

fix(gateway): support non-standard protocol origins in allowedOrigins check#35396
Sid-Qin wants to merge 1 commit into
openclaw:mainfrom
Sid-Qin:fix/35035-clawcontrol-origin-allowed

Conversation

@Sid-Qin

@Sid-Qin Sid-Qin commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Problem: Desktop apps using non-standard URL schemes (e.g. app://localhost for Electron/ClawControl, tauri://localhost for Tauri) are rejected by the gateway origin check even when explicitly listed in gateway.controlUi.allowedOrigins.
  • Why it matters: Users of ClawControl desktop app connecting over Tailscale Serve cannot authenticate — they must use the overly permissive allowedOrigins: ["*"] workaround.
  • What changed: parseOrigin() in src/gateway/origin-check.ts now reconstructs the origin string from protocol + "//" + host when URL.origin returns "null" (which Node.js/browsers do for non-standard protocols). Added 4 test cases covering non-standard protocol matching.
  • What did NOT change: Standard HTTP/HTTPS origin handling, wildcard matching, host-header fallback, local-loopback logic — all existing behavior is preserved.

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

  • app://localhost, tauri://localhost, and other non-standard protocol origins now work correctly in gateway.controlUi.allowedOrigins configuration.

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

Repro + Verification

Environment

  • OS: macOS / Linux
  • Runtime: Node.js 22+
  • Integration/channel: Gateway WebSocket (ClawControl desktop app)

Steps

  1. Set gateway.controlUi.allowedOrigins to ["app://localhost"]
  2. Connect from ClawControl desktop app (Electron) over Tailscale Serve

Expected

  • Connection accepted, matched by allowlist.

Actual

  • Before fix: origin not allowedURL.origin returns "null" for app:// scheme, so the allowlist lookup compares "null" against "app://localhost" and never matches.
  • After fix: Origin is reconstructed as protocol + "//" + host"app://localhost", which matches the allowlist entry.

Evidence

> new URL("app://localhost").origin
'null'   // ← this is why matching fails

> const url = new URL("app://localhost")
> `${url.protocol}//${url.host}`
'app://localhost'   // ← reconstructed origin matches allowlist

All 15 tests pass including 4 new ones:

✓ src/gateway/origin-check.test.ts (15 tests) 3ms
  Tests  15 passed (15)

Human Verification (required)

  • Verified scenarios: app://localhost, tauri://localhost, wildcard with non-standard origins, rejection when non-standard origin is not in allowlist
  • Edge cases checked: empty host, case sensitivity (lowercased), standard HTTP/HTTPS origins still work identically
  • What I did not verify: Live ClawControl desktop app connection (no Electron environment available)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Failure Recovery (if this breaks)

  • How to disable/revert: Revert this commit. The only change is in parseOrigin()'s origin construction for non-standard protocols.
  • Files/config to restore: src/gateway/origin-check.ts
  • Known bad symptoms: If the reconstructed origin format is wrong, non-standard origins would still be rejected (same as current behavior — no regression).

Risks and Mitigations

  • Risk: Synthetic origin format may differ from what the browser/Electron actually sends. Mitigation: The reconstruction uses the same protocol + "//" + host pattern that URL.href uses for these schemes, and the allowlist comparison is a direct string match against user-configured values — users control both sides.

… check

URL.origin returns "null" for non-standard protocols like app:// and
tauri:// used by desktop apps (Electron, Tauri). This caused
checkBrowserOrigin to always reject these origins even when explicitly
listed in gateway.controlUi.allowedOrigins.

Reconstruct origin from protocol + host when URL.origin is "null" so
that allowlist matching works correctly for ClawControl and other
desktop-app clients.

Closes openclaw#35035
@greptile-apps

greptile-apps Bot commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a bug where non-standard protocol origins (e.g. app://localhost, tauri://localhost) were always rejected by the gateway origin check because URL.origin returns the string "null" for non-standard schemes in Node.js/browsers. The fix is minimal and surgical: in parseOrigin(), when url.origin === "null", the origin is reconstructed as url.protocol + "//" + url.host before being compared against the allowlist.

Key observations:

  • The logic is sound. For non-standard schemes, url.protocol already includes the colon (e.g. "app:"), so "app:" + "//" + "localhost" correctly produces "app://localhost", matching user-configured allowlist entries.
  • The existing guard at line 14 (trimmed === "null") correctly handles the case where a browser sends an opaque Origin: null header — this is a distinct scenario and remains unaffected by the new code.
  • Standard HTTP/HTTPS origins are never affected since URL.origin only returns the string "null" for non-standard schemes; standard schemes always produce a proper origin value, so the reconstruction branch is never taken for them.
  • There is no spoofing risk: an attacker cannot craft a non-standard-protocol URL whose reconstruction resolves to a legitimate standard origin, because the reconstruction only runs when url.origin === "null", which never happens for http:/https:.
  • 4 well-chosen tests are added covering: app:// match, tauri:// match, rejection when not in allowlist, and wildcard allowance.

Confidence Score: 5/5

  • This PR is safe to merge — the change is minimal, provably correct, and fully covered by tests.
  • The fix is a single conditional in one function, the reconstruction formula is well-understood, the security boundary (allowlist opt-in) is unchanged, and the new tests validate all meaningful scenarios including rejection. No regressions to standard origin handling are possible.
  • No files require special attention.

Last reviewed commit: 80da54c

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

Labels

gateway Gateway runtime size: S

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