fix(gateway): preserve per-device auth token + self-heal orphaned config keys - #155
Conversation
…fig keys Closes #149, #150. The gateway auth token gates LAN access to the agent's privileged tools (run_command / file_write / system_power). Two bugs shipped the public literal "clawbox" (documented in open-source history) as the live token: 1. gateway-pre-start.sh ran `set_if(auth, "token", "clawbox")` on EVERY gateway start, clobbering the strong per-device token the configure route generates — so the rotation never stuck. 2. clawbox-gateway.service launched with `--token clawbox`, overriding config at runtime and drifting from what the Control UI (which reads gateway.auth.token) used — the #150 source-of-truth mismatch. Fix — config is the single source of truth: - gateway-pre-start.sh preserves a strong token (configure-route random hex, a ${ENV} interpolation, or a SecretRef object with a known env/file/exec key) and only generates a fresh secrets.token_hex(32) when the value is missing or the weak legacy literal. - clawbox-gateway.service drops --token; the gateway resolves gateway.auth.token from openclaw.json (OPENCLAW_GATEWAY_TOKEN ?? configuredToken), the same value gateway-proxy.ts injects into the SPA. - install.sh / install-x64.sh seed a random token instead of the literal, only when missing/weak. The install-x64 JS predicate is kept in lockstep with the python one (rejects arrays + empty/keyless objects + empty ${}). Also self-heal a config-validation residue class (the agentRuntime incident): gateway-pre-start.sh strips an orphaned `agentRuntime` key from agents.defaults.models[*] — written by @openclaw/codex >= 2026.5.27 and left behind when the plugin is realigned to the pinned core, which fails strict validation and bricks the AI provider page until `doctor --fix`. Adds src/tests/unit/gateway-pre-start-token.test.ts, which extracts the real predicate from the shipped script and asserts preserve/rotate + python↔JS parity. Validated end-to-end on a Jetson: clawbox/empty-dict rotate to 64-hex, strong tokens preserved across restarts, Control UI authenticates against the config token with no --token flag.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughService ExecStart no longer embeds ChangesGateway token lifecycle: from hardcoded literal to per-device strong-token preservation
Sequence DiagramsequenceDiagram
participant SystemD as systemd
participant PreStart as gateway-pre-start.sh
participant Config as openclaw.json
participant Installer as installer scripts
participant Gateway as gateway process
SystemD->>PreStart: run pre-start script
PreStart->>Config: read gateway.auth.token
PreStart->>PreStart: is_strong_gateway_token(token)?
alt token is weak or missing
PreStart->>PreStart: generate new 32-byte hex token
PreStart->>Config: write new token
else token is strong
PreStart->>Config: leave token unchanged
end
Installer->>Config: seed token when installing (if missing/weak)
PreStart-->>SystemD: pre-start complete
SystemD->>Gateway: exec openclaw gateway (--allow-unconfigured --bind lan)
Gateway->>Config: read resolved token at runtime
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
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)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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 `@install-x64.sh`:
- Around line 355-360: The EXISTING_GW_TOKEN check in install-x64.sh (the block
that reads EXISTING_GW_TOKEN via as_user "$OPENCLAW_BIN" and may generate
GW_TOKEN) must skip rotating tokens that are literal interpolation placeholders
like ${VAR}; update the conditional that currently tests empty/"clawbox"/length
to also detect and preserve strings matching the ${...} pattern (e.g. use a bash
regex check for ^\$\{.+\}$) so that if EXISTING_GW_TOKEN is an interpolation
token you do not overwrite it with a new random GW_TOKEN before the later
Node.js check runs.
In `@install.sh`:
- Around line 978-989: The current predicate that checks EXISTING_GW_TOKEN only
treats empty, literal "clawbox", or length <32 as weak and will rotate
interpolation tokens like ${GATEWAY_TOKEN}; update the condition used where
EXISTING_GW_TOKEN is evaluated so it treats ${...} style tokens as valid/strong
(e.g. add a test for tokens matching the interpolation pattern like starting
with '${' and ending with '}' or a regex /^\$\{[^}]+\}$/) before deciding to
generate GW_TOKEN and call as_clawbox config set gateway.auth.token; keep the
existing GW_TOKEN generation and as_clawbox calls intact but skip them when
EXISTING_GW_TOKEN is an interpolation token.
In `@src/tests/unit/gateway-pre-start-token.test.ts`:
- Around line 82-87: Add tests covering SecretRef objects with "file" and "exec"
keys to match the predicate logic in classify (used by
scripts/gateway-pre-start.sh). Specifically, add test cases that call classify({
file: "some/path" }) and classify({ exec: "some-command" }) and assert they
return "strong" (mirroring the existing env test), so removal of accepted keys
"file" or "exec" will be caught; reference the classify function and the
SecretRef shape when adding these expectations.
🪄 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: ASSERTIVE
Plan: Pro
Run ID: c6040842-821e-4003-a3d4-77365de37670
📒 Files selected for processing (5)
config/clawbox-gateway.serviceinstall-x64.shinstall.shscripts/gateway-pre-start.shsrc/tests/unit/gateway-pre-start-token.test.ts
…t file/exec SecretRefs Address CodeRabbit review on #155: - install.sh / install-x64.sh: the bash seed predicate only treated empty / "clawbox" / <32-char as weak, so a `${ENV}` interpolation token (e.g. ${OPENCLAW_GATEWAY_TOKEN}, 16 chars) was rotated to a random value — clobbering an externally-managed token before the later checks run. Add a `^\$\{.+\}$` regex branch so interpolation tokens are preserved, matching is_strong_gateway_token in gateway-pre-start.sh and the JS predicate in install-x64.sh. - test: cover SecretRef objects with `file` and `exec` keys (not just `env`) so dropping an accepted key from the predicate is caught.
The EXISTING_GW_TOKEN read I added runs `openclaw config get
gateway.auth.token`, which exits non-zero on a fresh install (the key
doesn't exist yet). Under `set -euo pipefail` the bare assignment
propagated that non-zero status and aborted install.sh right after
setting gateway.auth.mode — bricking first-boot install. The e2e-install
harness caught it ("install.sh did not finish within 2400000ms").
Append `|| true` so the probe never aborts the installer; an empty
result correctly falls through to seeding a fresh per-device token.
Same fix in install-x64.sh. Reproduced the abort and verified the fix
under set -euo pipefail.
|
Actionable comments posted: 0 |
The chat fetched /setup-api/gateway/ws-config without cache:'no-store', and the route set no Cache-Control. When the per-device gateway token is regenerated (reseed, settings change, post-update), a cached ws-config response replays the OLD token on every reconnect, so the gateway rejects it with 'token mismatch' indefinitely — only a hard reload recovers. Add cache:'no-store' to the fetch and Cache-Control:no-store on the route so reconnects always pick up the current token. This matters for the #155 token-hardening rollout: open chat tabs would otherwise get stuck after the token changes on update.
Closes #149, closes #150.
The bug (security)
The gateway auth token gates LAN access to the agent's privileged MCP tools (
run_command,file_write,system_power). Two code paths shipped the public literalclawbox— documented in the open-source history (gateway-proxy.tsLEGACY_GATEWAY_TOKEN) — as the live token, so anyone on the same WiFi could connect to the gateway WebSocket and drive the agent, bypassing the wizard login:gateway-pre-start.shranset_if(auth, "token", "clawbox")on every gateway start — clobbering the strong per-device token the configure route already generates, so the rotation never stuck.clawbox-gateway.servicelaunched with--token clawbox, overriding config at runtime and drifting from the token the Control UI reads fromgateway.auth.token(the Control UI gateway bootstrap should use the same token source as the live gateway service #150 source-of-truth mismatch).Reported by @jamesachurchill.
The fix — config is the single source of truth
The gateway resolves the token as
OPENCLAW_GATEWAY_TOKEN ?? gateway.auth.token(verified in the installed core), so:gateway-pre-start.shpreserves a strong token — configure-route random hex, a${ENV}interpolation, or a SecretRef object with a knownenv/file/execkey — and only generates a freshsecrets.token_hex(32)when missing or the weak legacy literal.clawbox-gateway.servicedrops--token; the gateway resolvesgateway.auth.tokenfromopenclaw.json— the same valuegateway-proxy.tsinjects into the SPA. One source, no service↔UI drift.install.sh/install-x64.shseed a random token instead of the literal, only when missing/weak. The install-x64 JS predicate is kept byte-for-byte in lockstep with the python one (both reject arrays, empty/keyless objects, and empty${}).Bonus: config-residue self-heal
gateway-pre-start.shnow strips an orphanedagentRuntimekey fromagents.defaults.models[*]— written by@openclaw/codex >= 2026.5.27and left behind when the plugin is realigned to the pinned core, which fails strict config validation and bricks the AI provider page until a manualopenclaw doctor --fix. This makes affected devices self-heal on the next gateway start (the live customer incident from this week).Tests
New
src/tests/unit/gateway-pre-start-token.test.tsextracts the real predicate from the shipped script and exercises both the predicate and the rotation wiring via python3 (skips gracefully where python3 is absent), including python↔JS parity cases.Validated on a real Jetson
clawboxand empty-dict tokens rotate to 64-hex; strong tokens preserved across restarts (idempotent, no churn)--token; Control UI connects and authenticates against the config token ([ws] webchat connected client=openclaw-control-ui) — proves single-source-of-truthagentRuntimestripped, unrelated keys preserved; gateway[gateway] ready, no crash-loopScope notes
supportedReasoningEffortsversion-gate (other half of the residue finding) is deliberately deferred — it only matters if the OpenClaw pin drops below 2026.5.18 (currently 2026.5.22), and adding a calver parser to the boot-critical script isn't worth it for a non-occurring case.Test plan
bash -non all three shell scriptsSummary by CodeRabbit
New Features
Bug Fixes / Improvements
Tests