Skip to content

feat: runtime config mutability via OpenClaw shim + OpenShell v0.0.15#940

Open
ericksoa wants to merge 15 commits intomainfrom
feat/runtime-config-mutability
Open

feat: runtime config mutability via OpenClaw shim + OpenShell v0.0.15#940
ericksoa wants to merge 15 commits intomainfrom
feat/runtime-config-mutability

Conversation

@ericksoa
Copy link
Contributor

@ericksoa ericksoa commented Mar 25, 2026

Summary

POC for runtime config mutability — allows changing OpenClaw config fields (model, agent preferences) without sandbox recreation.

  • OpenClaw shim patch: patches/apply-openclaw-shim.js injects _nemoClawMergeOverrides() into all 6 dist entry points. Reads OPENCLAW_CONFIG_OVERRIDES_FILE, deep-merges onto frozen openclaw.json, strips gateway.* for security.
  • OpenShell v0.0.15 minimum: auto-TLS termination (removes 35 tls: terminate annotations), security hardening SEC-002–010, version check enforced in onboard preflight.
  • Config CLI: nemoclaw <sandbox> config-set --key <path> --value <value> and config-get via openshell sandbox upload/download.
  • OpenShell shim patch: patches/openshell-config-approval.patch extends PolicyChunk approval flow for config: prefixed chunks (TUI shows CONFIG badge, server skips network merge on approval, sandbox proxy scans for request files and applies approved overrides).

Verified

  • Gateway log confirms override applied: agent model: inference/SHIM-TEST-WORKS
  • gateway.* blocked at CLI level + stripped by shim (defense in depth)
  • 338/340 tests pass (2 skipped, 0 failures)
  • All pre-commit and pre-push hooks pass

Test plan

  • nemoclaw onboard with patched OpenShell cluster
  • nemoclaw <sandbox> config-set --key agents.defaults.model.primary --value "inference/new-model"
  • Verify override persists via config-get
  • Verify gateway.auth.token change is refused
  • Verify gateway log shows overridden model after sandbox restart

Summary by CodeRabbit

  • New Features

    • Added config-get and config-set commands to manage sandbox runtime configuration
    • Enabled dynamic configuration overrides that persist independently from initial setup
  • Updates

    • Increased minimum OpenClaw version requirement to 0.0.15
    • Updated network policies to improve configuration handling

ericksoa added 15 commits March 24, 2026 21:48
…0.0.15

EXPERIMENTAL — POC branch to validate three-tier config resolution:
  1. Frozen openclaw.json (gateway.auth.token, CORS — always immutable)
  2. Policy defaults (config_overrides in openclaw-sandbox.yaml)
  3. User runtime overrides (nemoclaw config-set → overrides file → hot-reload)

OpenClaw shim patch (patches/openclaw-config-overrides.patch):
- Adds OPENCLAW_CONFIG_OVERRIDES_FILE env var support to config loader
- Deep-merges overrides onto frozen config, stripping gateway.* for security
- Adds overrides file to chokidar watcher for hot-reload

