fix: make openclaw.json immutable at runtime, move config to build time#588
Conversation
…lockdown - Bake both 'nvidia' and 'inference' providers into openclaw.json at image build time; remove runtime Python config-patching from buildSandboxConfigSyncScript (writes to locked root:root 444 file) - Use `openclaw models set` for runtime model selection (writes to writable agent config in .openclaw-data/) - Add identity/, devices/, canvas/, cron/ to .openclaw-data symlinks so the gateway can write device-auth.json at runtime - Remove dead `openclaw doctor --fix` and `openclaw plugins install` calls from nemoclaw-start.sh (already ran at build time, fail with EPERM at runtime) Caused-by: 2d3f84e (fix: lock gateway config via Landlock filesystem policy) Fixes #514
The # pragma: allowlist secret comments inside the multi-line python3 -c
string cause Python to treat everything after # as a comment, swallowing
the \ line continuation and closing braces. This results in:
SyntaxError: '{' was never closed
Reported by DanTup in PR #570.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe changes refactor OpenClaw configuration initialization from runtime to build-time, moving configuration logic from startup scripts and onboarding into the Dockerfile. Runtime scripts now expect Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 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 |
| 'api': 'openai-completions', \ | ||
| 'models': [{'id': model.split('/')[-1], 'name': model, 'reasoning': False, 'input': ['text'], 'cost': {'input': 0, 'output': 0, 'cacheRead': 0, 'cacheWrite': 0}, 'contextWindow': 131072, 'maxTokens': 4096}] \ | ||
| }}}, \ | ||
| 'agents': {'defaults': {'model': {'primary': f'inference/{model}'}}}, \ |
There was a problem hiding this comment.
this part is where I'm concerned. We need to have all the inference choices in BEFORE we configure the immutable file OR we have to have a way to update the config from outside the sandbox (this is more likely what we need)
There was a problem hiding this comment.
yes, the model inference choices have to be handled from the outside the sandbox via openshell with initial versions baked in before we configure immutability - going to add additional tests and affordances for that.
There was a problem hiding this comment.
Hi @ericksoa and @kjw3, since openclaw.json is now immutable at runtime in the sandbox, what is the recommended way to update config settings and override skill configurations if defaults are baked in at build time? openclaw config set commands can no longer update in sandbox. Clarifying this would help users safely manage OpenClaw config settings and skills under the new sandbox model.
There was a problem hiding this comment.
Hi @EltronAI we are looking at ways to implement this in a way that is consistent with the policy engine in openshell, specifically allowing more granular control of what can change. For example, there maybe configurations where the policy engine will forbid changes to skills unless certain guidelines are met, or forbid changes to system prompt, and others where those changes are 100% fine.
Some of these are design details we are still working out, and hope to support very soon.
Appreciate the feedback!
openclaw models set writes to openclaw.json, which is correctly locked (root:root 444 + Landlock read-only). Model routing is handled by the host-side gateway via openshell inference set (Step 5), not from inside the sandbox. The sync script should only write NemoClaw's own selection config to ~/.nemoclaw/config.json. Remove openclaw models set call, dead pythonLiteralJson helper, and unused getOpenClawPrimaryModel/DEFAULT_OLLAMA_MODEL imports.
|
Re-review summary: this looks good to merge. Review take:
Validation:
From my side, this is ready to merge once final approval is in place. Let it rip 🤙 |
|
Thanks for carrying this through and for fixing the remaining gap. Also want to explicitly credit @franknvda1 here: the core direction came from #570, and this PR cleanly finishes that work by removing the remaining sandbox-side Let it rip 🤙 |
|
@kjw3 I think these can be resolved/closed with this fix too: |
OpenClaw's Discord integration fails when it tries to write a user-provided bot token to openclaw.json, which is now immutable (root:root 444, Landlock read-only) after #588. OpenClaw already supports reading DISCORD_BOT_TOKEN and SLACK_BOT_TOKEN from environment variables, so we pass them through at sandbox creation time — the same pattern used for NVIDIA_API_KEY and TELEGRAM_BOT_TOKEN. Also disables channels.defaults.configWrites in the build-time config to prevent any channel plugin from attempting runtime writes to the immutable config file. Closes #599
* docs: add community feedback invitation for policy presets * docs: link baseline policy reference to the YAML file on GitHub * fix: pass Discord and Slack bot tokens via env vars into sandbox OpenClaw's Discord integration fails when it tries to write a user-provided bot token to openclaw.json, which is now immutable (root:root 444, Landlock read-only) after #588. OpenClaw already supports reading DISCORD_BOT_TOKEN and SLACK_BOT_TOKEN from environment variables, so we pass them through at sandbox creation time — the same pattern used for NVIDIA_API_KEY and TELEGRAM_BOT_TOKEN. Also disables channels.defaults.configWrites in the build-time config to prevent any channel plugin from attempting runtime writes to the immutable config file. Closes #599 * fix: add Discord endpoints to default sandbox policy Add discord.com, gateway.discord.gg, and cdn.discordapp.com to the baseline sandbox policy so Discord integration works without needing the preset applied separately.
…fixes NVIDIA#628) When users select Local Ollama during onboarding, the sandbox's openclaw.json still showed the default cloud model because the Dockerfile was built before the model was selected. Fix: reorder the onboarding flow so inference provider/model selection (Step 3) happens before sandbox creation (Step 4). The selected model is then patched into the Dockerfile's ARG NEMOCLAW_MODEL default before the image build, ensuring openclaw.json is baked with the correct model at build time. This preserves the immutable config design from PR NVIDIA#588 — openclaw.json is still root:root 444 and never modified at runtime. The fix operates entirely at build time. Changes: - Rename setupNim() → selectInference() (no sandbox dependency) - Add patchDockerfileModel() to rewrite ARG NEMOCLAW_MODEL - Defer NIM container start to after sandbox creation - Move registry.updateSandbox() to main onboard() flow - Add tests for Dockerfile patching
…ixes NVIDIA#606) openclaw.json is locked (root:root 444) at build time to prevent agent tampering (NVIDIA#514, NVIDIA#588). However, users legitimately need to modify config at runtime — e.g. running `openclaw onboard` to add a Discord bot token. The atomic write (tmp → copyfile → rename) in OpenClaw's config writer fails with EACCES against the immutable file. PR NVIDIA#601 addressed the env-var path (passing DISCORD_BOT_TOKEN into the sandbox), but the underlying issue remains: any `openclaw onboard` or `/config` write inside the sandbox hits the same EACCES error. Fix: at sandbox startup, copy the immutable openclaw.json to the writable state directory (~/.openclaw-data/) and set OPENCLAW_CONFIG_PATH to redirect all OpenClaw config reads/writes to the copy. The original immutable file stays intact as a read-only reference; the Landlock policy on /sandbox/.openclaw continues to protect it. Changes: - nemoclaw-start.sh: add prepare_writable_config() that copies the locked config to ~/.openclaw-data/openclaw.json and exports OPENCLAW_CONFIG_PATH; update print_dashboard_urls to respect the env var - e2e-test.sh: add test 11 verifying writable overlay works and immutable original stays untouched
…me (NVIDIA#588) * fix: resolve openclaw.json permissions conflict and scope Dockerfile lockdown - Bake both 'nvidia' and 'inference' providers into openclaw.json at image build time; remove runtime Python config-patching from buildSandboxConfigSyncScript (writes to locked root:root 444 file) - Use `openclaw models set` for runtime model selection (writes to writable agent config in .openclaw-data/) - Add identity/, devices/, canvas/, cron/ to .openclaw-data symlinks so the gateway can write device-auth.json at runtime - Remove dead `openclaw doctor --fix` and `openclaw plugins install` calls from nemoclaw-start.sh (already ran at build time, fail with EPERM at runtime) Caused-by: 2d3f84e (fix: lock gateway config via Landlock filesystem policy) Fixes NVIDIA#514 * fix: remove pragma comments that break inline Python in Dockerfile The # pragma: allowlist secret comments inside the multi-line python3 -c string cause Python to treat everything after # as a comment, swallowing the \ line continuation and closing braces. This results in: SyntaxError: '{' was never closed Reported by DanTup in PR NVIDIA#570. * fix: remove openclaw models set from sync script — config stays on host openclaw models set writes to openclaw.json, which is correctly locked (root:root 444 + Landlock read-only). Model routing is handled by the host-side gateway via openshell inference set (Step 5), not from inside the sandbox. The sync script should only write NemoClaw's own selection config to ~/.nemoclaw/config.json. Remove openclaw models set call, dead pythonLiteralJson helper, and unused getOpenClawPrimaryModel/DEFAULT_OLLAMA_MODEL imports. --------- Co-authored-by: Frank Ruiz <frankr@nvidia.com>
…DIA#601) * docs: add community feedback invitation for policy presets * docs: link baseline policy reference to the YAML file on GitHub * fix: pass Discord and Slack bot tokens via env vars into sandbox OpenClaw's Discord integration fails when it tries to write a user-provided bot token to openclaw.json, which is now immutable (root:root 444, Landlock read-only) after NVIDIA#588. OpenClaw already supports reading DISCORD_BOT_TOKEN and SLACK_BOT_TOKEN from environment variables, so we pass them through at sandbox creation time — the same pattern used for NVIDIA_API_KEY and TELEGRAM_BOT_TOKEN. Also disables channels.defaults.configWrites in the build-time config to prevent any channel plugin from attempting runtime writes to the immutable config file. Closes NVIDIA#599 * fix: add Discord endpoints to default sandbox policy Add discord.com, gateway.discord.gg, and cdn.discordapp.com to the baseline sandbox policy so Discord integration works without needing the preset applied separately.
…me (NVIDIA#588) * fix: resolve openclaw.json permissions conflict and scope Dockerfile lockdown - Bake both 'nvidia' and 'inference' providers into openclaw.json at image build time; remove runtime Python config-patching from buildSandboxConfigSyncScript (writes to locked root:root 444 file) - Use `openclaw models set` for runtime model selection (writes to writable agent config in .openclaw-data/) - Add identity/, devices/, canvas/, cron/ to .openclaw-data symlinks so the gateway can write device-auth.json at runtime - Remove dead `openclaw doctor --fix` and `openclaw plugins install` calls from nemoclaw-start.sh (already ran at build time, fail with EPERM at runtime) Caused-by: 2d3f84e (fix: lock gateway config via Landlock filesystem policy) Fixes NVIDIA#514 * fix: remove pragma comments that break inline Python in Dockerfile The # pragma: allowlist secret comments inside the multi-line python3 -c string cause Python to treat everything after # as a comment, swallowing the \ line continuation and closing braces. This results in: SyntaxError: '{' was never closed Reported by DanTup in PR NVIDIA#570. * fix: remove openclaw models set from sync script — config stays on host openclaw models set writes to openclaw.json, which is correctly locked (root:root 444 + Landlock read-only). Model routing is handled by the host-side gateway via openshell inference set (Step 5), not from inside the sandbox. The sync script should only write NemoClaw's own selection config to ~/.nemoclaw/config.json. Remove openclaw models set call, dead pythonLiteralJson helper, and unused getOpenClawPrimaryModel/DEFAULT_OLLAMA_MODEL imports. --------- Co-authored-by: Frank Ruiz <frankr@nvidia.com>
…DIA#601) * docs: add community feedback invitation for policy presets * docs: link baseline policy reference to the YAML file on GitHub * fix: pass Discord and Slack bot tokens via env vars into sandbox OpenClaw's Discord integration fails when it tries to write a user-provided bot token to openclaw.json, which is now immutable (root:root 444, Landlock read-only) after NVIDIA#588. OpenClaw already supports reading DISCORD_BOT_TOKEN and SLACK_BOT_TOKEN from environment variables, so we pass them through at sandbox creation time — the same pattern used for NVIDIA_API_KEY and TELEGRAM_BOT_TOKEN. Also disables channels.defaults.configWrites in the build-time config to prevent any channel plugin from attempting runtime writes to the immutable config file. Closes NVIDIA#599 * fix: add Discord endpoints to default sandbox policy Add discord.com, gateway.discord.gg, and cdn.discordapp.com to the baseline sandbox policy so Discord integration works without needing the preset applied separately.
Summary
Makes
openclaw.jsonfully immutable at runtime by moving all configuration to build time and removing all runtime config modification from inside the sandbox. Model routing is handled by the host-side gateway (openshell inference set), not from inside the sandbox.nvidiaandinferenceproviders intoopenclaw.jsonat Docker build timebuildSandboxConfigSyncScript(was writing to lockedroot:root 444file)openclaw models setfrom sync script — it also writes toopenclaw.json, which is correctly locked. Model selection happens from the host viaopenshell inference set(Step 5 of onboard)identity/,devices/,canvas/,cron/,update-check.jsonto.openclaw-datasymlinks so the gateway can write device-auth.json at runtimeopenclaw doctor --fixandopenclaw plugins installcalls fromnemoclaw-start.sh(already ran at build time, fail with EPERM at runtime)# pragma: allowlist secretcomments that broke inline Python in Dockerfile (DanTup's finding on fix: resolve openclaw.json permissions conflict and scope Dockerfile … #570)pythonLiteralJson, unusedgetOpenClawPrimaryModel/DEFAULT_OLLAMA_MODELimportsCaused-by: 2d3f84e (fix: lock gateway config via Landlock filesystem policy)
Fixes #580
Follow-up to #514
Supersedes #570 (cherry-picked + pragma fix + removed
openclaw models set)Design
The sandbox should never modify its own configuration — that's giving the prisoner keys to his own cell. The OpenShell security model enforces this via:
/sandbox/.openclawis read-only in the filesystem policyopenclaw.jsonisroot:root 444openshell inference setconfigures the gateway's privacy router from outside; the sandbox just callshttps://inference.local/v1and the gateway handles model routing, credential injection, and request rewritingThe sync script (Step 6) now only writes NemoClaw's own selection config to
~/.nemoclaw/config.json(writable). All model/provider config lives at the gateway layer.Test results
Automated
npm test— 173/173 passDocker build
nvidia,inference) baked intoopenclaw.jsonopenclaw.jsonisroot:root 444Security verification
echo >> ~/.openclaw/openclaw.jsonfails with permission denied from inside sandboxopenclaw models setfails with EACCES from inside sandbox (writes toopenclaw.json)openclaw.jsonhash unchanged after onboard (no runtime writes)Host-side model routing
openshell inference setswitches model from host without sandbox involvementSummary by CodeRabbit