Skip to content

feat(security): remove web search config from docker build - #1835

Merged
ericksoa merged 16 commits into
NVIDIA:mainfrom
ksapru:fix/secure-brave-config-final
Apr 15, 2026
Merged

feat(security): remove web search config from docker build#1835
ericksoa merged 16 commits into
NVIDIA:mainfrom
ksapru:fix/secure-brave-config-final

Conversation

@ksapru

@ksapru ksapru commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR remediates a security vulnerability where web search configuration—including API key placeholders—was exposed during the Docker build process. By migrating configuration from build-time ARG injection to runtime environment resolution via OpenShell, we ensure that no sensitive-looking strings appear in build logs, image metadata, or Docker history.

Related Issue

Fixes a security exposure where NEMOCLAW_WEB_CONFIG_B64 was leaked in docker build logs.

Issue.

Changes

  • Dockerfile:
    • Completely removed ARG NEMOCLAW_WEB_CONFIG_B64 and its corresponding ENV declaration.
    • Refactored the Python configuration script to hardcode the web.search block using the safe openshell:resolve:env:BRAVE_API_KEY placeholder.
  • src/lib/onboard.ts:
    • Removed build-time Dockerfile modification for web search.
    • Implemented runtime injection of the BRAVE_API_KEY directly into the sandbox's environment variables during openshell sandbox create.
  • scripts/install-openshell.sh:
    • Fixed architecture detection on macOS to correctly identify Apple Silicon even when the shell is running in Rosetta 2 mode (preventing 404 errors during OpenShell upgrades).

Type of Change

  • Code change for a new feature, bug fix, or refactor.

Testing

  • npx prek run --all-files passes (manually verified).
  • Verified that docker build logs no longer contain NEMOCLAW_WEB_CONFIG_B64.
  • Verified that docker inspect <image> and docker history <image> show no trace of the web search configuration.
  • Verified runtime resolution of the API key within the sandbox.

Checklist

General

Code Changes

  • Formatters applied.
  • No secrets, API keys, or credentials committed.
  • Doc pages updated (N/A — security refactor).

Signed-off-by: Krish Sapru ksapru@bu.edu

Summary by CodeRabbit

  • Chores
    • Simplified build/runtime config by removing encoded web-config plumbing.
    • Added a clear flag to enable or disable web search configuration.
    • When enabled, web search uses Brave with fetch enabled and API key resolved from the environment.
    • Runtime injects the Brave API key only when web search is enabled and a key is present.
  • Tests
    • Updated staged-Dockerfile tests to reflect the new enable/disable flag behavior.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removed build-time base64 web config and added NEMOCLAW_WEB_SEARCH_ENABLED build arg/ENV. Image generation now emits the Brave tools.web block only when the flag is "1". Onboard injects BRAVE_API_KEY into sandboxes only if fetch is enabled and a non-empty key is available.

Changes

Cohort / File(s) Summary
Dockerfile
Dockerfile
Removed ARG NEMOCLAW_WEB_CONFIG_B64 and its ENV. Added ARG NEMOCLAW_WEB_SEARCH_ENABLED and exported ENV.
Config generation / image content
.../python config generation (embedded in image build)
Replaced base64-decoding flow with a gate on NEMOCLAW_WEB_SEARCH_ENABLED: when "1" emits tools.web with search.provider: brave, search.apiKey: openshell:resolve:env:BRAVE_API_KEY, and fetch.enabled: true; when "0" omits the tools.web block.
Sandbox / Onboard code
src/lib/onboard.ts
patchStagedDockerfile() writes ARG NEMOCLAW_WEB_SEARCH_ENABLED ("1" if webSearchConfig present, else "0"). createSandbox() appends BRAVE_API_KEY env only when webSearchConfig?.fetchEnabled and a non-empty key from getCredential(...) or process.env[...].
Tests
test/onboard.test.ts
Replaced test fixtures/placeholders: ARG NEMOCLAW_WEB_CONFIG_B64=e30=ARG NEMOCLAW_WEB_SEARCH_ENABLED=0. Updated assertions to expect ARG NEMOCLAW_WEB_SEARCH_ENABLED=1 when Brave search is enabled.