OpenShell minimum bumped to v0.0.15:
- Auto-TLS termination (PR #544) — removes need for tls: terminate
- Security hardening SEC-002–010 (PR #548)
- Runtime settings channel (PR #474)
- Version check now enforced in onboard preflight

Policy changes:
- Remove 35 deprecated tls: terminate annotations (base + all presets)
- Remove permissive wildcard L7 rules from claude_code/nvidia endpoints
- Add config_overrides section defining mutable fields + defaults

New commands:
- nemoclaw <sandbox> config-set --key <path> --value <value>
- nemoclaw <sandbox> config-get [--key <path>]
Patch file for OpenShell server + TUI that extends the PolicyChunk
approval flow to handle config-change requests (config: prefix on
rule_name). Applied the same way as the OpenClaw shim — at build time,
not pushed upstream.

Server: skip network policy merge for config: chunks on approval.
TUI: show CONFIG badge, display config key instead of endpoint.
Updated openshell-config-approval.patch now includes:

Sandbox side (lib.rs, grpc_client.rs):
- Config request scanner: polls /sandbox/.openclaw-data/config-requests/
  for JSON request files, submits as config: PolicyChunks via existing
  SubmitPolicyAnalysis gRPC (same pattern as network denial submission)
- Config apply loop: in the policy poll loop, checks for approved config:
  chunks and writes merged overrides to config-overrides.json5
- get_draft_policy client method for fetching approved chunks
- gateway.* blocked at submission time (defense in depth)

Server side (grpc.rs):
- approve_draft_chunk: skip network merge for config: chunks
- submit_policy_analysis: relax proposed_rule for config: chunks

TUI side (sandbox_draft.rs):
- CONFIG badge for config: chunks
- Panel renamed "Rules & Config"

Round-trip flow:
1. Agent writes {"key":"...","value":"..."} to config-requests/*.json
2. Sandbox proxy scans, creates PolicyChunk, submits to gateway
3. TUI shows CONFIG chunk with key name, user approves with [a]
4. Sandbox poll loop detects approval, writes overrides file
5. OpenClaw hot-reloads via chokidar watcher
…atch

The policy YAML uses deny_unknown_fields — extension fields are not
allowed. Mutable config field allow-list lives in NemoClaw code
(config-set.js), not in the policy YAML. The filesystem_policy already
controls what's writable via the read_only/read_write path lists.

Also: detect dev-build OpenShell and use local image tag instead of
non-existent GHCR tag.
…quired L7 rules

- config-set.js: use `openshell sandbox connect` with stdin piping
  instead of nonexistent `openshell exec` command
- onboard.js: same fix for overrides file write
- openclaw-sandbox.yaml: restore required wildcard rules on endpoints
  with protocol: rest + enforcement: enforce (proxy validates their
  presence)

Tested: config-set writes overrides, config-get reads them back,
gateway.* is blocked.
Root cause: OpenClaw bundler duplicates resolveConfigForRead into 6
dist chunks. Previous patch only hit config-CO7zBdn8.js but gateway
runs through daemon-cli.js. New approach patches ALL files.

Also: sandbox connect can't write files (different mount namespace).
Switched to openshell sandbox upload/download.
Gateway log shows: agent model: inference/SHIM-TEST-WORKS

The OpenClaw config overrides shim successfully deep-merges the
overrides file onto the frozen openclaw.json at config load time.
Pre-seeded override in entrypoint, verified via gateway log download.

Entrypoint temporarily hardcodes a test override for verification —
revert to empty {} default after confirming.
Resolve 6 merge conflicts to combine main's security/structural
changes with the runtime config mutability feature:

- Dockerfile: use base image FROM, apply openclaw shim on pre-installed
  CLI, keep all new build args and security hardening, add
  OPENCLAW_CONFIG_OVERRIDES_FILE env var
- nemoclaw.js: import both config-set and inference-config modules
- onboard.js: use main's formatEnvAssignment/patchStagedDockerfile,
  add OPENCLAW_CONFIG_OVERRIDES_FILE to sandbox env args
- npm.yaml/pypi.yaml: take main's access:full + binaries structure
- nemoclaw-start.sh: keep main's privilege separation (gosu/gateway
  user), add overrides file creation in both root and non-root paths
- Remove config_overrides tests (section no longer in policy YAML)
- Convert config-set.test.js to ESM/vitest
- All 338 tests pass
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

This PR introduces a runtime configuration override system for OpenClaw, enabling dynamic sandbox configuration updates via JSON5 file merging. Changes include new CLI commands (config-set/config-get), OpenClaw shim patches for merging overrides at startup, OpenShell server-side approval logic, network policy cleanup (removing TLS termination directives), updated version constraints, and supporting infrastructure including container setup and tests.

Changes

Cohort / File(s) Summary
CLI Configuration Management
bin/lib/config-set.js, bin/nemoclaw.js
New module for reading, parsing, validating, and updating sandbox config overrides via dotted-path keys with allow-list enforcement; integrated into CLI as config-set and config-get actions.
Sandbox Initialization & Gateway
bin/lib/onboard.js
Added preflight version enforcement from blueprint, conditional gateway image selection (dev vs. pinned), repository patches directory copy to sandbox build context, and writeConfigOverridesFromPolicy() helper to initialize overrides from policy defaults.
Container & Runtime Setup
Dockerfile, scripts/nemoclaw-start.sh, scripts/install-openshell.sh
Updated container build to apply OpenClaw shim patch; startup script now exports and initializes OPENCLAW_CONFIG_OVERRIDES_FILE at /sandbox/.openclaw-data/config-overrides.json5; raised minimum OpenShell version to 0.0.15.
OpenClaw Config Override Shim
patches/apply-openclaw-shim.js, patches/apply-openclaw-shim.sh, patches/openclaw-config-overrides.patch
Three complementary patch implementations (Node.js, Bash, and unified patch file) that inject a config merge shim into OpenClaw's resolveConfigForRead(), enabling deep-merge of runtime overrides while blocking gateway.* keys.
OpenShell Server-Side Config Management
patches/openshell-config-approval.patch
Adds sandbox-side config request scanning, policy chunk creation, server-side approval handling for config:* chunks, and automatic application of approved configs to /sandbox/.openclaw-data/config-overrides.json5.
Network Policy Cleanup
nemoclaw-blueprint/policies/openclaw-sandbox.yaml, nemoclaw-blueprint/policies/presets/{discord,docker,huggingface,jira,outlook,slack,telegram}.yaml
Removed deprecated tls: terminate directives from all REST endpoint definitions across base and preset network policies; deleted entire rules blocks from two endpoints (statsig.anthropic.com, sentry.io).
Metadata & Version Requirements
nemoclaw-blueprint/blueprint.yaml
Updated min_openshell_version from "0.1.0" to "0.0.15".
Testing & Validation
test/config-set.test.js, test/policies.test.js, scripts/poc-round-trip-test.sh
Added unit tests validating allow-list restrictions and override file paths; policy tests verifying removal of deprecated tls: terminate from all policies; interactive end-to-end POC script for manual config mutation workflows.

Sequence Diagram

sequenceDiagram
    participant Sandbox as Sandbox Process
    participant TaskLoop as Background Task
    participant OpenShell as OpenShell gRPC
    participant Backend as OpenShell Backend
    participant FS as Filesystem
    
    Sandbox->>TaskLoop: Start periodic config request scan
    loop Every poll interval
        TaskLoop->>FS: Check /sandbox/.openclaw-data/config-requests/*.json
        FS-->>TaskLoop: Return request files
        TaskLoop->>TaskLoop: Parse key/value, block gateway.*
        TaskLoop->>OpenShell: submit_policy_analysis(config:key chunks)
        OpenShell->>Backend: Process config policy chunks
        Backend->>Backend: Mark config chunks as "approved"
        TaskLoop->>FS: Delete processed request files
    end
    
    Sandbox->>Sandbox: Policy revision polling
    Sandbox->>OpenShell: get_draft_policy(status="approved")
    OpenShell-->>Sandbox: Return approved config:* chunks
    Sandbox->>FS: Deep-merge rationale JSON, write config-overrides.json5
    FS-->>Sandbox: Overrides file updated
    Sandbox->>Sandbox: OpenClaw reads and applies merged config
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

enhancement: testing

Suggested reviewers

  • kjw3

Poem

🐰 Hops through config overrides with glee
No more frozen JSON, now dynamically free!
Gateway keys blocked, while the shims gently merge,
Runtime mutations emerge! Emerge! Emerge!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: runtime config mutability via OpenClaw shim + OpenShell v0.0.15' clearly and specifically summarizes the main changes: introducing runtime configuration mutability through an OpenClaw shim implementation and requiring OpenShell v0.0.15.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 feat/runtime-config-mutability
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/runtime-config-mutability

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

Copy link
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: 13

🧹 Nitpick comments (5)
patches/openshell-config-approval.patch (1)

214-218: Non-atomic file write may cause race with chokidar watcher.

std::fs::write(CONFIG_OVERRIDES_PATH, &json) is not atomic. The watcher (from openclaw-config-overrides.patch:47) may trigger during the write, causing the shim to read a truncated or empty file.

The shim handles parse errors gracefully by returning the original config, but this could cause:

  1. Spurious error logs
  2. Missed config updates until the next watcher trigger
🔧 Suggested fix: atomic write via temp file + rename
use std::io::Write;

let tmp_path = format!("{}.tmp", CONFIG_OVERRIDES_PATH);
let mut file = std::fs::File::create(&tmp_path)?;
file.write_all(json.as_bytes())?;
file.sync_all()?;
std::fs::rename(&tmp_path, CONFIG_OVERRIDES_PATH)?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@patches/openshell-config-approval.patch` around lines 214 - 218, The current
non-atomic write using std::fs::write(CONFIG_OVERRIDES_PATH, &json) can race
with the chokidar watcher and produce truncated reads; replace it with an atomic
write: write the JSON to a temporary file (e.g., tmp_path = format!("{}.tmp",
CONFIG_OVERRIDES_PATH)) using File::create and write_all, call file.sync_all(),
then std::fs::rename(tmp_path, CONFIG_OVERRIDES_PATH) and handle/report errors
from each operation instead of returning early after the non-atomic write; touch
the same surrounding function where CONFIG_OVERRIDES_PATH and the warn! call
live to locate the change.
bin/lib/onboard.js (2)

1627-1637: Duplicate setNestedValue function - also exists in bin/lib/config-set.js:106-116.

Both implementations are identical. Extract to a shared utility module to avoid duplication.

♻️ Suggested refactor

Create a shared utility, e.g., bin/lib/utils.js:

function setNestedValue(obj, dottedPath, value) {
  const parts = dottedPath.split(".");
  let current = obj;
  for (let i = 0; i < parts.length - 1; i++) {
    if (!(parts[i] in current) || typeof current[parts[i]] !== "object") {
      current[parts[i]] = {};
    }
    current = current[parts[i]];
  }
  current[parts[parts.length - 1]] = value;
}

module.exports = { setNestedValue };

Then import in both onboard.js and config-set.js.

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

In `@bin/lib/onboard.js` around lines 1627 - 1637, The duplicate setNestedValue
implementation should be extracted to a shared utility and imported where
needed: create a new module (e.g., utils.js) that exports the setNestedValue
function, remove the local setNestedValue definitions in the modules containing
the duplicates, and replace them with a require/import of the shared
setNestedValue; ensure the exported function name matches the current usage
sites (setNestedValue) so callers in onboard.js and config-set.js continue to
work without other changes.

1569-1580: writeConfigOverridesFromPolicy silently returns if the policy file lacks a config_overrides section.

The function searches for "\nconfig_overrides:\n" in nemoclaw-blueprint/policies/openclaw-sandbox.yaml, but this section does not exist in the file. The function returns early without writing anything and without logging output, making the behavior opaque.

If no policy-derived defaults are expected at this stage, add debug logging to clarify intent:

Debug logging suggestion
   const startIdx = yaml.indexOf("\nconfig_overrides:\n");
-  if (startIdx === -1) return;
+  if (startIdx === -1) {
+    console.log("  ⓘ No config_overrides section in policy — skipping initial overrides file");
+    return;
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 1569 - 1580, The function
writeConfigOverridesFromPolicy currently returns silently when the policy file
is missing or when the "\nconfig_overrides:\n" section isn't found; update
writeConfigOverridesFromPolicy to emit a clear debug/info log in both
early-return cases (when fs.existsSync(policyPath) is false and when startIdx
=== -1) that includes the policyPath and a short message (e.g., "policy file not
found" or "no config_overrides section present") so callers can understand why
no overrides were written; locate the logic around policyPath,
fs.existsSync(policyPath), and the startIdx === -1 check and add the logging
there (use the existing logging mechanism or console if none exists).
patches/openclaw-config-overrides.patch (1)

10-10: Consider removing synchronous debug logging before production.

The appendFileSync calls on lines 10, 14, 26, and 30 are helpful for POC debugging but add synchronous I/O overhead in the config resolution path. Consider gating these behind a debug flag or removing them for production.

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

In `@patches/openclaw-config-overrides.patch` at line 10, The synchronous debug
logging calls using fs$1.appendFileSync (seen writing to
"/sandbox/.openclaw-data/nemoclaw-shim.log") should not run unconditionally in
the config resolution path; change the code to either remove these
appendFileSync calls or gate them behind a debug flag (e.g. an environment
variable like OPENCLAW_DEBUG or a module-level debug constant) and/or switch to
asynchronous logging (fs.appendFile) so they don't block execution; update every
occurrence (the appendFileSync calls that mention OPENCLAW_CONFIG_OVERRIDES_FILE
and any similar lines) to check the debug flag before logging or replace with
non-blocking I/O.
bin/lib/config-set.js (1)

44-46: Redundant require() calls inside functions.

fs is already required at line 8, and os is required multiple times inside functions (lines 45, 63, 88). Move these to the top of the file for clarity.

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

In `@bin/lib/config-set.js` around lines 44 - 46, The function sandboxRun contains
redundant require() calls for "os" and "fs" (fs is already required earlier and
os is required multiple times inside functions); refactor by moving const os =
require("os") and const fs = require("fs") to the top-level module scope and
remove the duplicate requires inside sandboxRun and any other functions (e.g.,
where os is required again), keeping the rest of sandboxRun's logic unchanged
and referencing the top-level os and fs variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/config-set.js`:
- Around line 168-172: The gateway check is case-sensitive and can be bypassed
(e.g., "Gateway" or "GATEWAY"); update the condition that guards key to use a
case-insensitive comparison by lowercasing the key (e.g., call
key.toLowerCase()) and then test startsWith("gateway.") or === "gateway" so keys
like "Gateway.auth.token" are refused; apply this change where the variable key
is evaluated in the existing block that currently checks
key.startsWith("gateway.") || key === "gateway".
- Around line 73-74: The code reads the overrides file into raw (const raw =
fs.readFileSync(dlFile, "utf-8")) and then calls JSON.parse(raw), which fails
for JSON5 features; update this to use a JSON5 parser (e.g., require/import
json5 and call JSON5.parse(raw)) so comments/trailing commas are accepted, and
add the json5 dependency to package.json; ensure you reference the same
dlFile/raw variables and replace the JSON.parse call in config-set.js
accordingly.

In `@Dockerfile`:
- Around line 73-74: The env variable OPENCLAW_CONFIG_OVERRIDES_FILE currently
points to a .json5 file but the shim in patches/apply-openclaw-shim.js uses
JSON.parse (which only accepts strict JSON); fix by making the formats
consistent — either rename OPENCLAW_CONFIG_OVERRIDES_FILE to end with .json
(update the Dockerfile value) or update patches/apply-openclaw-shim.js to parse
JSON5 (import/require the json5 package and replace JSON.parse with JSON5.parse
when reading the file) and ensure the dependency is available at build/runtime.
- Around line 29-31: The apply-openclaw-shim step can silently succeed if fewer
files are patched than expected; update patches/apply-openclaw-shim.js to
enforce a hard failure by defining an EXPECTED_PATCHES constant (set to 6) and,
after the patch loop that increments patched, compare patched to
EXPECTED_PATCHES and call process.exit(1) after logging an error if they differ
so the Docker RUN fails when the shim no longer matches upstream bundles.

In `@patches/apply-openclaw-shim.js`:
- Around line 17-18: The shim currently reads the override file into _raw and
uses JSON.parse to produce _ov, which fails for JSON5 files; change the parse
step to use a JSON5 parser (replace JSON.parse(_raw) with JSON5.parse(_raw)) and
ensure you require the JSON5 module available in the OpenClaw bundle (e.g.
require the existing deps.json5 or require('json5') depending on the bundle
context) so the code uses JSON5.parse; update the code that sets _ov to call
JSON5.parse and add the corresponding require/import for JSON5 inside
apply-openclaw-shim.js.
- Around line 19-20: The code currently only deletes the lowercase property
using delete _ov.gateway which allows keys like "Gateway" or "GATEWAY" to
remain; update the logic that works with the overrides object (_ov) to normalize
key casing by iterating Object.keys(_ov) and deleting any property whose
key.toLowerCase() === 'gateway' (i.e., remove all case variants), ensuring you
still check _ov exists and is an object before iterating.

In `@patches/apply-openclaw-shim.sh`:
- Around line 1-62: This script patches OpenClaw files but is unused—either
remove patches/apply-openclaw-shim.sh from the repo or add clear documentation
about when/how it is invoked; if you intend to keep it, fix
_nemoClawMergeOverrides in the SHIM: replace JSON.parse with a JSON5 parser
(require("json5").parse or similar) to match the "JSON5 overlay" claim, and
instead of the case-sensitive delete _ov.gateway perform a case-insensitive
removal of any top-level keys named "gateway" (e.g., iterate Object.keys(_ov)
and delete keys where key.toLowerCase() === "gateway") before the deep-merge
implementation (_dm) to ensure gateway.* keys are stripped regardless of casing.

In `@patches/openclaw-config-overrides.patch`:
- Around line 16-17: The current deletion only removes the lowercase key (delete
_ov.gateway) and misses case variants; update the cleanup to remove any key
whose case-insensitive name equals "gateway" (e.g., iterate Object.keys(_ov) and
delete keys where key.toLowerCase() === "gateway") so both "gateway" and
"Gateway" (and other variants) are removed; apply the same pattern used in
apply-openclaw-shim.js for consistency with the security model.

In `@patches/openshell-config-approval.patch`:
- Around line 110-114: The gateway block check in the Rust scanner (the if using
key.starts_with("gateway.") || key == "gateway" in the shown snippet) is
case-sensitive and can be bypassed; change the check to normalize case (e.g.,
use key.to_lowercase() and then starts_with("gateway.") or == "gateway") and
keep the existing behavior of warn!(...) and processed_files.push(path) /
continue; also apply the same case-normalization fix to the client-side check in
bin/lib/config-set.js so keys like "Gateway.auth.token" or "GATEWAY" are
correctly blocked.
- Line 213: The current config applier only deletes the exact lowercase
"gateway" key via merged.remove("gateway"), which misses variants like "Gateway"
or "GATEWAY"; update the removal logic to perform a case-insensitive removal by
scanning the merged map's keys for any that equalIgnoreCase("gateway") (collect
matches and remove them) so all casing variants are deleted; locate the usage of
merged.remove("gateway") and replace it with the case-insensitive key
discovery-and-remove approach to ensure the security constraint is enforced for
any key casing.

In `@scripts/nemoclaw-start.sh`:
- Around line 257-260: The current block writes and chowns
OPENCLAW_CONFIG_OVERRIDES_FILE even if it's a symlink into sandbox-writable
areas; to fix, before creating the file in the script, validate the path: ensure
OPENCLAW_CONFIG_OVERRIDES_FILE is not a symlink (test -L) and does not resolve
to a location under /sandbox/.openclaw-data (check prefix or use readlink -f and
reject paths starting with /sandbox/.openclaw-data); if either check fails, do
not create or chown the file and emit a clear error/log message; otherwise
safely create the file (echo '{}' > ...) and chown as currently done.

In `@scripts/poc-round-trip-test.sh`:
- Around line 21-31: The script defines SANDBOX_NAME="poc-test" but then calls
nemoclaw onboard (and relies on interactive wait_enter), which will create a
sandbox with the default name instead of using $SANDBOX_NAME; update the call
site so the onboarding command uses the intended sandbox name (e.g., pass the
name to the nemoclaw onboard command or use the CLI's non-interactive
flag/parameter to set the sandbox to SANDBOX_NAME), or alternatively replace the
interactive flow (wait_enter / nemoclaw onboard) with a prompt that instructs
the user to enter "poc-test" when asked and verify the onboarding
returned/created SANDBOX_NAME before proceeding. Ensure changes reference
SANDBOX_NAME, wait_enter, and the nemoclaw onboard invocation so later steps
that use $SANDBOX_NAME refer to the actual created sandbox.

In `@test/config-set.test.js`:
- Around line 18-22: The allow-list test currently only ensures no keys start
with "gateway.", but must also reject the exact "gateway" key; update the test
that iterates over allowList (variable allowList in the "does NOT include
gateway paths" it block) to assert each key is neither equal to "gateway" nor
startsWith("gateway."), e.g. replace the single
assert.ok(!key.startsWith("gateway."), ...) with a combined check that fails if
key === "gateway" or key.startsWith("gateway."), preserving the existing failure
message and context.

---

Nitpick comments:
In `@bin/lib/config-set.js`:
- Around line 44-46: The function sandboxRun contains redundant require() calls
for "os" and "fs" (fs is already required earlier and os is required multiple
times inside functions); refactor by moving const os = require("os") and const
fs = require("fs") to the top-level module scope and remove the duplicate
requires inside sandboxRun and any other functions (e.g., where os is required
again), keeping the rest of sandboxRun's logic unchanged and referencing the
top-level os and fs variables.

In `@bin/lib/onboard.js`:
- Around line 1627-1637: The duplicate setNestedValue implementation should be
extracted to a shared utility and imported where needed: create a new module
(e.g., utils.js) that exports the setNestedValue function, remove the local
setNestedValue definitions in the modules containing the duplicates, and replace
them with a require/import of the shared setNestedValue; ensure the exported
function name matches the current usage sites (setNestedValue) so callers in
onboard.js and config-set.js continue to work without other changes.
- Around line 1569-1580: The function writeConfigOverridesFromPolicy currently
returns silently when the policy file is missing or when the
"\nconfig_overrides:\n" section isn't found; update
writeConfigOverridesFromPolicy to emit a clear debug/info log in both
early-return cases (when fs.existsSync(policyPath) is false and when startIdx
=== -1) that includes the policyPath and a short message (e.g., "policy file not
found" or "no config_overrides section present") so callers can understand why
no overrides were written; locate the logic around policyPath,
fs.existsSync(policyPath), and the startIdx === -1 check and add the logging
there (use the existing logging mechanism or console if none exists).

In `@patches/openclaw-config-overrides.patch`:
- Line 10: The synchronous debug logging calls using fs$1.appendFileSync (seen
writing to "/sandbox/.openclaw-data/nemoclaw-shim.log") should not run
unconditionally in the config resolution path; change the code to either remove
these appendFileSync calls or gate them behind a debug flag (e.g. an environment
variable like OPENCLAW_DEBUG or a module-level debug constant) and/or switch to
asynchronous logging (fs.appendFile) so they don't block execution; update every
occurrence (the appendFileSync calls that mention OPENCLAW_CONFIG_OVERRIDES_FILE
and any similar lines) to check the debug flag before logging or replace with
non-blocking I/O.

In `@patches/openshell-config-approval.patch`:
- Around line 214-218: The current non-atomic write using
std::fs::write(CONFIG_OVERRIDES_PATH, &json) can race with the chokidar watcher
and produce truncated reads; replace it with an atomic write: write the JSON to
a temporary file (e.g., tmp_path = format!("{}.tmp", CONFIG_OVERRIDES_PATH))
using File::create and write_all, call file.sync_all(), then
std::fs::rename(tmp_path, CONFIG_OVERRIDES_PATH) and handle/report errors from
each operation instead of returning early after the non-atomic write; touch the
same surrounding function where CONFIG_OVERRIDES_PATH and the warn! call live to
locate the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 53a2a906-0e41-42b0-813e-5e35426124e2

📥 Commits

Reviewing files that changed from the base of the PR and between 2e0066f and e6efd70.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (22)
  • Dockerfile
  • bin/lib/config-set.js
  • bin/lib/onboard.js
  • bin/nemoclaw.js
  • nemoclaw-blueprint/blueprint.yaml
  • nemoclaw-blueprint/policies/openclaw-sandbox.yaml
  • nemoclaw-blueprint/policies/presets/discord.yaml
  • nemoclaw-blueprint/policies/presets/docker.yaml
  • nemoclaw-blueprint/policies/presets/huggingface.yaml
  • nemoclaw-blueprint/policies/presets/jira.yaml
  • nemoclaw-blueprint/policies/presets/outlook.yaml
  • nemoclaw-blueprint/policies/presets/slack.yaml
  • nemoclaw-blueprint/policies/presets/telegram.yaml
  • patches/apply-openclaw-shim.js
  • patches/apply-openclaw-shim.sh
  • patches/openclaw-config-overrides.patch
  • patches/openshell-config-approval.patch
  • scripts/install-openshell.sh
  • scripts/nemoclaw-start.sh
  • scripts/poc-round-trip-test.sh
  • test/config-set.test.js
  • test/policies.test.js
💤 Files with no reviewable changes (7)
  • nemoclaw-blueprint/policies/presets/telegram.yaml
  • nemoclaw-blueprint/policies/presets/huggingface.yaml
  • nemoclaw-blueprint/policies/presets/docker.yaml
  • nemoclaw-blueprint/policies/presets/discord.yaml
  • nemoclaw-blueprint/policies/presets/jira.yaml
  • nemoclaw-blueprint/policies/presets/outlook.yaml
  • nemoclaw-blueprint/policies/presets/slack.yaml

Comment on lines +73 to +74
const raw = fs.readFileSync(dlFile, "utf-8");
return JSON.parse(raw);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

JSON.parse() cannot parse JSON5 features in the overrides file.

The file is named config-overrides.json5 and other parts of the system (e.g., the server-side Rust code) may write JSON5 content with comments or trailing commas. Using JSON.parse() here will fail on valid JSON5 content.

🐛 Proposed fix: Use a JSON5 parser
+const JSON5 = require("json5");
+
 function readOverrides(sandboxName) {
   // ...
     const raw = fs.readFileSync(dlFile, "utf-8");
-    return JSON.parse(raw);
+    return JSON5.parse(raw);

You may need to add json5 as a dependency in package.json.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const raw = fs.readFileSync(dlFile, "utf-8");
return JSON.parse(raw);
const raw = fs.readFileSync(dlFile, "utf-8");
return JSON5.parse(raw);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/config-set.js` around lines 73 - 74, The code reads the overrides
file into raw (const raw = fs.readFileSync(dlFile, "utf-8")) and then calls
JSON.parse(raw), which fails for JSON5 features; update this to use a JSON5
parser (e.g., require/import json5 and call JSON5.parse(raw)) so
comments/trailing commas are accepted, and add the json5 dependency to
package.json; ensure you reference the same dlFile/raw variables and replace the
JSON.parse call in config-set.js accordingly.

Comment on lines +168 to +172
// Security: block gateway.* regardless of allow-list
if (key.startsWith("gateway.") || key === "gateway") {
console.error(` Refused: gateway.* fields are immutable (security-enforced).`);
process.exit(1);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Case-sensitive gateway check allows bypass with "Gateway" or "GATEWAY".

The check key.startsWith("gateway.") only matches lowercase. A user could bypass this by using --key Gateway.auth.token or --key GATEWAY.auth.token.

Per context snippets, the server-side Rust code has the same case-sensitive check, so there's no secondary defense.

🛡️ Proposed fix
   // Security: block gateway.* regardless of allow-list
-  if (key.startsWith("gateway.") || key === "gateway") {
+  const keyLower = key.toLowerCase();
+  if (keyLower.startsWith("gateway.") || keyLower === "gateway") {
     console.error(`  Refused: gateway.* fields are immutable (security-enforced).`);
     process.exit(1);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Security: block gateway.* regardless of allow-list
if (key.startsWith("gateway.") || key === "gateway") {
console.error(` Refused: gateway.* fields are immutable (security-enforced).`);
process.exit(1);
}
// Security: block gateway.* regardless of allow-list
const keyLower = key.toLowerCase();
if (keyLower.startsWith("gateway.") || keyLower === "gateway") {
console.error(` Refused: gateway.* fields are immutable (security-enforced).`);
process.exit(1);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/config-set.js` around lines 168 - 172, The gateway check is
case-sensitive and can be bypassed (e.g., "Gateway" or "GATEWAY"); update the
condition that guards key to use a case-insensitive comparison by lowercasing
the key (e.g., call key.toLowerCase()) and then test startsWith("gateway.") or
=== "gateway" so keys like "Gateway.auth.token" are refused; apply this change
where the variable key is evaluated in the existing block that currently checks
key.startsWith("gateway.") || key === "gateway".

Comment on lines +29 to +31
COPY patches/apply-openclaw-shim.js /tmp/apply-openclaw-shim.js
RUN node /tmp/apply-openclaw-shim.js /usr/local/lib/node_modules/openclaw \
&& rm /tmp/apply-openclaw-shim.js
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fail closed when the shim stops matching upstream bundles.

patches/apply-openclaw-shim.js counts patched files but doesn't abort when that count drops below the expected six. If OpenClaw changes its dist layout or the target signature, this RUN still succeeds and you'll ship an image with runtime overrides silently disabled.

🔧 Add a hard failure in patches/apply-openclaw-shim.js
const EXPECTED_PATCHES = 6;

// ...existing patch loop...

if (patched !== EXPECTED_PATCHES) {
  console.error(
    `[nemoclaw-shim] expected ${EXPECTED_PATCHES} patched files, got ${patched}`,
  );
  process.exit(1);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 29 - 31, The apply-openclaw-shim step can silently
succeed if fewer files are patched than expected; update
patches/apply-openclaw-shim.js to enforce a hard failure by defining an
EXPECTED_PATCHES constant (set to 6) and, after the patch loop that increments
patched, compare patched to EXPECTED_PATCHES and call process.exit(1) after
logging an error if they differ so the Docker RUN fails when the shim no longer
matches upstream bundles.

Comment on lines +73 to +74
NEMOCLAW_INFERENCE_COMPAT_B64=${NEMOCLAW_INFERENCE_COMPAT_B64} \
OPENCLAW_CONFIG_OVERRIDES_FILE=/sandbox/.openclaw-data/config-overrides.json5
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

The public overrides path says JSON5, but the injected shim only accepts JSON.

OPENCLAW_CONFIG_OVERRIDES_FILE now ends with .json5, while patches/apply-openclaw-shim.js still reads it with JSON.parse. Comments or trailing commas will be rejected at runtime even though the surrounding contract advertises JSON5. Either parse JSON5 in the shim or rename this contract to .json.

🧰 Tools
🪛 Trivy (0.69.3)

[error] 67-74: Secrets passed via build-args or envs or copied secret files

Possible exposure of secret env "NEMOCLAW_PROVIDER_KEY" in ENV

Rule: DS-0031

Learn more

(IaC/Dockerfile)

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

In `@Dockerfile` around lines 73 - 74, The env variable
OPENCLAW_CONFIG_OVERRIDES_FILE currently points to a .json5 file but the shim in
patches/apply-openclaw-shim.js uses JSON.parse (which only accepts strict JSON);
fix by making the formats consistent — either rename
OPENCLAW_CONFIG_OVERRIDES_FILE to end with .json (update the Dockerfile value)
or update patches/apply-openclaw-shim.js to parse JSON5 (import/require the
json5 package and replace JSON.parse with JSON5.parse when reading the file) and
ensure the dependency is available at build/runtime.

Comment on lines +17 to +18
\t\tvar _raw = require("node:fs").readFileSync(_p, "utf-8");
\t\tvar _ov = JSON.parse(_raw);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: JSON.parse() cannot parse JSON5 files.

The injected shim uses JSON.parse() but the override file is config-overrides.json5. JSON5 supports comments, trailing commas, and unquoted keys that JSON.parse() will reject with a syntax error.

The authoritative .patch file at patches/openclaw-config-overrides.patch:15 correctly uses JSON5.parse(_raw). This shim must match that behavior.

🐛 Proposed fix: Use JSON5 parser

The injected code needs access to a JSON5 parser. Since this runs inside the OpenClaw bundle which already has json5 available (via deps.json5), consider one of these approaches:

-\t\tvar _ov = JSON.parse(_raw);
+\t\tvar _ov = require("json5").parse(_raw);

Or if json5 isn't reliably available as a standalone module in the bundle context, you may need to inject a reference to the existing deps.json5 or bundle a minimal JSON5 parser inline.

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

In `@patches/apply-openclaw-shim.js` around lines 17 - 18, The shim currently
reads the override file into _raw and uses JSON.parse to produce _ov, which
fails for JSON5 files; change the parse step to use a JSON5 parser (replace
JSON.parse(_raw) with JSON5.parse(_raw)) and ensure you require the JSON5 module
available in the OpenClaw bundle (e.g. require the existing deps.json5 or
require('json5') depending on the bundle context) so the code uses JSON5.parse;
update the code that sets _ov to call JSON5.parse and add the corresponding
require/import for JSON5 inside apply-openclaw-shim.js.

Comment on lines +110 to +114
+ if key.starts_with("gateway.") || key == "gateway" {
+ warn!(key = %key, "Config request for gateway.* blocked");
+ processed_files.push(path);
+ continue;
+ }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Case-sensitive gateway blocking in Rust scanner.

The check key.starts_with("gateway.") || key == "gateway" only matches lowercase. Requests with "Gateway.auth.token" or "GATEWAY" will bypass this check.

This must be fixed along with the client-side check in bin/lib/config-set.js to close the bypass.

🛡️ Proposed fix
-        if key.starts_with("gateway.") || key == "gateway" {
+        let key_lower = key.to_lowercase();
+        if key_lower.starts_with("gateway.") || key_lower == "gateway" {
             warn!(key = %key, "Config request for gateway.* blocked");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
+ if key.starts_with("gateway.") || key == "gateway" {
+ warn!(key = %key, "Config request for gateway.* blocked");
+ processed_files.push(path);
+ continue;
+ }
let key_lower = key.to_lowercase();
if key_lower.starts_with("gateway.") || key_lower == "gateway" {
warn!(key = %key, "Config request for gateway.* blocked");
processed_files.push(path);
continue;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@patches/openshell-config-approval.patch` around lines 110 - 114, The gateway
block check in the Rust scanner (the if using key.starts_with("gateway.") || key
== "gateway" in the shown snippet) is case-sensitive and can be bypassed; change
the check to normalize case (e.g., use key.to_lowercase() and then
starts_with("gateway.") or == "gateway") and keep the existing behavior of
warn!(...) and processed_files.push(path) / continue; also apply the same
case-normalization fix to the client-side check in bin/lib/config-set.js so keys
like "Gateway.auth.token" or "GATEWAY" are correctly blocked.

+ }
+ }
+ }
+ merged.remove("gateway");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Case-sensitive gateway key removal in config applier.

merged.remove("gateway") only removes the exact lowercase key. To fully enforce the security constraint:

🛡️ Proposed fix
-    merged.remove("gateway");
+    // Remove gateway key regardless of casing
+    let gateway_keys: Vec<_> = merged.keys()
+        .filter(|k| k.to_lowercase() == "gateway")
+        .cloned()
+        .collect();
+    for k in gateway_keys {
+        merged.remove(&k);
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
+ merged.remove("gateway");
// Remove gateway key regardless of casing
let gateway_keys: Vec<_> = merged.keys()
.filter(|k| k.to_lowercase() == "gateway")
.cloned()
.collect();
for k in gateway_keys {
merged.remove(&k);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@patches/openshell-config-approval.patch` at line 213, The current config
applier only deletes the exact lowercase "gateway" key via
merged.remove("gateway"), which misses variants like "Gateway" or "GATEWAY";
update the removal logic to perform a case-insensitive removal by scanning the
merged map's keys for any that equalIgnoreCase("gateway") (collect matches and
remove them) so all casing variants are deleted; locate the usage of
merged.remove("gateway") and replace it with the case-insensitive key
discovery-and-remove approach to ensure the security constraint is enforced for
any key casing.

Comment on lines +257 to +260
if [ -n "${OPENCLAW_CONFIG_OVERRIDES_FILE:-}" ] && [ ! -f "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
echo '{}' >"${OPENCLAW_CONFIG_OVERRIDES_FILE}"
chown sandbox:sandbox "${OPENCLAW_CONFIG_OVERRIDES_FILE}"
fi
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Don't create the overrides file through a sandbox-controlled symlink.

/sandbox/.openclaw-data is sandbox-writable. On restart, a dangling config-overrides.json5 symlink will make this root block create and chown the symlink target, which is an arbitrary-file-write/chown primitive.

🔒 Harden the root-side initialization
-if [ -n "${OPENCLAW_CONFIG_OVERRIDES_FILE:-}" ] && [ ! -f "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
-  echo '{}' >"${OPENCLAW_CONFIG_OVERRIDES_FILE}"
-  chown sandbox:sandbox "${OPENCLAW_CONFIG_OVERRIDES_FILE}"
-fi
+if [ -n "${OPENCLAW_CONFIG_OVERRIDES_FILE:-}" ]; then
+  if [ -L "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
+    echo "[SECURITY] Refusing symlink overrides path: ${OPENCLAW_CONFIG_OVERRIDES_FILE}" >&2
+    exit 1
+  fi
+  if [ -e "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ] && [ ! -f "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
+    echo "[SECURITY] Overrides path is not a regular file: ${OPENCLAW_CONFIG_OVERRIDES_FILE}" >&2
+    exit 1
+  fi
+  if [ ! -e "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
+    install -o sandbox -g sandbox -m 0644 /dev/null "${OPENCLAW_CONFIG_OVERRIDES_FILE}"
+    printf '{}' >"${OPENCLAW_CONFIG_OVERRIDES_FILE}"
+  fi
+fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if [ -n "${OPENCLAW_CONFIG_OVERRIDES_FILE:-}" ] && [ ! -f "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
echo '{}' >"${OPENCLAW_CONFIG_OVERRIDES_FILE}"
chown sandbox:sandbox "${OPENCLAW_CONFIG_OVERRIDES_FILE}"
fi
if [ -n "${OPENCLAW_CONFIG_OVERRIDES_FILE:-}" ]; then
if [ -L "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
echo "[SECURITY] Refusing symlink overrides path: ${OPENCLAW_CONFIG_OVERRIDES_FILE}" >&2
exit 1
fi
if [ -e "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ] && [ ! -f "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
echo "[SECURITY] Overrides path is not a regular file: ${OPENCLAW_CONFIG_OVERRIDES_FILE}" >&2
exit 1
fi
if [ ! -e "${OPENCLAW_CONFIG_OVERRIDES_FILE}" ]; then
install -o sandbox -g sandbox -m 0644 /dev/null "${OPENCLAW_CONFIG_OVERRIDES_FILE}"
printf '{}' >"${OPENCLAW_CONFIG_OVERRIDES_FILE}"
fi
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/nemoclaw-start.sh` around lines 257 - 260, The current block writes
and chowns OPENCLAW_CONFIG_OVERRIDES_FILE even if it's a symlink into
sandbox-writable areas; to fix, before creating the file in the script, validate
the path: ensure OPENCLAW_CONFIG_OVERRIDES_FILE is not a symlink (test -L) and
does not resolve to a location under /sandbox/.openclaw-data (check prefix or
use readlink -f and reject paths starting with /sandbox/.openclaw-data); if
either check fails, do not create or chown the file and emit a clear error/log
message; otherwise safely create the file (echo '{}' > ...) and chown as
currently done.

Comment on lines +21 to +31
SANDBOX_NAME="poc-test"

step "1. Verify prerequisites"
echo " openshell: $(openshell --version 2>&1 | head -1)"
echo " Docker image: $(docker images nemoclaw-poc:config-mutability --format '{{.Repository}}:{{.Tag}} ({{.Size}})' 2>/dev/null || echo 'NOT FOUND')"

step "2. Run nemoclaw onboard"
info "This will create a sandbox using the patched Docker image."
info "When prompted for model, accept the default."
wait_enter
nemoclaw onboard
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sandbox name mismatch: SANDBOX_NAME="poc-test" but nemoclaw onboard uses default.

The script sets SANDBOX_NAME="poc-test" but nemoclaw onboard (line 31) will prompt for or use the default name my-assistant. Subsequent commands reference $SANDBOX_NAME, which will point to a different sandbox than what was created.

🐛 Proposed fix

Either pass the sandbox name to onboard (if supported) or update the script to capture the actual name:

 SANDBOX_NAME="poc-test"
 
 step "2. Run nemoclaw onboard"
 info "This will create a sandbox using the patched Docker image."
-info "When prompted for model, accept the default."
+info "When prompted for sandbox name, enter: $SANDBOX_NAME"
+info "When prompted for model, accept the default."
 wait_enter
-nemoclaw onboard
+NEMOCLAW_SANDBOX_NAME="$SANDBOX_NAME" nemoclaw onboard --non-interactive

Or if the script is meant to be fully interactive, update the instructions to note the user should enter "poc-test" when prompted.

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

In `@scripts/poc-round-trip-test.sh` around lines 21 - 31, The script defines
SANDBOX_NAME="poc-test" but then calls nemoclaw onboard (and relies on
interactive wait_enter), which will create a sandbox with the default name
instead of using $SANDBOX_NAME; update the call site so the onboarding command
uses the intended sandbox name (e.g., pass the name to the nemoclaw onboard
command or use the CLI's non-interactive flag/parameter to set the sandbox to
SANDBOX_NAME), or alternatively replace the interactive flow (wait_enter /
nemoclaw onboard) with a prompt that instructs the user to enter "poc-test" when
asked and verify the onboarding returned/created SANDBOX_NAME before proceeding.
Ensure changes reference SANDBOX_NAME, wait_enter, and the nemoclaw onboard
invocation so later steps that use $SANDBOX_NAME refer to the actual created
sandbox.

Comment on lines +18 to +22
it("does NOT include gateway paths", () => {
const allowList = loadAllowList();
for (const key of allowList) {
assert.ok(!key.startsWith("gateway."), `allow-list must not contain gateway.* keys, found: ${key}`);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Also reject the exact gateway key.

This only guards gateway.*. If loadAllowList() ever emits gateway, config-set --key gateway ... is untested and will look allowed until the shim silently strips it.

🧪 Tighten the assertion
-        assert.ok(!key.startsWith("gateway."), `allow-list must not contain gateway.* keys, found: ${key}`);
+        assert.ok(
+          key !== "gateway" && !key.startsWith("gateway."),
+          `allow-list must not contain gateway keys, found: ${key}`,
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/config-set.test.js` around lines 18 - 22, The allow-list test currently
only ensures no keys start with "gateway.", but must also reject the exact
"gateway" key; update the test that iterates over allowList (variable allowList
in the "does NOT include gateway paths" it block) to assert each key is neither
equal to "gateway" nor startsWith("gateway."), e.g. replace the single
assert.ok(!key.startsWith("gateway."), ...) with a combined check that fails if
key === "gateway" or key.startsWith("gateway."), preserving the existing failure
message and context.

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.

1 participant