Skip to content

feat: auto-allow remote gateway hostname in WS proxy#20

Open
alexmercier25 wants to merge 1 commit intodaggerhashimoto:masterfrom
alexmercier25:fix/remote-gateway-support
Open

feat: auto-allow remote gateway hostname in WS proxy#20
alexmercier25 wants to merge 1 commit intodaggerhashimoto:masterfrom
alexmercier25:fix/remote-gateway-support

Conversation

@alexmercier25
Copy link

@alexmercier25 alexmercier25 commented Feb 25, 2026

Fixes issue #19

Problem

When Nerve runs on a different machine than the OpenClaw gateway (e.g. Nerve on Mac, gateway on Raspberry Pi), the WS proxy rejects the connection with "target not allowed" because WS_ALLOWED_HOSTS only includes localhost, 127.0.0.1, and ::1.

Changes

server/lib/config.ts

• Extract hostname from GATEWAY_URL and auto-add it to WS_ALLOWED_HOSTS
• No change for localhost setups (no regression)

scripts/setup.ts

• When user enters a non-localhost gateway URL, auto-write WS_ALLOWED_HOSTS to .env
• Print warning about gateway.controlUi.allowedOrigins config needed on the gateway host

Testing

• Tested with Nerve on macOS connecting to OpenClaw gateway on Raspberry Pi (LAN)
• WS proxy connects without manual WS_ALLOWED_HOSTS config
• Localhost gateway setups unaffected

Summary by CodeRabbit

  • New Features
    • Automatic configuration of WebSocket connections based on gateway hostname during setup
    • Added advisory messaging when remote gateways are configured, indicating workspace and memory panels remain locally accessible only
    • Improved error handling to gracefully manage invalid gateway URLs during configuration

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

📝 Walkthrough

Walkthrough

The changes implement automatic augmentation of WS_ALLOWED_HOSTS with the gateway hostname. Setup script additions parse the gateway URL across three configuration codepaths, verify the host is non-loopback, and append the hostname to the allowlist. Config validation extracts the gateway hostname and adds a warning for remote gateways. URL parsing errors are handled gracefully with try-catch blocks.

Changes

Cohort / File(s) Summary
Setup Script Gateway Augmentation
scripts/setup.ts
Adds gateway hostname parsing and WS_ALLOWED_HOSTS augmentation logic in three codepaths: interactive collection post-gateway testing, defaults initialization, and non-defaults flow. Checks for non-loopback hosts and appends hostname if not already present; includes try-catch error handling and user guidance logging.
Config Validation Gateway Support
server/lib/config.ts
Extracts gateway hostname from Gateway URL to augment WS_ALLOWED_HOSTS set. Adds startup validation warning when a remote (non-local) gateway is detected, indicating workspace/memory panels will operate as local-only.

Possibly Related Issues

Poem

🐰 A gateway's name, we gently parse,
To WS's list, we add with class,
Non-loopback hosts get their place to shine,
While remote whispers give us a sign,
Configuration flows, now refined! ✨

