Skip to content

fix(onboard): include portless origin in allowedOrigins for reverse-proxy access - #3002

Merged
ericksoa merged 1 commit into
NVIDIA:mainfrom
senthilr-nv:fix/cors-origin-reverse-proxy
May 4, 2026
Merged

fix(onboard): include portless origin in allowedOrigins for reverse-proxy access#3002
ericksoa merged 1 commit into
NVIDIA:mainfrom
senthilr-nv:fix/cors-origin-reverse-proxy

Conversation

@senthilr-nv

@senthilr-nv senthilr-nv commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

When CHAT_UI_URL is set to an HTTPS URL without an explicit port — as documented for Brev deployments — onboard injects the internal dashboard port (:18789) into the origin via onboard.ts:4002. Reverse proxies (Brev Cloudflare Tunnel, nginx, Caddy, Tailscale Funnel) serve on standard :443, so the browser sends origin https://host which doesn't match https://host:18789 in allowedOrigins — causing "origin not allowed" on the dashboard.

Root cause: generate-openclaw-config.py builds allowedOrigins from the port-overridden URL only.

Fix: Also include the portless origin for non-loopback URLs. This is safe (same host) and covers both direct and reverse-proxy access.

Prior art

PR #812 and PR #2440 fixed the CHAT_UI_URL plumbing but the port-override gap remained. This PR closes the remaining gap from the #795 / #20 lineage.

Changes

File Change
scripts/generate-openclaw-config.py Add portless origin to allowedOrigins for non-loopback URLs when a port is present
test/generate-openclaw-config.test.ts Update existing test to expect portless origin; add new test for reverse-proxy case

Testing

  • Unit tests: 43/43 passing (vitest run test/generate-openclaw-config.test.ts)
  • Manual verification: On Brev GCP (nemoclaw-gcp, n2-standard-4), confirmed that adding the portless origin to allowedOrigins resolves the CORS error when accessing https://brev-nc-xxx.brevlab.com/chat?session=main

What this does NOT fix

  • onboard.ts:4002 still unconditionally overrides the port. A future improvement could skip the port override when the URL is HTTPS with no explicit port, but this config-level fix is sufficient and lower-risk.

Related

Signed-off-by: Senthil Ravichandran senthilr@nvidia.com

Summary by CodeRabbit

  • Bug Fixes

    • Improved origin handling: when a configured UI URL includes an explicit port and is non-loopback, the app accepts both the full origin (with port) and a portless origin; IPv6 hostnames keep bracket formatting and duplicate entries are removed. Loopback behavior is unchanged.
  • Tests

    • Expanded tests to cover ported and portless origins, reverse-proxy scenarios, IPv6 behavior (including loopback) and resilience to malformed port values.

