Skip to content

fix: allow custom control UI origins#73511

Open
ishangodawatta wants to merge 1 commit intoopenclaw:mainfrom
ishangodawatta:IshanG97/46520-custom-origin-schemes
Open

fix: allow custom control UI origins#73511
ishangodawatta wants to merge 1 commit intoopenclaw:mainfrom
ishangodawatta:IshanG97/46520-custom-origin-schemes

Conversation

@ishangodawatta
Copy link
Copy Markdown

@ishangodawatta ishangodawatta commented Apr 28, 2026

Summary

  • Problem: gateway.controlUi.allowedOrigins can include a custom desktop-app origin like tauri://localhost, but the gateway normalises that browser Origin header to the opaque null origin and never matches the explicit allowlist entry.
  • Why it matters: Tauri/Electron-style Control UI wrappers have to fall back to wildcard origins even when operators want a specific origin allowlist.
  • What changed: preserve the exact scheme://host origin for non-special URL schemes when the browser sends a scheme and host, then continue matching through the existing lower-case allowlist path.
  • What did NOT change (scope boundary): literal Origin: null remains rejected, unlisted custom origins remain rejected, and host-header/local-loopback fallbacks keep their existing rules.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • 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

Root Cause (if applicable)

  • Root cause: new URL("tauri://localhost").origin returns the opaque string null, so exact allowlist matching compares tauri://localhost with null.
  • Missing detection / guardrail: the origin-check unit table did not include custom-scheme browser origins.
  • Contributing context (if known): browser desktop wrappers can send non-special scheme origins even though those schemes do not have a standard web origin serialisation.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: src/gateway/origin-check.test.ts
  • Scenario the test should lock in: allowlisted custom-scheme origins match exactly after normalisation, while unlisted custom-scheme origins still fail.
  • Why this is the smallest reliable guardrail: the bug is isolated to origin normalisation before the existing allowlist check.
  • Existing test that already covers this (if any): none.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

Operators can set gateway.controlUi.allowedOrigins to values like tauri://localhost without using * for desktop Control UI wrappers.

Diagram (if applicable)

Before:
tauri://localhost -> URL.origin "null" -> allowlist miss

After:
tauri://localhost -> preserved custom origin -> explicit allowlist match

Security Impact (required)

  • New permissions/capabilities? (Yes/No) No
  • Secrets/tokens handling changed? (Yes/No) No
  • New/changed network calls? (Yes/No) No
  • Command/tool execution surface changed? (Yes/No) No
  • Data access scope changed? (Yes/No) No
  • If any Yes, explain risk + mitigation: N/A. This keeps explicit allowlist matching required for non-wildcard custom-scheme origins.

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: local source checkout
  • Model/provider: N/A
  • Integration/channel (if any): Gateway Control UI origin check
  • Relevant config (redacted): gateway.controlUi.allowedOrigins = ["tauri://localhost"]

Steps

  1. Configure gateway.controlUi.allowedOrigins with tauri://localhost.
  2. Connect with browser Origin: tauri://localhost.
  3. Compare against checkBrowserOrigin behaviour.

Expected

  • The explicit custom-scheme allowlist entry is accepted.
  • Unlisted custom-scheme origins are rejected.

Actual

  • Current main sees new URL("tauri://localhost").origin as null, so the explicit allowlist entry does not match.

Evidence

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

Local command evidence:

  • git diff --check HEAD~1..HEAD passed.
  • Manual Node parser check passed for tauri://LOCALHOST, electron://localhost, https://CONTROL.example.com, literal null, and malformed not a url cases.
  • pnpm docs:list was attempted but blocked by Corepack registry timeout while downloading pnpm 10.33.0 (UND_ERR_CONNECT_TIMEOUT).
  • pnpm test src/gateway/origin-check.test.ts reached the repo test wrapper via cached pnpm but could not run because this worktree has no vitest dependency and dependency installation is blocked by registry timeouts.

Human Verification (required)

  • Verified scenarios: source-level root cause in Node URL behaviour; exact custom-scheme origin preservation; literal null and malformed origins still return no parsed origin.
  • Edge cases checked: upper-case custom origin host normalises to lower case; unlisted custom scheme remains rejected by the unit table.
  • What you did not verify: local Vitest/full changed gate, because this machine cannot install missing npm dependencies from the registry. CI should run the new test.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

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

Risks and Mitigations

  • Risk: custom desktop wrapper origins could be over-accepted.
    • Mitigation: this only changes the parsed origin string; explicit allowlist, wildcard, host-header fallback, and local-loopback checks remain the same, and tests cover an unlisted custom-scheme rejection.

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime size: XS labels Apr 28, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 28, 2026

Greptile Summary

