feat(sandbox): bake Homebrew core into the sandbox base image (#3913)#3916
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (3)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughBakes Homebrew into the sandbox base image (cloned as the sandbox user and symlinked), adds ChangesHomebrew sandbox support
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@Dockerfile.base`:
- Around line 233-240: The RUN step currently clones Homebrew with a moving ref
(--branch=stable); replace this with a pinned ref workflow: add an ARG (e.g.
HOMEBREW_REF) and in the same RUN use gosu sandbox git clone --depth=1
https://github.com/Homebrew/brew.git /home/linuxbrew/.linuxbrew/Homebrew (no
--branch), then run gosu sandbox git -C /home/linuxbrew/.linuxbrew/Homebrew
fetch --depth=1 origin ${HOMEBREW_REF} && gosu sandbox git -C
/home/linuxbrew/.linuxbrew/Homebrew checkout --detach ${HOMEBREW_REF} so the
layer is reproducible and tied to the immutable ref; ensure HOMEBREW_REF ARG is
declared and documented.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: b74bd06a-5deb-461b-b8d9-05a0fcfe7f1c
📒 Files selected for processing (3)
Dockerfile.basenemoclaw-blueprint/policies/openclaw-sandbox.yamltest/policies.test.ts
…#3913) The brew policy preset advertises "Homebrew (Linuxbrew) package manager access" and whitelists the linuxbrew binary paths, but the sandbox cannot actually install Homebrew at runtime: 1. nemoclaw-blueprint/policies/openclaw-sandbox.yaml does not list /home/linuxbrew under filesystem_policy.read_write, so Landlock denies writes there. 2. The sandbox runs as the unprivileged `sandbox` user with no sudo (Dockerfile.base creates `sandbox` with HOME=/sandbox). Homebrew's install script's first step is `sudo` to create + chown /home/linuxbrew/.linuxbrew, which the sandbox cannot grant. Applying the brew preset and running the documented bootstrap line in the sandbox fails immediately with "Insufficient permissions to install Homebrew to /home/linuxbrew/.linuxbrew". The preset's binary whitelist for /home/linuxbrew/.linuxbrew/bin/* becomes dead code. This change bakes Homebrew core into the sandbox base image during build (running as root, then dropping to sandbox via gosu for the clone). The companion baseline-policy change adds /home/linuxbrew to filesystem_policy.read_write so brew can extract bottles and manage Cellar/opt symlinks at runtime. After the next base-image rebuild, every sandbox starts with a working brew at /home/linuxbrew/.linuxbrew/bin/brew. Applying the brew preset plus running `brew install <formula>` works end-to-end with no bootstrap required. Precedent: NVIDIA#3682 (in v0.0.46, closes NVIDIA#3677) baked the WeChat plugin into the sandbox base image for the same reason. Files: - Dockerfile.base: add the brew install step at the end, after the WeChat plugin block. Image-build cost is ~80 to 150 MB for Homebrew core (formulae download on demand). - nemoclaw-blueprint/policies/openclaw-sandbox.yaml: add /home/linuxbrew to filesystem_policy.read_write with an inline comment explaining the link to the Dockerfile.base step. - test/policies.test.ts: add a behavior-style assertion that the baseline read_write list includes the Homebrew prefix, so a future removal of this entry trips CI. Out of scope for this PR: - agents/hermes/Dockerfile.base. Hermes does not currently get the WeChat plugin baking either; same scoping rationale. - Adding /home/linuxbrew/.linuxbrew/bin to the system PATH. Users can run `brew` via the full path or via `eval "$(brew shellenv)"`. PR NVIDIA#3846 documents the latter. Closes NVIDIA#3913 Refs NVIDIA#1767, NVIDIA#3757 Signed-off-by: latenighthackathon <support@latenighthackathon.com>
d091c18 to
91b7020
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@Dockerfile.base`:
- Around line 247-255: The Dockerfile sets ARG HOMEBREW_VERSION=5.1.12 but that
tag doesn't exist, causing the git clone in the RUN block (gosu sandbox git
clone --depth=1 --branch="${HOMEBREW_VERSION}"
https://github.com/Homebrew/brew.git /home/linuxbrew/.linuxbrew/Homebrew) to
fail; update the ARG HOMEBREW_VERSION to a valid tag such as 5.1.11 so the
branch="${HOMEBREW_VERSION}" clone succeeds and subsequent steps (ln -s
.../Homebrew/bin/brew and gosu sandbox /home/linuxbrew/.linuxbrew/bin/brew
--version) run correctly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 960321a5-14a7-48c8-8bae-61fff3822f98
📒 Files selected for processing (3)
Dockerfile.basenemoclaw-blueprint/policies/openclaw-sandbox.yamltest/policies.test.ts
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Keep Homebrew's baseline read_write path in permissive OpenClaw policies so live policy transitions used by shields down and network-policy E2E do not attempt to remove /home/linuxbrew after #3916. Adds regression coverage that permissive policies retain baseline read_write paths.
Rewrites the two passive sentences flagged by review in the new Homebrew Specifics subsection and the brew preset row of the security best-practices table, and regenerates the matching agent skill mirrors. No content change beyond voice; the post-NVIDIA#3916 flow (apply preset, then brew install) is unchanged. Signed-off-by: latenighthackathon <support@latenighthackathon.com> Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
After NVIDIA#3916 baked Homebrew (Linuxbrew) into the sandbox base image, the brew preset is the only step needed before installing a formula. Update the integration policy examples, the security best-practices preset table, and the brew preset description to reflect the new flow so users no longer expect a separate Homebrew bootstrap. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Rewrites the two passive sentences flagged by review in the new Homebrew Specifics subsection and the brew preset row of the security best-practices table, and regenerates the matching agent skill mirrors. No content change beyond voice; the post-NVIDIA#3916 flow (apply preset, then brew install) is unchanged. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
…3946) ## Summary Updates the network-policy docs and the brew preset description so users know that the brew binary already ships in the sandbox base image after #3916, and the brew preset is the only step needed before installing a formula. ## Problem Before #3916 landed, the brew preset only granted network egress and assumed Homebrew was already on PATH. Several pages still describe brew as a generic package-manager preset without mentioning that the binary is now baked into the image, so a new user following the integration policy examples can be left looking for a separate bootstrap step that no longer applies. This PR brings those pages and the preset description in line with the post-#3916 flow. ## Changes - docs/network-policy/integration-policy-examples.mdx: add a short "Homebrew Specifics" subsection under Package and Model Tooling that explains the post-#3916 flow with a concrete policy-add brew + exec -- brew install example, and notes that no separate Homebrew bootstrap, build dependency install, or `brew shellenv` step is required. - docs/security/best-practices.mdx: expand the brew preset row in the policy preset table to note that the binary is preinstalled and the preset only opens network egress to GitHub and the Homebrew formulae index. - nemoclaw-blueprint/policies/presets/brew.yaml: clarify in the preset description and a YAML comment that the brew binary is preinstalled (referencing #3913). - .agents/skills/nemoclaw-user-{configure-security,manage-policy}/references/*.md: regenerated via the canonical docs-to-skills invocation so the agent skill mirrors stay in sync with the source pages. ## Test plan - python3 scripts/docs-to-skills.py docs/ .agents/skills/ --prefix nemoclaw-user --doc-platform fern-mdx: skill regen runs to completion and only touches the two expected reference mirrors. - Pre-commit + pre-push hooks (markdownlint-cli2, Verify docs-to-skills output, Test (skills YAML), Source-shape test budget, TypeScript (CLI), gitleaks, NEMOCLAW_* env-var doc gate, etc.) all pass on the docs-only diff. Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Expanded security best-practices entry for the Homebrew preset to explain what the preset enables and the risk of installing arbitrary packages. * Added a “Homebrew Specifics” integration guide with usage examples showing how to apply the preset and run brew installs in the sandbox. * Clarified that Homebrew is preinstalled in the sandbox base image and that the preset grants outbound access to fetch formulae/bottles. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/NVIDIA/NemoClaw/pull/3946?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com> Co-authored-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
## Summary Fixes #3757 as a follow-up to #3916. The base image now exposes `brew` on the normal sandbox `PATH` without making Homebrew infer `/usr/local` as its prefix. ## Problem #3916 correctly baked Homebrew into `/home/linuxbrew/.linuxbrew`, but it exposed the entry point with a plain `/usr/local/bin/brew` symlink. In a fresh sandbox that made Homebrew report `/usr/local` as the prefix, which broke the QA path in two ways: - `brew install --quiet hello` tried to create locks under root-owned `/usr/local/var/homebrew`. - On arm64, Homebrew rejected the Intel/default `/usr/local` prefix. After fixing the prefix, installed formula commands also need to be reachable in sandbox shell sessions without asking users to source `brew shellenv` manually. ## Changes - Replace the `/usr/local/bin/brew` symlink with a wrapper that execs `/home/linuxbrew/.linuxbrew/bin/brew`, preserving the Linuxbrew prefix. - Add build-time assertions that `/usr/local/bin/brew --prefix` and sandbox login-shell `brew --prefix` both return `/home/linuxbrew/.linuxbrew`. - Add `/home/linuxbrew/.linuxbrew/bin` through `/etc/profile.d/nemoclaw-linuxbrew.sh` only for the `sandbox` user, while deliberately avoiding a global Docker `ENV PATH` entry for the sandbox-writable Linuxbrew prefix. - Add network-policy E2E coverage for the real QA path: apply `brew`, install `hello`, resolve `hello` on PATH, and run it. - Update the Homebrew docs wording from symlink to wrapper and show shell-session execution for installed formula commands. ## Validation - `npm run build:cli` - `npm run typecheck:cli` - `npx vitest run test/policies.test.ts` - `npm run source-shape:check` - `hadolint Dockerfile.base` - `bash -n test/e2e/test-network-policy.sh` - `shellcheck test/e2e/test-network-policy.sh` - `git diff --check` - Throwaway base-image behavior smoke with this wrapper/profile change applied in-container and no global Linuxbrew `PATH`: - root login shell does not include `/home/linuxbrew/.linuxbrew/bin` - direct `/usr/local/bin/brew --prefix` -> `/home/linuxbrew/.linuxbrew` - sandbox login shell includes `/home/linuxbrew/.linuxbrew/bin` - `brew install --quiet hello` succeeds - `command -v hello` -> `/home/linuxbrew/.linuxbrew/bin/hello` - `hello` -> `Hello, world!` ## Local caveats - Full `docker build -f Dockerfile.base ...` could not run on this host because Docker has no working buildx/BuildKit component; the legacy builder fails on the existing `RUN --mount` step before reaching this change. - `npx vitest run test/nemoclaw-start.test.ts test/policies.test.ts` passed the related policy coverage but still hit existing local fixture failures in `test/nemoclaw-start.test.ts` because `nemoclaw/node_modules/json5` is absent in this worktree. `test/policies.test.ts` passes by itself. - `npx prek run --files Dockerfile.base docs/network-policy/integration-policy-examples.mdx test/policies.test.ts` passed the visible file hooks including hadolint, gitleaks, docs-to-skills, and source-shape, then failed in the full CLI test hook on the same unrelated `test/nemoclaw-start.test.ts` baseline-capture failures plus a coverage temp-file ENOENT. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Chores** * Switched Homebrew runtime to a wrapper-script approach and conditionally expose the package manager bin on sandbox shell PATH * Added runtime validations to ensure wrapper and PATH behave correctly across privileged and sandbox sessions * Made sandbox command execution timeouts configurable * **Documentation** * Clarified wrapper behavior and how installed commands are available in sandbox sessions * **Tests** * Updated test wording to reference "PATH wrapper" * Added an end-to-end test validating install/use flow and expected runtime behavior (TC-NET-11) <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/NVIDIA/NemoClaw/pull/4459?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Summary
Closes #3913. Bakes Homebrew core into the sandbox base image so the
brewpolicy preset actually works end-to-end. Without this, applying the preset and runningbrew install <formula>inside the sandbox fails withInsufficient permissions to install Homebrew to "/home/linuxbrew/.linuxbrew"because the sandbox lacks filesystem write to/home/linuxbrew/AND runs as an unprivileged user with no sudo.Problem
Two filesystem constraints block runtime Homebrew installation today:
nemoclaw-blueprint/policies/openclaw-sandbox.yamllists only[/tmp, /dev/null, /sandbox/.openclaw, /sandbox/.nemoclaw]plus the workdir underfilesystem_policy.read_write. Landlock denies writes to/home/linuxbrew/.Dockerfile.baseruns the sandbox asuseradd -r -g sandbox -d /sandbox -s /bin/bash sandbox(no sudo). Homebrew's install script's first step issudoto create + chown/home/linuxbrew/.linuxbrew, which the sandbox cannot grant.Result: the
brewpreset's binary whitelist for/home/linuxbrew/.linuxbrew/bin/*is dead code. Two QA reports already documented the symptom: #1767 (filed 2026-04-10) and #3757 (filed 2026-05-18). #3846 was an interim docs-only attempt that we closed in favor of this PR; documenting a bootstrap that cannot actually succeed in the default sandbox would have set wrong expectations.Approach
Follow the #3682 precedent (WeChat plugin baked into the base image in v0.0.46): provision the tool at image-build time, when the build runs as root and can create + chown the prefix.
Dockerfile.base: after the existing WeChat plugin install block, add a step that creates/home/linuxbrew/.linuxbrew/bin, chowns tosandbox:sandbox, clones Homebrew core as the sandbox user viagosuat the pinnedARG HOMEBREW_VERSION=5.1.12tag, symlinks thebrewentry point, and assertsbrew --versionsucceeds. TheARGkeeps the base-image layer reproducible across rebuilds and can be bumped by editing the default or passing--build-arg.nemoclaw-blueprint/policies/openclaw-sandbox.yaml: add/home/linuxbrewtofilesystem_policy.read_writeso runtimebrew install <formula>can extract bottles and manage Cellar/opt symlinks.test/policies.test.ts: behavior-style assertion that the baselineread_writelist includes the Homebrew prefix. Trips CI if a future change drops the entry.Cost
Approximately 80 to 150 MB on the base image (Homebrew core only; formulae download on demand). Acceptable relative to the existing ~2.4 GB sandbox image and consistent with the trade-off the project already accepted for the WeChat plugin baking.
Follow-up docs PR planned
Once this merges and the base image rebuilds, a small docs PR will land covering the new flow on the matching MDX pages: apply the
brewpreset, runbrew install <formula>. The previous attempt at documenting a runtime bootstrap (closed #3846) becomes obsolete and the new docs will reflect thatbrewis included by default.Out of scope
agents/hermes/Dockerfile.base— Hermes does not currently get the WeChat plugin baking either; same scoping rationale. Can extend in a follow-up if maintainers want Homebrew under Hermes.PATH— users can runbrewvia the full path/home/linuxbrew/.linuxbrew/bin/brewor viaeval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)". A separate PR could add the prefix to/etc/bash.bashrcif a cleaner default UX is wanted.Test plan
prek run --filesclean on all changed filestest/policies.test.tsassertion passes (Homebrew prefixtest)read_writearray); satisfies the source-shape budgetHOMEBREW_VERSION=5.1.12tag exists upstream (gh api repos/Homebrew/brew/releases+ directhttps://github.com/Homebrew/brew/releases/tag/5.1.12returns 200)brew --versionruns inside a freshly-created sandbox (covered by the build-timegosu sandbox /home/linuxbrew/.linuxbrew/bin/brew --versionassertion inDockerfile.base)nemoclaw onboard-><name> policy-add brew --yes-><name> connect->brew install hello-> succeedsSigned-off-by: latenighthackathon latenighthackathon@users.noreply.github.com
Summary by CodeRabbit
New Features
Tests