@senthilr-nv senthilr-nv added bug platform: brev Affects Brev hosted development environments labels May 4, 2026
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The config generator now derives a portless origin (scheme://hostname, preserving IPv6 brackets) from CHAT_UI_URL when it includes an explicit port and the host is not loopback; the allowedOrigins list (loopback, full origin, optional portless origin) is de-duplicated and assigned to gateway.controlUi.allowedOrigins. Tests cover IPv4, IPv6, and loopback cases.

Changes

OpenClaw Portless Origin Support

Layer / File(s) Summary
Data Shape / Input Parsing
scripts/generate-openclaw-config.py
Parse CHAT_UI_URL, detect explicit port, determine loopback status, and preserve IPv6 brackets when building a portless hostname.
Core Implementation
scripts/generate-openclaw-config.py
Compute portless_origin only for non-loopback hosts with an explicit port; assemble origins as http://127.0.0.1:18789, the parsed full chat origin, and the optional portless origin; filter falsy entries and de-duplicate while preserving order.
Tests / Validation
test/generate-openclaw-config.test.ts
Add assertions verifying both full-origin-with-port and portless-origin for non-loopback hosts, reverse-proxy scenarios, IPv6 bracket preservation for public IPv6, absence of portless origin for IPv6 loopback, and resilience to malformed ports.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I spotted a port and gave the host a hop,
Brackets for IPv6, no loopback stop.
Two origins now, tidy and spry,
Duplicates gone — a quiet sigh.
A rabbit's tweak, quick as a hop, bye-bye.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately describes the main change: adding portless origins to allowedOrigins to support reverse-proxy access, which is the core fix addressing the CORS origin mismatch problem.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Comment @coderabbitai help to get the list of available commands and usage tips.

@senthilr-nv
senthilr-nv force-pushed the fix/cors-origin-reverse-proxy branch from 01b316f to 6583d8e Compare May 4, 2026 19:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/generate-openclaw-config.py`:
- Around line 202-206: portless_origin derivation should avoid raising
ValueError on malformed ports and must preserve IPv6 bracket notation: when
checking parsed.port, guard the access with a try/except (catch ValueError) and
treat invalid/non-numeric/out-of-range ports as "no port" (i.e., behave as if
port is absent); when building the origin from parsed.hostname, wrap IPv6
addresses in brackets (e.g., if hostname contains ':' and is not already
bracketed) so non-loopback IPv6 produces "https://[2001:db8::1]"; update the
logic around the port/hostname checks that set portless_origin to use the safe
parsed.port handling and the bracketed hostname, and still call
is_loopback(parsed.hostname) as before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f6dc0e96-61dd-40e4-b20e-40c4d185ece9

📥 Commits

Reviewing files that changed from the base of the PR and between 7d5cdd4 and 01b316f.

📒 Files selected for processing (2)
  • scripts/generate-openclaw-config.py
  • test/generate-openclaw-config.test.ts

Comment thread scripts/generate-openclaw-config.py Outdated
@senthilr-nv
senthilr-nv force-pushed the fix/cors-origin-reverse-proxy branch from 6583d8e to 10fe201 Compare May 4, 2026 20:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
scripts/generate-openclaw-config.py (1)

202-206: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard parsed.port access to avoid crashing on malformed CHAT_UI_URL.

parsed.port can throw ValueError (e.g., :abc, :99999), so the current condition can fail hard during config generation.

Suggested minimal fix
-    if parsed.scheme and parsed.hostname and parsed.port and not is_loopback(parsed.hostname):
+    try:
+        has_explicit_port = parsed.port is not None
+    except ValueError:
+        has_explicit_port = False
+
+    if parsed.scheme and parsed.hostname and has_explicit_port and not is_loopback(parsed.hostname):
         host_part = f"[{parsed.hostname}]" if ":" in parsed.hostname else parsed.hostname
         portless_origin = f"{parsed.scheme}://{host_part}"
     else:
         portless_origin = None
#!/bin/bash
set -euo pipefail

echo "1) Confirm direct parsed.port usage in current code:"
rg -n --type=py 'parsed\.port' scripts/generate-openclaw-config.py -C2

echo
echo "2) Reproduce stdlib behavior for malformed ports:"
python3 - <<'PY'
from urllib.parse import urlparse
samples = [
    "https://example.com:abc",
    "https://example.com:99999",
    "https://example.com:18789",
]
for u in samples:
    p = urlparse(u)
    try:
        port = p.port
    except ValueError as e:
        port = f"ValueError: {e}"
    print(f"{u} -> {port}")
PY
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/generate-openclaw-config.py` around lines 202 - 206, The condition
directly accesses parsed.port which can raise ValueError for malformed
CHAT_UI_URL; wrap the port access in a safe try/except (or pre-validate) and use
the resulting port variable in the existing logic: e.g., try to get port =
parsed.port except ValueError: port = None, then change the if to check
parsed.scheme and parsed.hostname and port and not is_loopback(parsed.hostname),
keeping the host_part and portless_origin assignment using parsed.hostname as
before (references: parsed.port, parsed.hostname, host_part, portless_origin,
is_loopback).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@scripts/generate-openclaw-config.py`:
- Around line 202-206: The condition directly accesses parsed.port which can
raise ValueError for malformed CHAT_UI_URL; wrap the port access in a safe
try/except (or pre-validate) and use the resulting port variable in the existing
logic: e.g., try to get port = parsed.port except ValueError: port = None, then
change the if to check parsed.scheme and parsed.hostname and port and not
is_loopback(parsed.hostname), keeping the host_part and portless_origin
assignment using parsed.hostname as before (references: parsed.port,
parsed.hostname, host_part, portless_origin, is_loopback).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9ef051b9-d0fe-4404-80eb-67ccb086fd65

📥 Commits

Reviewing files that changed from the base of the PR and between 6583d8e and 10fe201.

📒 Files selected for processing (2)
  • scripts/generate-openclaw-config.py
  • test/generate-openclaw-config.test.ts

@senthilr-nv senthilr-nv self-assigned this May 4, 2026
@senthilr-nv
senthilr-nv force-pushed the fix/cors-origin-reverse-proxy branch 2 times, most recently from 6ce3c7d to 70954ac Compare May 4, 2026 20:24
…roxy access

When CHAT_UI_URL is set to an HTTPS URL without an explicit port (as
documented for Brev deployments), onboard injects the internal dashboard
port (e.g. :18789) into the origin.  Reverse proxies (Brev Cloudflare
Tunnel, nginx, Caddy) serve on standard :443, so the browser origin
doesn't carry the port — causing "origin not allowed" on the dashboard.

Add the portless origin alongside the port-annotated one in
allowedOrigins for non-loopback URLs.  This is safe (same host) and
covers both direct and proxied access paths.

Prior art: PR NVIDIA#812 and PR NVIDIA#2440 fixed the CHAT_UI_URL plumbing but
the port-override gap remained.

Fixes NVIDIA#3000
Ref: NVIDIA#795, NVIDIA#20

Signed-off-by: Senthil Ravichandran <senthilr@nvidia.com>
@senthilr-nv
senthilr-nv force-pushed the fix/cors-origin-reverse-proxy branch from 70954ac to 76832c6 Compare May 4, 2026 20:30
@senthilr-nv
senthilr-nv requested a review from jyaunches May 4, 2026 20:34
@senthilr-nv senthilr-nv assigned ericksoa and unassigned ericksoa May 4, 2026
@senthilr-nv
senthilr-nv requested a review from ericksoa May 4, 2026 20:34

@ericksoa ericksoa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the current head. This is a narrow and correct fix for the reverse-proxy origin mismatch: it adds only the same non-loopback host without the explicit internal port, preserves loopback behavior, handles malformed ports and IPv6 safely, and has focused config-generation coverage. The current WSL failure is in Ubuntu apt metadata download before repo tests run, so it is unrelated to this PR.

@ericksoa
ericksoa merged commit 42e0f62 into NVIDIA:main May 4, 2026
9 of 10 checks passed
@wscurran wscurran added area: cli Command line interface, flags, terminal UX, or output bug-fix PR fixes a bug or regression and removed NemoClaw CLI labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli Command line interface, flags, terminal UX, or output bug-fix PR fixes a bug or regression platform: brev Affects Brev hosted development environments

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(onboard): CORS origin mismatch when CHAT_UI_URL is behind a reverse proxy

3 participants