Sequence Diagram(s)

sequenceDiagram
    participant Builder as Builder (Dockerfile)
    participant Image as Image (openclaw.json)
    participant Onboard as Onboard Service
    participant Cred as Credential Source
    participant Sandbox as Sandbox (runtime)

    Builder->>Image: Build with ARG NEMOCLAW_WEB_SEARCH_ENABLED
    alt NEMOCLAW_WEB_SEARCH_ENABLED == "1"
        Image->>Image: emit tools.web with Brave provider\napiKey resolver: openshell:resolve:env:BRAVE_API_KEY\nfetch.enabled: true
    else
        Image->>Image: omit tools.web block
    end
    Onboard->>Cred: request BRAVE_API_KEY (getCredential / env)
    Cred-->>Onboard: return key or empty
    alt key non-empty and fetch enabled
        Onboard->>Sandbox: create sandbox with BRAVE_API_KEY env
    else
        Onboard->>Sandbox: create sandbox without BRAVE_API_KEY
    end
    Sandbox-->>Onboard: sandbox ready
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I swapped a baked blob for a single little flag,
Now Brave wakes only when the build tag will wag.
If fetch craves a key and the vault does supply,
I tuck it in the sandbox — quiet, not sly.
A rabbit’s hop: smaller images, lighter sky.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: removing web search configuration from Docker build to address a security vulnerability by migrating to runtime resolution.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@ksapru
ksapru marked this pull request as ready for review April 13, 2026 13:36
@wscurran wscurran added security dependencies Pull requests that update a dependency file labels Apr 13, 2026
@wscurran

Copy link
Copy Markdown
Contributor

✨ Thanks for submitting this PR, which proposes a fix for a security vulnerability by removing web search configuration from the Docker build process.

Related issue: #1741

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
Dockerfile (1)

160-171: ⚠️ Potential issue | 🟠 Major

Preserve web-search gating to avoid config/runtime drift.

Line 166 and Line 168 hard-enable tools.web.search and tools.web.fetch, but src/lib/onboard.ts:2680-2701 only injects BRAVE_API_KEY when webSearchConfig?.fetchEnabled is true. This makes fetchEnabled=false ineffective and can produce always-on web config with missing credentials. Keep the secret handling change, but restore a non-secret enablement gate (or remove the toggle everywhere and align behavior explicitly).

Proposed fix (non-secret flag, secrets still runtime-only)
@@
 ARG NEMOCLAW_PROXY_HOST=10.200.0.1
 ARG NEMOCLAW_PROXY_PORT=3128
+ARG NEMOCLAW_WEB_FETCH_ENABLED=0
@@
 ENV NEMOCLAW_MODEL=${NEMOCLAW_MODEL} \
@@
     NEMOCLAW_PROXY_HOST=${NEMOCLAW_PROXY_HOST} \