This PR fixes allowlist matching for custom-scheme browser origins (e.g. tauri://localhost) by handling the WHATWG URL spec behavior where url.origin returns the string "null" for non-special schemes. The fix reconstructs the origin from protocol + "//" + host when url.origin === "null" and url.host is non-empty, while keeping literal Origin: null headers and custom origins not present in the allowlist rejected. The implementation is correct and the new test cases cover the key scenarios.

Confidence Score: 5/5

Safe to merge — targeted fix with correct logic and no security regressions.

The change is minimal and well-reasoned: it correctly handles the WHATWG URL origin === "null" edge case for non-special schemes, the early guard for literal "null" headers is preserved, and custom-scheme origins only pass when explicitly listed in allowedOrigins. New test cases cover the allowlist match and reject paths.

No files require special attention.

Reviews (1): Last reviewed commit: "fix: allow custom control UI origins" | Re-trigger Greptile

@clawsweeper
Copy link
Copy Markdown
Contributor

clawsweeper Bot commented Apr 28, 2026

Codex review: needs maintainer review before merge.

Summary
The PR updates Gateway origin normalization so hosted non-special schemes can match explicit gateway.controlUi.allowedOrigins entries, adds origin-check regression cases, and adds a changelog entry.

Reproducibility: yes. Current main plus the Node URL check gives a high-confidence reproduction: tauri://localhost parses with URL.origin === "null", so checkBrowserOrigin compares "null" to an explicit tauri://localhost allowlist entry and rejects it.

Next step before merge
This is an active contributor PR with a valid Gateway authorization-boundary fix and no discrete automated repair finding; the next action is maintainer review plus current-head CI and changelog refresh.

Security
Cleared: No concrete security or supply-chain regression was found; the diff only changes origin normalization, tests, and changelog text while preserving explicit allowlist matching as the authorization gate.

Review details

Best possible solution:

Land this narrow Gateway normalization fix after maintainer review, refreshed changelog placement, and green validation, while preserving explicit allowlist enforcement for custom-scheme origins.

Do we have a high-confidence way to reproduce the issue?

Yes. Current main plus the Node URL check gives a high-confidence reproduction: tauri://localhost parses with URL.origin === "null", so checkBrowserOrigin compares "null" to an explicit tauri://localhost allowlist entry and rejects it.

Is this the best way to solve the issue?

Yes. Reconstructing scheme://host only for opaque URL origins that still have a host is the narrowest maintainable fix because it feeds the existing allowlist path instead of adding a Tauri/Electron-specific bypass.

Acceptance criteria:

  • pnpm test src/gateway/origin-check.test.ts
  • pnpm check:changed

What I checked:

  • current-main-parser: parseOrigin constructs new URL(trimmed) and stores url.origin directly, so hosted non-special schemes normalize to the opaque string null before allowlist comparison. (src/gateway/origin-check.ts:22, a53be2d2ce8c)
  • runtime-url-root-cause: Node reports tauri://LOCALHOST and electron://localhost with origin: "null" while preserving protocol and host, matching the reported allowlist failure mode. (a53be2d2ce8c)
  • affected-handshake-surface: The browser operator UI/WebChat WebSocket path passes requestOrigin and gateway.controlUi.allowedOrigins into checkBrowserOrigin and returns the origin-mismatch failure on rejection. (src/gateway/server/ws-connection/message-handler.ts:518, a53be2d2ce8c)
  • docs-allowlist-policy: Control UI docs require explicit full origins for non-loopback deployments and warn that allowedOrigins: ["*"] allows any browser origin, so the reported wildcard workaround is broader than intended. Public docs: docs/web/control-ui.md. (docs/web/control-ui.md:441, a53be2d2ce8c)
  • pr-diff-scope: The PR reconstructs scheme://host only when url.origin === "null" and url.host is present, then continues through the existing lowercase allowlist path; it also adds accepted and rejected custom-scheme regression cases plus changelog text. (src/gateway/origin-check.ts:22, 5d19030888d5)
  • pr-discussion-context: The linked issue comment says the PR author verified current main and opened this narrow parser/test/changelog fix; Greptile and the existing ClawSweeper review both treated the implementation as targeted and safe pending maintainer review. (5d19030888d5)

Likely related people:

  • steipete: The provided prior ClawSweeper history pass traces the explicit non-loopback Control UI origin requirement, WebSocket origin hardening, loopback fallback tightening, and origin-check extraction through this Gateway surface. (role: introduced behavior and recent maintainer; confidence: high; commits: 66d8117d4483, 223d7dc23d73, d5ae4b83378d; files: src/gateway/origin-check.ts, src/gateway/origin-check.test.ts, src/gateway/server/ws-connection/message-handler.ts)
  • Vincent Koc: Current local git log and blame show the extracted origin-check helper and test file present in recent main commits, making this person relevant for current-file routing even though older history is partly promisor-backed in this checkout. (role: recent current-main maintainer; confidence: medium; commits: bc0f89074f85, faf8a0774cc2; files: src/gateway/origin-check.ts, src/gateway/origin-check.test.ts)
  • byungsker: Prior feature-history evidence attributes nearby wildcard allowlist behavior and regression coverage in the same origin-check surface to this contributor. (role: adjacent origin allowlist contributor; confidence: medium; commits: 904016b7de43; files: src/gateway/origin-check.ts, src/gateway/origin-check.test.ts)
  • frankekn: Prior feature-history evidence links wildcard support for controlUi.allowedOrigins remote access to the same helper/test area this PR changes. (role: adjacent allowed-origins contributor; confidence: medium; commits: 1636f7ff5fd7; files: src/gateway/origin-check.ts, src/gateway/origin-check.test.ts)

Remaining risk / open question:

  • Current PR head is not fully green; aggregate check runs such as check, check-additional, check-lint, and the parity gate report failures on the head SHA.
  • The patch check against the current checkout fails on the CHANGELOG.md context, so changelog placement likely needs refresh before landing.

Codex review notes: model gpt-5.5, reasoning high; reviewed against a53be2d2ce8c.

@ishangodawatta ishangodawatta force-pushed the IshanG97/46520-custom-origin-schemes branch 3 times, most recently from d01ea64 to 36d0bdb Compare April 28, 2026 12:32
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.

[Bug]: Origin validation fails for custom URL schemes (tauri://, electron://)

1 participant