Estimated Code Review Effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: automatic allowance of remote gateway hostname in the WebSocket proxy.
Description check ✅ Passed The description covers all key template sections including What (problem and solution), Why (issue #19 reference), and How (implementation details). The checklist is incomplete but the core description is comprehensive.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
server/lib/config.ts (1)

89-93: Loopback hostnames are added redundantly to the Set.

When GATEWAY_URL is a loopback address (e.g. http://localhost:3000), gatewayHostname will be 'localhost', which is already present in the Set. This is harmless due to Set deduplication, but it diverges from the explicit !isLoopback(gwHostname) guard used in scripts/setup.ts. Consider mirroring the same guard here for consistency.

♻️ Proposed fix
+const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1']);
+
 /** WebSocket proxy allowed hostnames (extend via WS_ALLOWED_HOSTS env var, comma-separated) */
 export const WS_ALLOWED_HOSTS = new Set([
   'localhost', '127.0.0.1', '::1',
-  ...(gatewayHostname ? [gatewayHostname] : []),
+  ...(gatewayHostname && !LOOPBACK_HOSTS.has(gatewayHostname) ? [gatewayHostname] : []),
   ...(process.env.WS_ALLOWED_HOSTS?.split(',').map(h => h.trim()).filter(Boolean) ?? []),
 ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/lib/config.ts` around lines 89 - 93, The WS_ALLOWED_HOSTS set
currently spreads gatewayHostname unconditionally which can duplicate loopback
entries; change the spread to only include gatewayHostname when it exists and is
not a loopback by checking isLoopback(gatewayHostname) (mirroring
scripts/setup.ts). In other words, guard the ...(gatewayHostname ?
[gatewayHostname] : []) spread with a !isLoopback(gatewayHostname) check so only
non-loopback gateway hostnames are added to WS_ALLOWED_HOSTS; keep the other
env-derived hosts unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/setup.ts`:
- Around line 365-378: The augmentation that adds the gateway hostname
(gwHostname) to config.WS_ALLOWED_HOSTS is being clobbered by the tailscale and
network branches which set config.WS_ALLOWED_HOSTS = tsIp / ip; fix by either
(A) moving the "Auto-add WS_ALLOWED_HOSTS for remote gateways" block so it runs
after the entire access-mode if/else-if/else block, or (B) changing the
tailscale/network branches to compose instead of overwrite (e.g. append tsIp/ip
to existing config.WS_ALLOWED_HOSTS if present), ensuring the logic in the try
block that computes gwHostname and updates config.WS_ALLOWED_HOSTS runs last and
preserves both entries.

---

Nitpick comments:
In `@server/lib/config.ts`:
- Around line 89-93: The WS_ALLOWED_HOSTS set currently spreads gatewayHostname
unconditionally which can duplicate loopback entries; change the spread to only
include gatewayHostname when it exists and is not a loopback by checking
isLoopback(gatewayHostname) (mirroring scripts/setup.ts). In other words, guard
the ...(gatewayHostname ? [gatewayHostname] : []) spread with a
!isLoopback(gatewayHostname) check so only non-loopback gateway hostnames are
added to WS_ALLOWED_HOSTS; keep the other env-derived hosts unchanged.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 79e14b1 and 1016dc8.

📒 Files selected for processing (2)
  • scripts/setup.ts
  • server/lib/config.ts

Comment on lines +365 to +378
// Auto-add WS_ALLOWED_HOSTS for remote gateways
try {
const gwHostname = new URL(config.GATEWAY_URL!).hostname;
if (!isLoopback(gwHostname)) {
config.WS_ALLOWED_HOSTS = config.WS_ALLOWED_HOSTS
? (config.WS_ALLOWED_HOSTS.includes(gwHostname) ? config.WS_ALLOWED_HOSTS : `${config.WS_ALLOWED_HOSTS},${gwHostname}`)
: gwHostname;
success(`Added ${gwHostname} to WS_ALLOWED_HOSTS`);
console.log('');
warn('Remote gateway detected. On the gateway host, ensure Nerve\'s origin is in');
dim(' gateway.controlUi.allowedOrigins in your OpenClaw config (~/.openclaw/openclaw.json).');
console.log('');
}
} catch { /* invalid URL — already validated above */ }
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

WS_ALLOWED_HOSTS gateway entry is silently clobbered by the access mode section.

This is the core fix path for the PR, but it doesn't work for two common configurations.

The tailscale and network access mode branches (lines 479 and 502) unconditionally overwrite config.WS_ALLOWED_HOSTS with just the LAN/Tailscale IP:

// tailscale branch (line 479)
config.WS_ALLOWED_HOSTS = tsIp;           // clobbers gwHostname

// network branch (line 502)
config.WS_ALLOWED_HOSTS = ip;             // clobbers gwHostname

So any user running Nerve on a LAN machine pointing at a remote gateway — which is exactly the scenario this PR targets (Nerve on macOS → Pi gateway) — loses the gateway hostname from WS_ALLOWED_HOSTS in the written .env, and the WS proxy will still reject the connection.

The local and custom branches don't touch WS_ALLOWED_HOSTS, so those paths are unaffected.

Fix: Move the augmentation to after the access mode block, or compose rather than overwrite in the tailscale/network branches.

🐛 Proposed fix — move augmentation after access mode
-  // Auto-add WS_ALLOWED_HOSTS for remote gateways
-  try {
-    const gwHostname = new URL(config.GATEWAY_URL!).hostname;
-    if (!isLoopback(gwHostname)) {
-      config.WS_ALLOWED_HOSTS = config.WS_ALLOWED_HOSTS
-        ? (config.WS_ALLOWED_HOSTS.includes(gwHostname) ? config.WS_ALLOWED_HOSTS : `${config.WS_ALLOWED_HOSTS},${gwHostname}`)
-        : gwHostname;
-      success(`Added ${gwHostname} to WS_ALLOWED_HOSTS`);
-      console.log('');
-      warn('Remote gateway detected. On the gateway host, ensure Nerve\'s origin is in');
-      dim('  gateway.controlUi.allowedOrigins in your OpenClaw config (~/.openclaw/openclaw.json).');
-      console.log('');
-    }
-  } catch { /* invalid URL — already validated above */ }
-
   // ── 2/5: Agent Identity ──────────────────────────────────────────

Add the augmentation block after the access mode } closing brace at the end of the if/else if/else block (after line 580):

  // Auto-add WS_ALLOWED_HOSTS for remote gateways (runs after access mode sets the base value)
  try {
    const gwHostname = new URL(config.GATEWAY_URL!).hostname;
    if (!isLoopback(gwHostname)) {
      config.WS_ALLOWED_HOSTS = config.WS_ALLOWED_HOSTS
        ? (config.WS_ALLOWED_HOSTS.includes(gwHostname) ? config.WS_ALLOWED_HOSTS : `${config.WS_ALLOWED_HOSTS},${gwHostname}`)
        : gwHostname;
      success(`Added ${gwHostname} to WS_ALLOWED_HOSTS`);
      console.log('');
      warn('Remote gateway detected. On the gateway host, ensure Nerve\'s origin is in');
      dim('  gateway.controlUi.allowedOrigins in your OpenClaw config (~/.openclaw/openclaw.json).');
      console.log('');
    }
  } catch { /* invalid URL — already validated above */ }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/setup.ts` around lines 365 - 378, The augmentation that adds the
gateway hostname (gwHostname) to config.WS_ALLOWED_HOSTS is being clobbered by
the tailscale and network branches which set config.WS_ALLOWED_HOSTS = tsIp /
ip; fix by either (A) moving the "Auto-add WS_ALLOWED_HOSTS for remote gateways"
block so it runs after the entire access-mode if/else-if/else block, or (B)
changing the tailscale/network branches to compose instead of overwrite (e.g.
append tsIp/ip to existing config.WS_ALLOWED_HOSTS if present), ensuring the
logic in the try block that computes gwHostname and updates
config.WS_ALLOWED_HOSTS runs last and preserves both entries.

@daggerhashimoto
Copy link
Owner

Thanks for tackling this, remote gateway support is a real need.
I’m requesting changes before merge so we land this cleanly and with correct scope.

Blocking issues

  1. Scope/closure mismatch
  1. WS_ALLOWED_HOSTS can be clobbered in setup flow
  • In scripts/setup.ts, the remote gateway hostname is added early, but later access-mode branches (tailscale / network) assign config.WS_ALLOWED_HOSTS = ... and can overwrite it.
  • Please either:
  • move remote-host augmentation to the end of the access-mode flow, or
  • compose+dedupe instead of overwrite in those branches.
  • Expected result: when applicable, both values survive (e.g. gateway host + local access IP).
  1. Loopback normalization consistency
  • server/lib/config.ts currently appends parsed gatewayHostname directly.
  • Please guard with loopback detection (consistent with setup flow), including IPv6 loopback forms.
  1. Docs update for remote mode behavior
  • Add a short note in installer docs:
  • remote gateway host is auto-allowed for WS proxy,
  • gateway.controlUi.allowedOrigins must be configured on the gateway host,
  • workspace/memory panels remain local-only for now.
  1. Minimal tests
  • Add tests covering:
  • non-loopback GATEWAY_URL host gets into WS_ALLOWED_HOSTS,
  • loopback host is not treated as remote,
  • setup flow doesn’t lose remote host in network/tailscale paths.

Once these are in, this should be mergeable. 🙏

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants