fix: auto-disable device auth for non-loopback URLs (#2341)#2449
fix: auto-disable device auth for non-loopback URLs (#2341)#2449
Conversation
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)
|
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. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR refactors the Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
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.
…hable-pairing-required
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
…hable-pairing-required
There was a problem hiding this comment.
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 | 🟡 MinorThe ENV-order checks still key off the deleted
python3 -cpath.These blocks only stop when they see
RUN ... python3 -c, but the Dockerfile now usesgenerate-openclaw-config.py. With no match, they fall through to the finalexpect(...).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, andNEMOCLAW_DISABLE_DEVICE_AUTHordering 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
📒 Files selected for processing (8)
Dockerfilescripts/generate-openclaw-config.pyscripts/nemoclaw-start.shsrc/lib/dashboard-contract.test.tssrc/lib/dashboard-contract.tstest/generate-openclaw-config.test.tstest/nemoclaw-start.test.tstest/security-c2-dockerfile-injection.test.ts
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.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/generate-openclaw-config.py (1)
129-131: Simplify allowlist lookup withdict.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
📒 Files selected for processing (1)
scripts/generate-openclaw-config.py
Per CodeRabbit review — early-return if openclaw.json doesn't exist instead of raising FileNotFoundError.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
scripts/generate-openclaw-config.py
- 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
|
Pushed a follow-up commit (9f5d517) addressing the remaining CodeRabbit feedback:
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.
There was a problem hiding this comment.
🧹 Nitpick comments (2)
scripts/generate-openclaw-config.py (1)
71-75: Normalize whitespace-only env values before fallback defaults.
orhandles""but not" ". Trimming first avoids malformedproxy_url/CHAT_UI_URLderivations.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
📒 Files selected for processing (4)
Dockerfilescripts/generate-openclaw-config.pyscripts/nemoclaw-start.shtest/generate-openclaw-config.test.ts
✅ Files skipped from review due to trivial changes (1)
- scripts/nemoclaw-start.sh
ericksoa
left a comment
There was a problem hiding this comment.
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).
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.
#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>
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.pypython3 -cinline block and 7-line token-clearing block in Dockerfile with a proper, testable Python scriptPhase 2: Auto-disable device auth for non-loopback URLs
CHAT_UI_URLis non-loopback (e.g., Brev Launchable), automatically setdangerouslyDisableDeviceAuth: trueshouldDisableDeviceAuthtoDashboardDeliveryChaininterfaceTest Results
Closes #2341
Summary by CodeRabbit
New Features
Improvements
Tests