Skip to content

fix: auto-disable device auth for non-loopback URLs (#2341)#2449

Merged
ericksoa merged 22 commits intomainfrom
issue-2341-brev-launchable-pairing-required
Apr 27, 2026
Merged

fix: auto-disable device auth for non-loopback URLs (#2341)#2449
ericksoa merged 22 commits intomainfrom
issue-2341-brev-launchable-pairing-required

Conversation

@jyaunches
Copy link
Copy Markdown
Contributor

@jyaunches jyaunches commented Apr 24, 2026

Summary

Brev Launchable users see "pairing required" in the OpenClaw Web UI after deployment. They have only web access (no terminal), so they cannot complete device pairing.

Changes

Phase 1: Extract inline Python to scripts/generate-openclaw-config.py

  • Replaced the 68-line python3 -c inline block and 7-line token-clearing block in Dockerfile with a proper, testable Python script
  • 14 functional tests covering all config derivation paths
  • Updated C-2 security regression tests for script-based scanning

Phase 2: Auto-disable device auth for non-loopback URLs

  • When CHAT_UI_URL is non-loopback (e.g., Brev Launchable), automatically set dangerouslyDisableDeviceAuth: true
  • Added shouldDisableDeviceAuth to DashboardDeliveryChain interface
  • 7 additional tests for non-loopback detection + loopback regression

Test Results

  • 56 tests passing across 3 test files
  • 9/10 validation scenarios passed (1 skipped: Docker build+inspect, Docker daemon not running locally)

Closes #2341

Summary by CodeRabbit

  • New Features

    • Device authentication is now automatically disabled when the chat UI URL is non-loopback, improving security for remote deployments.
  • Improvements

    • Configuration generation is now more robust and centralized, with enhanced documentation for authentication-related settings.
  • Tests

    • Added comprehensive test coverage for configuration generation and security validation.

Phase 3 contained only comment/docstring updates for the Dockerfile,
nemoclaw-start.sh, and generate-openclaw-config.py. These are trivially
small changes that belong alongside the code changes in Phases 1 and 2.

Removing Phase 3 as a standalone phase eliminates unnecessary ceremony
while keeping all documentation tasks intact.
Covers 19 tests across 2 phases:
- Phase 1: 14 functional tests for generate-openclaw-config.py
  (config generation, env var handling, token clearing, permissions)
- Phase 2: 13 tests for auto-disable device auth + is_loopback parity
  (dashboard-contract.ts + Python script non-loopback detection)
9 BDD scenarios (6 happy, 3 sad):
- Phase 1: Config script output, Dockerfile structure, C-2 tests,
  functional tests, clear-token, onboard regression
- Phase 2: Brev URL auto-disable (core fix), loopback regression,
  dashboard-contract shouldDisableDeviceAuth
Scenario 2.3 builds a lightweight Dockerfile that mirrors the real
ARG→ENV→Python pipeline, then extracts openclaw.json and asserts
dangerouslyDisableDeviceAuth=true for a Brev URL. Also verifies
loopback URL still produces false. Skippable when Docker is unavailable.

This closes the gap between unit testing the Python script in isolation
and validating the full Dockerfile wiring that a real Brev Launchable
build follows.
10 BDD scenarios approved (7 happy, 3 sad).
Includes Docker build+inspect scenario (2.3) for full Dockerfile pipeline.
Brev org: Nemoclaw CI/CD
Design review found no issues:
- is_loopback() mirrors isLoopbackHostname() from url-utils.ts
- build_config(env) dict parameter follows testability patterns
- scripts/ already has Python files, naming fits
- ARG→ENV contract preserved, patchStagedDockerfile() untouched
- shouldDisableDeviceAuth derived from existing hasNonLoopbackUrl
…ntrolUi

The actual openclaw.json structure nests dangerouslyDisableDeviceAuth and
allowInsecureAuth under gateway.controlUi, not gateway.auth. The auth
sub-object only contains token. Fixed in tests.md, validation.md, and
spec.md assertions.

Verified against Dockerfile lines 326-329:
  'controlUi': {
      'allowInsecureAuth': allow_insecure,
      'dangerouslyDisableDeviceAuth': disable_device_auth,
      'allowedOrigins': origins,
  }
14 functional tests covering:
- Valid JSON generation with minimal env vars
- dangerouslyDisableDeviceAuth derivation (loopback, env var)
- allowInsecureAuth derivation (http vs https)
- allowedOrigins (loopback-only vs dual-origin)
- Messaging channels base64 parsing
- Web search enable/disable
- Agent timeout propagation
- Gateway auth token empty string
- File permissions 0600
- --clear-token flag

All 14 tests fail (Red phase) — script does not exist yet.
…g.py

Replace the 68-line python3 -c inline block and 7-line token-clearing
block in Dockerfile with a proper Python script:
- COPY scripts/generate-openclaw-config.py to image
- RUN python3 /usr/local/lib/nemoclaw/generate-openclaw-config.py
- RUN python3 ... --clear-token (after openclaw doctor)

The script has build_config(env) as a pure testable function.
Zero behavior change — generated config is structurally identical.

Updated C-2 security tests to scan the script file instead of
inline Dockerfile Python blocks.

14 new functional tests + 19 updated C-2 tests = 33 passing.
…e auth

7 new tests for non-loopback auto-disable:
- Brev Launchable URL → dangerouslyDisableDeviceAuth: true
- Any non-loopback → true
- 127.0.0.1, localhost, [::1] → false (regression)
- Explicit env var override on loopback → true
- URL trumps env var for non-loopback → true

3 tests fail (Red phase): auto-disable not yet implemented.
When CHAT_UI_URL is non-loopback (e.g., Brev Launchable), automatically
set dangerouslyDisableDeviceAuth=true in generated openclaw.json. Terminal-
based pairing is impossible when the user only has web access.

Python script: is_loopback() mirrors isLoopbackHostname() from url-utils.ts.
The explicit NEMOCLAW_DISABLE_DEVICE_AUTH=1 env var still works but cannot
re-enable device auth for non-loopback URLs (security default).

dashboard-contract.ts: added shouldDisableDeviceAuth to DashboardDeliveryChain
interface and buildChain() output. Derived from hasNonLoopbackUrl || isWsl.

Updated Dockerfile and nemoclaw-start.sh comments to document auto-derivation.

56 tests passing across 3 test files.
Validated:
- 1.1: Config script generates valid output ✓
- 1.2: Dockerfile uses COPY+RUN ✓
- 1.3: C-2 security regression tests pass ✓
- 1.4: 21 functional tests pass (≥10 required) ✓
- 1.5: --clear-token clears only token ✓
- 1.6: patchStagedDockerfile() unchanged ✓
- 2.1: Brev URL → dangerouslyDisableDeviceAuth=true ✓
- 2.2: Loopback URLs preserve device auth ✓
- 2.4: buildChain shouldDisableDeviceAuth ✓

Skipped:
- 2.3: Docker build+inspect (daemon not running, runs in CI)
@copy-pr-bot
Copy link
Copy Markdown

copy-pr-bot Bot commented Apr 24, 2026

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

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

This PR refactors the openclaw.json configuration generation by extracting inline Dockerfile Python logic into a dedicated external script. The new scripts/generate-openclaw-config.py validates environment variables, computes configuration including device authentication settings based on URL loopback status, and writes the config to disk. The dashboard contract gains a shouldDisableDeviceAuth property, and comprehensive tests validate the new behavior across loopback and non-loopback deployment scenarios.

Changes

Cohort / File(s) Summary
Config Generation Script
scripts/generate-openclaw-config.py
New Python CLI that generates openclaw.json from environment variables. Includes validation for NEMOCLAW_AGENT_TIMEOUT, base64 decoding of messaging channels, channel policy building per type, URL normalization, loopback detection, and conditional device-auth disabling. Writes config with 0600 permissions.
Build Configuration
Dockerfile
Delegates config generation to the external generate-openclaw-config.py script instead of inline python3 -c code. Removes 71 lines of embedded logic. Updates documentation for NEMOCLAW_DISABLE_DEVICE_AUTH to reflect force-disable semantics and auto-disable for non-loopback URLs.
Dashboard Contract
src/lib/dashboard-contract.ts, src/lib/dashboard-contract.test.ts
Adds shouldDisableDeviceAuth: boolean property to DashboardDeliveryChain interface. Computes the value as true for non-loopback URLs or WSL environments. Tests validate behavior across loopback (127.0.0.1, localhost, [::1]) vs. non-loopback origins.
Test Suites
test/generate-openclaw-config.test.ts, test/security-c2-dockerfile-injection.test.ts
New comprehensive test suite for Python config generator (~231 lines) covering device-auth disabling, allowed origins, base64 parsing, numeric validation, file permissions, and default fallbacks. Security regression tests refactored to validate the external script instead of Dockerfile inline logic, with tightened assertions on auth hardening.
Script Documentation
scripts/nemoclaw-start.sh
Minor documentation updates clarifying NEMOCLAW_DISABLE_DEVICE_AUTH behavior and noting auto-disable for non-loopback CHAT_UI_URL values.

Sequence Diagram

sequenceDiagram
    participant Env as Environment Variables
    participant Script as generate-openclaw-config.py
    participant Loopback as Loopback Checker
    participant JSON as openclaw.json
    
    Env->>Script: Pass build inputs (CHAT_UI_URL, NEMOCLAW_DISABLE_DEVICE_AUTH, etc.)
    Script->>Script: Validate NEMOCLAW_AGENT_TIMEOUT as positive integer
    Script->>Script: Parse comma-separated inputs & base64-decode channels
    Script->>Script: Build channel policies per type (discord/telegram/slack)
    Script->>Script: Normalize CHAT_UI_URL to absolute URL
    Script->>Loopback: Check if URL is loopback
    Loopback-->>Script: Return loopback status
    Script->>Script: Compute shouldDisableDeviceAuth based on loopback & env override
    Script->>Script: Set allowedOrigins and allowInsecureAuth based on scheme/loopback
    Script->>Script: Assemble models/providers and agent defaults
    Script->>Script: Conditionally add web-search tool if enabled
    Script->>JSON: Write config with indentation & 0600 permissions
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Hops with glee, a script so sleek,
No more Python in the Dockerfile's creak!
Config flows clean from env to JSON bright,
Device auth bows to loopback's might—
Launchable users now chat without a care! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: auto-disable device auth for non-loopback URLs (#2341)' clearly and concisely summarizes the main change—automatically disabling device authentication for non-loopback URLs to fix the Brev Launchable deployment issue.
Linked Issues check ✅ Passed The PR fully addresses the requirements in #2341 by detecting non-loopback CHAT_UI_URL values and auto-disabling device authentication via dangerouslyDisableDeviceAuth, enabling browser-only users to access the chat UI without terminal-based pairing.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing and testing the auto-disable device auth feature and refactoring inline Python config generation into a separate script—no unrelated changes are present.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2341-brev-launchable-pairing-required

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

The specs/ directory is in .gitignore and should not be committed.
These files were force-added during the vd_workflow review phase
and should remain local-only.
4 tests scanned Dockerfile for inline Python patterns that were
replaced by scripts/generate-openclaw-config.py in this PR:
- 'auth': {'token': ''} → check script source for empty token
- cfg.setdefault(...)['token'] = '' → check --clear-token flag
- ['token'] = '' ordering → check --clear-token after doctor
- Post-doctor clearing assertion → check --clear-token invocation
@jyaunches jyaunches marked this pull request as ready for review April 25, 2026 01:40
Copy link
Copy Markdown
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/security-c2-dockerfile-injection.test.ts (1)

182-213: ⚠️ Potential issue | 🟡 Minor

The ENV-order checks still key off the deleted python3 -c path.

These blocks only stop when they see RUN ... python3 -c, but the Dockerfile now uses generate-openclaw-config.py. With no match, they fall through to the final expect(...).toBeTruthy() and stop verifying that the ENV promotion actually happens before config generation.

Suggested fix
-      if (/^\s*RUN\b.*python3\s+-c\b/.test(line)) {
+      if (/^\s*RUN\b.*python3\s+\/usr\/local\/lib\/nemoclaw\/generate-openclaw-config\.py\b/.test(line)) {
         expect(chatUiUrlPromoted).toBeTruthy();
         return; // Found the RUN layer and verified — done
       }

Apply the same sentinel update in the CHAT_UI_URL, NEMOCLAW_MODEL, and NEMOCLAW_DISABLE_DEVICE_AUTH ordering checks.

Also applies to: 228-259, 317-343

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/security-c2-dockerfile-injection.test.ts` around lines 182 - 213, The
tests currently detect the sentinel RUN by matching /^\s*RUN\b.*python3\s+-c\b/,
which no longer appears in the Dockerfile; update that regex to detect the new
config-generation command (e.g. match either generate-openclaw-config.py or the
python invocation that runs it), for example replace the sentinel test with
/^\s*RUN\b.*(?:python3\b.*generate-openclaw-config\.py|generate-openclaw-config\.py)\b/;
apply this same change in all three ENV-order checks for CHAT_UI_URL,
NEMOCLAW_MODEL, and NEMOCLAW_DISABLE_DEVICE_AUTH in the test file so the loop
correctly verifies ENV promotion occurs before the config-generation RUN layer.
🤖 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/generate-openclaw-config.py`:
- Around line 70-72: In build_config(), the code currently uses env[...] for
CHAT_UI_URL, NEMOCLAW_PROXY_HOST and NEMOCLAW_PROXY_PORT which raises KeyError
instead of using the documented defaults; change those lookups to use env.get
with the documented default values (e.g., env.get("CHAT_UI_URL", "<default>"),
env.get("NEMOCLAW_PROXY_HOST", "<default>"), env.get("NEMOCLAW_PROXY_PORT",
"<default>")) and construct proxy_url and chat_ui_url from those safe values
(leave NEMOCLAW_MODEL as-is if it must be required); update the proxy_url
assignment and chat_ui_url/model usage in build_config() accordingly so the
script falls back to defaults when the env var is missing.
- Around line 137-154: The code misparses schemeless CHAT_UI_URLs (e.g.,
"remote-host:18789") because urlparse treats the host as a scheme; before
calling urlparse(chat_ui_url) normalize by ensuring chat_ui_url has an explicit
scheme (prepend "http://" only when chat_ui_url does not already start with a
scheme like "http://" or "https://"), then re-run urlparse into parsed and
compute chat_origin, origins, _is_remote, disable_device_auth, and
allow_insecure from that parsed result so schemeless inputs are handled the same
way as in src/lib/dashboard-contract.ts.

---

Outside diff comments:
In `@test/security-c2-dockerfile-injection.test.ts`:
- Around line 182-213: The tests currently detect the sentinel RUN by matching
/^\s*RUN\b.*python3\s+-c\b/, which no longer appears in the Dockerfile; update
that regex to detect the new config-generation command (e.g. match either
generate-openclaw-config.py or the python invocation that runs it), for example
replace the sentinel test with
/^\s*RUN\b.*(?:python3\b.*generate-openclaw-config\.py|generate-openclaw-config\.py)\b/;
apply this same change in all three ENV-order checks for CHAT_UI_URL,
NEMOCLAW_MODEL, and NEMOCLAW_DISABLE_DEVICE_AUTH in the test file so the loop
correctly verifies ENV promotion occurs before the config-generation RUN layer.
🪄 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: 429542e6-8c77-40cf-9038-43fa63df4b3e

📥 Commits

Reviewing files that changed from the base of the PR and between 6966f4b and 68aed03.

📒 Files selected for processing (8)
  • Dockerfile
  • scripts/generate-openclaw-config.py
  • scripts/nemoclaw-start.sh
  • src/lib/dashboard-contract.test.ts
  • src/lib/dashboard-contract.ts
  • test/generate-openclaw-config.test.ts
  • test/nemoclaw-start.test.ts
  • test/security-c2-dockerfile-injection.test.ts

Comment thread scripts/generate-openclaw-config.py Outdated
Comment thread scripts/generate-openclaw-config.py Outdated
1. Use env.get() with documented defaults for CHAT_UI_URL,
   NEMOCLAW_PROXY_HOST, NEMOCLAW_PROXY_PORT instead of env[] (KeyError).

2. Normalize schemeless URLs before urlparse() — 'remote-host:18789'
   was misparsed (hostname treated as scheme). Mirrors ensureScheme()
   from dashboard-contract.ts.
Copy link
Copy Markdown
Contributor

@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)
scripts/generate-openclaw-config.py (1)

129-131: Simplify allowlist lookup with dict.get.

Line 129 performs a redundant membership check followed by indexing. Use dict.get() for a single lookup and improved readability.

Suggested refactor
-        if ch in _allowed_ids and _allowed_ids[ch]:
+        allowed = _allowed_ids.get(ch)
+        if allowed:
             account["dmPolicy"] = "allowlist"
-            account["allowFrom"] = _allowed_ids[ch]
+            account["allowFrom"] = allowed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate-openclaw-config.py` around lines 129 - 131, Replace the
redundant membership check and indexing with a single dict.get lookup: retrieve
the value via _allowed_ids.get(ch) into a variable (e.g., val) and then check
that variable before setting account["dmPolicy"]="allowlist" and
account["allowFrom"]=val; update the block that currently uses "if ch in
_allowed_ids and _allowed_ids[ch]" to use this single lookup to improve
readability and avoid double dictionary access.
🤖 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/generate-openclaw-config.py`:
- Around line 226-233: The clear_token function currently opens
~/.openclaw/openclaw.json unconditionally which raises if the file doesn't
exist; make --clear-token idempotent by checking for the file or catching
FileNotFoundError inside clear_token, then either start from an empty dict or
ensure the directory exists and write a minimal config with {"gateway": {"auth":
{"token": ""}}} when the file is missing, otherwise load, modify
cfg.setdefault("gateway", {}).setdefault("auth", {})["token"] = "" and write it
back; reference the clear_token function and the path variable to locate the
change.

---

Nitpick comments:
In `@scripts/generate-openclaw-config.py`:
- Around line 129-131: Replace the redundant membership check and indexing with
a single dict.get lookup: retrieve the value via _allowed_ids.get(ch) into a
variable (e.g., val) and then check that variable before setting
account["dmPolicy"]="allowlist" and account["allowFrom"]=val; update the block
that currently uses "if ch in _allowed_ids and _allowed_ids[ch]" to use this
single lookup to improve readability and avoid double dictionary access.
🪄 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: ee4baa6b-dbb9-4b91-9392-886a62ddd719

📥 Commits

Reviewing files that changed from the base of the PR and between 68aed03 and 50d29bf.

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

Comment thread scripts/generate-openclaw-config.py Outdated
Per CodeRabbit review — early-return if openclaw.json doesn't exist
instead of raising FileNotFoundError.
Copy link
Copy Markdown
Contributor

@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

🤖 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/generate-openclaw-config.py`:
- Around line 70-75: The current use of env.get(..., default) for proxy_host,
proxy_port, and chat_ui_url doesn't treat empty strings as unset; update the
assignments for proxy_host, proxy_port, and chat_ui_url so they normalize
empty-string env values to the documented defaults (e.g., retrieve with
env.get("NEMOCLAW_PROXY_HOST") and if falsy/empty string then set to
"10.200.0.1", similarly for NEMOCLAW_PROXY_PORT -> "3128" and CHAT_UI_URL ->
"http://127.0.0.1:18789"), then recompute proxy_url from the normalized
proxy_host/proxy_port; reference the variables proxy_host, proxy_port,
proxy_url, chat_ui_url and env.get in your changes.
🪄 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: 0f22fa45-0916-4f5d-8e21-d58144541d74

📥 Commits

Reviewing files that changed from the base of the PR and between 50d29bf and 4dc3faf.

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

Comment thread scripts/generate-openclaw-config.py Outdated
- generate-openclaw-config.py: treat empty-string env vars as unset so
  the documented defaults still apply when callers pass an explicit
  empty value (NEMOCLAW_PROXY_HOST, NEMOCLAW_PROXY_PORT, CHAT_UI_URL)
- security-c2 tests: update ENV-promotion ordering checks to match the
  new generate-openclaw-config.py RUN layer so the ordering invariant
  is enforced again instead of silently falling through
- generate-openclaw-config tests: add regression coverage for the
  empty-string fallback behavior
@ericksoa
Copy link
Copy Markdown
Contributor

Pushed a follow-up commit (9f5d517) addressing the remaining CodeRabbit feedback:

  • scripts/generate-openclaw-config.py: Empty-string env vars (NEMOCLAW_PROXY_HOST, NEMOCLAW_PROXY_PORT, CHAT_UI_URL) now fall back to the documented defaults via env.get(...) or DEFAULT, matching how dashboard-contract.ts handles String(... || "").trim().
  • test/security-c2-dockerfile-injection.test.ts: The three ENV-promotion ordering checks (CHAT_UI_URL, NEMOCLAW_MODEL, NEMOCLAW_DISABLE_DEVICE_AUTH) now match the new RUN python3 /usr/local/lib/nemoclaw/generate-openclaw-config.py sentinel instead of the deleted python3 -c form, so the ordering invariant is enforced again instead of silently falling through.
  • test/generate-openclaw-config.test.ts: Added regression coverage for the empty-string fallback (CHAT_UI_URL, NEMOCLAW_PROXY_HOST, NEMOCLAW_PROXY_PORT).

Skipped the dict.get() refactor at line 129–131 — pure stylistic nitpick that doesn't change behavior.

Resolves conflicts in Dockerfile and test/nemoclaw-start.test.ts.

- Dockerfile config-generation block: kept the externalized
  scripts/generate-openclaw-config.py invocation (the PR's purpose)
  and dropped the inline python3 -c block from main.
- Dockerfile token step: dropped the PR's --clear-token step and took
  main's late-layer secrets.token_hex(32) injection (#2482 reverted
  gateway auth token externalization, so the token is again baked at
  build time).
- scripts/generate-openclaw-config.py: ported the inference_inputs
  parsing (#2441) and channel healthMonitor field from main; removed
  the now-obsolete --clear-token mode.
- test/nemoclaw-start.test.ts: took main's version, since the PR's
  token-externalization regression tests no longer match main's
  reverted design.
- test/generate-openclaw-config.test.ts: removed the --clear-token
  test cases.
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (2)
scripts/generate-openclaw-config.py (1)

71-75: Normalize whitespace-only env values before fallback defaults.

or handles "" but not " ". Trimming first avoids malformed proxy_url/CHAT_UI_URL derivations.

Proposed patch
-    proxy_host = env.get("NEMOCLAW_PROXY_HOST") or "10.200.0.1"
-    proxy_port = env.get("NEMOCLAW_PROXY_PORT") or "3128"
+    proxy_host = (env.get("NEMOCLAW_PROXY_HOST", "").strip() or "10.200.0.1")
+    proxy_port = (env.get("NEMOCLAW_PROXY_PORT", "").strip() or "3128")
     proxy_url = f"http://{proxy_host}:{proxy_port}"
     model = env["NEMOCLAW_MODEL"]
-    chat_ui_url = env.get("CHAT_UI_URL") or "http://127.0.0.1:18789"
+    chat_ui_url = (env.get("CHAT_UI_URL", "").strip() or "http://127.0.0.1:18789")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/generate-openclaw-config.py` around lines 71 - 75, Env vars for
proxy_host, proxy_port and chat_ui_url must be normalized to trim whitespace
before applying defaults: change the lookups for proxy_host, proxy_port and
chat_ui_url to use env.get(..., "").strip() and then fall back to the literal
defaults when the stripped result is empty; then build proxy_url from the
validated proxy_host/proxy_port values (keep model = env["NEMOCLAW_MODEL"]
unchanged). This ensures values like "   " don't produce malformed proxy_url or
CHAT_UI_URL.
test/generate-openclaw-config.test.ts (1)

154-199: Add explicit schemeless URL regression cases.

Given the recent parser-normalization fix, add tests for schemeless loopback and non-loopback inputs to prevent regressions.

Proposed test additions
 describe("generate-openclaw-config.py: non-loopback auto-disable device auth", () => {
+  it("treats schemeless localhost as loopback", () => {
+    const config = runConfigScript({ CHAT_UI_URL: "localhost:18789" });
+    expect(config.gateway.controlUi.dangerouslyDisableDeviceAuth).toBe(false);
+    expect(config.gateway.controlUi.allowedOrigins).toContain("http://localhost:18789");
+  });
+
+  it("treats schemeless remote host as non-loopback", () => {
+    const config = runConfigScript({ CHAT_UI_URL: "remote-host:18789" });
+    expect(config.gateway.controlUi.dangerouslyDisableDeviceAuth).toBe(true);
+    expect(config.gateway.controlUi.allowedOrigins).toContain("http://remote-host:18789");
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/generate-openclaw-config.test.ts` around lines 154 - 199, Tests are
missing schemeless-URL regression cases after the parser-normalization change;
add new it() cases in test/generate-openclaw-config.test.ts that call
runConfigScript with CHAT_UI_URL values that omit the scheme (e.g.
"nemoclaw0-xxx.brevlab.com:18789" for non-loopback and "127.0.0.1:18789" or
"localhost:18789" for loopback) and assert
gateway.controlUi.dangerouslyDisableDeviceAuth behaves the same as the
equivalent scheme-prefixed tests (true for non-loopback, false for loopback, and
honor NEMOCLAW_DISABLE_DEVICE_AUTH overrides); locate the block of existing
tests (describe "generate-openclaw-config.py: non-loopback auto-disable device
auth") and add the schemeless cases adjacent to the matching tests so future
normalization regressions are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/generate-openclaw-config.py`:
- Around line 71-75: Env vars for proxy_host, proxy_port and chat_ui_url must be
normalized to trim whitespace before applying defaults: change the lookups for
proxy_host, proxy_port and chat_ui_url to use env.get(..., "").strip() and then
fall back to the literal defaults when the stripped result is empty; then build
proxy_url from the validated proxy_host/proxy_port values (keep model =
env["NEMOCLAW_MODEL"] unchanged). This ensures values like "   " don't produce
malformed proxy_url or CHAT_UI_URL.

In `@test/generate-openclaw-config.test.ts`:
- Around line 154-199: Tests are missing schemeless-URL regression cases after
the parser-normalization change; add new it() cases in
test/generate-openclaw-config.test.ts that call runConfigScript with CHAT_UI_URL
values that omit the scheme (e.g. "nemoclaw0-xxx.brevlab.com:18789" for
non-loopback and "127.0.0.1:18789" or "localhost:18789" for loopback) and assert
gateway.controlUi.dangerouslyDisableDeviceAuth behaves the same as the
equivalent scheme-prefixed tests (true for non-loopback, false for loopback, and
honor NEMOCLAW_DISABLE_DEVICE_AUTH overrides); locate the block of existing
tests (describe "generate-openclaw-config.py: non-loopback auto-disable device
auth") and add the schemeless cases adjacent to the matching tests so future
normalization regressions are caught.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 8bbd49d6-e082-4d16-9b1f-31c9161cad46

📥 Commits

Reviewing files that changed from the base of the PR and between 9f5d517 and 5c88191.

📒 Files selected for processing (4)
  • Dockerfile
  • scripts/generate-openclaw-config.py
  • scripts/nemoclaw-start.sh
  • test/generate-openclaw-config.test.ts
✅ Files skipped from review due to trivial changes (1)
  • scripts/nemoclaw-start.sh

Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

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

Approving. Brev Launchable fix (auto-disable device auth for non-loopback CHAT_UI_URLs) is intact in scripts/generate-openclaw-config.py and src/lib/dashboard-contract.ts. CodeRabbit feedback addressed. Merge with main resolved against main's reverted token-externalization design (#2482).

@ericksoa ericksoa merged commit f5ee8a4 into main Apr 27, 2026
18 checks passed
ericksoa added a commit that referenced this pull request Apr 27, 2026
The Phase 1 refactor in #2449 (commit f5ee8a4) extracted the inline
Python heredoc from Dockerfile into scripts/generate-openclaw-config.py
and updated Dockerfile with `COPY scripts/generate-openclaw-config.py …`,
but did not update src/lib/sandbox-build-context.ts which curates the
optimized build context for sandbox image builds.

Result: every nightly E2E job (and any sandbox onboard) failed with
"COPY failed: file not found in build context: stat
scripts/generate-openclaw-config.py: file does not exist".

Add the file to stageOptimizedSandboxBuildContext() next to
nemoclaw-start.sh, and add a test assertion so the staging stays in sync.
ericksoa added a commit that referenced this pull request Apr 27, 2026
#2449 added scripts/generate-openclaw-config.py and a COPY for it in the
Dockerfile, but did not update stageOptimizedSandboxBuildContext to include
the new file. The optimized build context is missing the script, so every
sandbox image build that uses the optimized path fails at Dockerfile step
21/56 with `COPY failed: file not found in build context`. This breaks
all dispatched nightly-e2e jobs since 2026-04-26 22:21 EDT.

Pulling this fix into the #2478 PR rather than a standalone PR so the
recovery hardening test on this branch can actually run.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
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.

[NemoClaw][Brev Launchable] OpenClaw Web UI shows "pairing required" after Brev Launchable deployment — no way to complete pairing from browser

2 participants