-    NEMOCLAW_PROXY_PORT=${NEMOCLAW_PROXY_PORT}
+    NEMOCLAW_PROXY_PORT=${NEMOCLAW_PROXY_PORT} \
+    NEMOCLAW_WEB_FETCH_ENABLED=${NEMOCLAW_WEB_FETCH_ENABLED}
@@
-config.update({ \
-    'tools': { \
-        'web': { \
-            'search': { \
-                'enabled': True, \
-                'provider': 'brave', \
-                'apiKey': 'openshell:resolve:env:BRAVE_API_KEY' \
-            }, \
-            'fetch': {'enabled': True} \
-        } \
-    } \
-}); \
+web_fetch_enabled = os.environ.get('NEMOCLAW_WEB_FETCH_ENABLED', '') == '1'; \
+if web_fetch_enabled: \
+    config.update({ \
+        'tools': { \
+            'web': { \
+                'search': { \
+                    'enabled': True, \
+                    'provider': 'brave', \
+                    'apiKey': 'openshell:resolve:env:BRAVE_API_KEY' \
+                }, \
+                'fetch': {'enabled': True} \
+            } \
+        } \
+    }); \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 160 - 171, The config block currently hard-sets
tools.web.search.enabled and tools.web.fetch.enabled to true, which ignores the
non-secret gate used by the runtime secret injector (BRAVE_API_KEY) and causes
config/runtime drift; change the config.update call that sets
'tools.web.search.enabled' and 'tools.web.fetch.enabled' to use a non-secret
enablement flag (e.g., a boolean variable or env flag) instead of hard true so
the runtime logic that checks webSearchConfig?.fetchEnabled still controls
whether BRAVE_API_KEY is injected, or remove the redundant toggle everywhere and
make enablement explicit in one place; look for the config.update call that sets
'tools.web.search'/'tools.web.fetch' and the runtime injector that references
BRAVE_API_KEY and ensure only the non-secret gate (not the secret presence)
decides fetchEnabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@Dockerfile`:
- Around line 160-171: The config block currently hard-sets
tools.web.search.enabled and tools.web.fetch.enabled to true, which ignores the
non-secret gate used by the runtime secret injector (BRAVE_API_KEY) and causes
config/runtime drift; change the config.update call that sets
'tools.web.search.enabled' and 'tools.web.fetch.enabled' to use a non-secret
enablement flag (e.g., a boolean variable or env flag) instead of hard true so
the runtime logic that checks webSearchConfig?.fetchEnabled still controls
whether BRAVE_API_KEY is injected, or remove the redundant toggle everywhere and
make enablement explicit in one place; look for the config.update call that sets
'tools.web.search'/'tools.web.fetch' and the runtime injector that references
BRAVE_API_KEY and ensure only the non-secret gate (not the secret presence)
decides fetchEnabled.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 25f81872-8320-42ec-95ba-13c57a45a06d

📥 Commits

Reviewing files that changed from the base of the PR and between 27d0668 and 990da7e.

📒 Files selected for processing (2)
  • Dockerfile
  • src/lib/onboard.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/onboard.ts

@prekshivyas prekshivyas self-assigned this Apr 14, 2026
@cv cv added the v0.0.16 label Apr 14, 2026
prekshivyas and others added 2 commits April 14, 2026 13:08
Address CodeRabbit review: the Dockerfile always enabled web search
with a placeholder API key, even when the user didn't configure Brave.
This caused config/runtime drift — openclaw.json had search.enabled
but no actual key at runtime.

- Add NEMOCLAW_WEB_SEARCH_ENABLED build arg (non-secret boolean)
- Gate the web search config block on this flag
- Replace NEMOCLAW_WEB_CONFIG_B64 patching with NEMOCLAW_WEB_SEARCH_ENABLED
- Update tests to match new arg name

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@prekshivyas

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/onboard.test.ts (1)

642-642: Add a regression guard for legacy NEMOCLAW_WEB_CONFIG_B64 removal.

The enabled-path assertion should also verify the deprecated build arg is absent, so secret-bearing config injection can’t silently reappear.

Patch suggestion
       const patched = fs.readFileSync(dockerfilePath, "utf8");
       assert.match(patched, /^ARG NEMOCLAW_WEB_SEARCH_ENABLED=1$/m);
+      assert.doesNotMatch(patched, /^ARG NEMOCLAW_WEB_CONFIG_B64=/m);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/onboard.test.ts` at line 642, The test currently only asserts the
enabled build arg exists (assert.match(patched, /^ARG
NEMOCLAW_WEB_SEARCH_ENABLED=1$/m)); update this test to also assert the
deprecated secret-bearing arg is absent by adding a negative assertion against
"NEMOCLAW_WEB_CONFIG_B64" (e.g., use assert.doesNotMatch or assert.notMatch on
the same patched string) so the legacy build arg cannot silently reappear —
locate the assertion on the patched variable in onboard.test.ts and add the
complementary absence check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/onboard.test.ts`:
- Line 642: The test currently only asserts the enabled build arg exists
(assert.match(patched, /^ARG NEMOCLAW_WEB_SEARCH_ENABLED=1$/m)); update this
test to also assert the deprecated secret-bearing arg is absent by adding a
negative assertion against "NEMOCLAW_WEB_CONFIG_B64" (e.g., use
assert.doesNotMatch or assert.notMatch on the same patched string) so the legacy
build arg cannot silently reappear — locate the assertion on the patched
variable in onboard.test.ts and add the complementary absence check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 444abca2-2176-4295-bb44-173b29909b4c

📥 Commits

Reviewing files that changed from the base of the PR and between c009e60 and 745655d.

📒 Files selected for processing (3)
  • Dockerfile
  • src/lib/onboard.ts
  • test/onboard.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/onboard.ts

The Dockerfile's Python script is a single-line python3 -c command.
An if-block with indented body is invalid in this context. Use the
inline ternary form: config.update({...}) if condition else None.

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
test/onboard.test.ts (1)

642-642: Add a negative assertion to lock out legacy config regressions.

Consider explicitly asserting the patched Dockerfile no longer contains NEMOCLAW_WEB_CONFIG_B64.

✅ Suggested assertion addition
       const patched = fs.readFileSync(dockerfilePath, "utf8");
       assert.match(patched, /^ARG NEMOCLAW_WEB_SEARCH_ENABLED=1$/m);
+      assert.doesNotMatch(patched, /^ARG NEMOCLAW_WEB_CONFIG_B64=/m);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/onboard.test.ts` at line 642, Add a negative assertion to the existing
test that verifies the patched Dockerfile no longer contains the legacy variable
by asserting the string "NEMOCLAW_WEB_CONFIG_B64" is not present in the
`patched` variable used in test/onboard.test.ts (near the existing
assert.match(patched, /^ARG NEMOCLAW_WEB_SEARCH_ENABLED=1$/m)). Insert an
assertion using the test framework's negative match (e.g., assert.doesNotMatch
or assert.notInclude depending on project conventions) to explicitly fail if
`NEMOCLAW_WEB_CONFIG_B64` appears, preventing regressions to the legacy config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/onboard.test.ts`:
- Line 642: Add a negative assertion to the existing test that verifies the
patched Dockerfile no longer contains the legacy variable by asserting the
string "NEMOCLAW_WEB_CONFIG_B64" is not present in the `patched` variable used
in test/onboard.test.ts (near the existing assert.match(patched, /^ARG
NEMOCLAW_WEB_SEARCH_ENABLED=1$/m)). Insert an assertion using the test
framework's negative match (e.g., assert.doesNotMatch or assert.notInclude
depending on project conventions) to explicitly fail if
`NEMOCLAW_WEB_CONFIG_B64` appears, preventing regressions to the legacy config.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 27b0ccaa-1486-417e-b309-b9a365dde385

📥 Commits

Reviewing files that changed from the base of the PR and between c009e60 and 745655d.

📒 Files selected for processing (3)
  • Dockerfile
  • src/lib/onboard.ts
  • test/onboard.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/onboard.ts

- Add negative assertion: patched Dockerfile must NOT contain
  NEMOCLAW_WEB_CONFIG_B64 (prevents secret-bearing arg reintroduction)
- Remove dead buildWebSearchDockerConfig() and encodeDockerJsonArg()
  from web-search.ts — no longer called after build arg removal
- Update web-search tests to match simplified module

Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cv
cv requested a review from ericksoa April 14, 2026 20:41
@prekshivyas
prekshivyas requested a review from cv April 14, 2026 21:14

@ericksoa ericksoa left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — clean removal of build-time secret exposure. Runtime injection via openshell:resolve:env is the right pattern. Net deletion, dead code removed, regression guard in tests.

@ericksoa
ericksoa merged commit f808af4 into NVIDIA:main Apr 15, 2026
1 check passed
@wscurran wscurran added chore Build, CI, dependency, or tooling maintenance and removed priority: high labels Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

chore Build, CI, dependency, or tooling maintenance dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants