Skip to content

Add native installers and Vite+-managed runtime - #16

Merged
bmdavis419 merged 3 commits into
mainfrom
feat/native-installers-viteplus
Jul 9, 2026
Merged

Add native installers and Vite+-managed runtime#16
bmdavis419 merged 3 commits into
mainfrom
feat/native-installers-viteplus

Conversation

@bmdavis419

@bmdavis419 bmdavis419 commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Switch provisioning from NodeSource/npm globals to Vite+-managed Node/npm with native Claude and Codex installers.
  • Install Executor through Vite+ and add tx9 enter --executor for executor-container maintenance.
  • Prefer Tailscale or LAN IPs for generated box URLs so list/dashboard links work from other machines.
  • Simplify Hermes provisioning to the official latest installer and remove pinned SHA/bundle documentation and checks.
  • Update guest profile, health/version reporting, backup exclusions, and static tests for the new installer layout.

Testing

  • Not run; PR content generated from the provided commits, diff stat, and patch.

Open in Devin Review

Note

Add native installers for Claude/Codex and replace NodeSource/npm globals with Vite+-managed runtime

  • Provisioning now installs Node.js and global tools via Vite+ (vp) instead of NodeSource and npm install -g; VP_HOME is set and PATH updated in both provision.sh and profile.sh
  • Claude and Codex are installed via their official native installers rather than npm packages; launchers are symlinked into $OPT/bin and owned by agent
  • Hermes installation drops the pinned-SHA/Git-bundle approach and runs the official installer directly, always installing the latest release
  • tx9 enter gains an --executor flag to open an interactive shell inside the executor container with box environment sourced
  • URLHost in box.go now prefers the machine's Tailscale IP, then primary LAN IP, before falling back to hostname/localhost, affecting dashboard URL reachability
  • Backups exclude ~/.local/bin/claude and ~/.local/bin/codex symlinks to avoid archive validation failures
  • Risk: existing boxes provisioned with the old NodeSource/pinned-Hermes layout will differ structurally from newly provisioned ones; repair/restore paths now target the new Vite+ layout

Macroscope summarized 68787f7.

Greptile Summary

This PR moves box provisioning to a Vite+-managed runtime and native tool installers. The main changes are:

  • Replaces NodeSource/npm global provisioning with Vite+-managed Node/npm.
  • Installs Claude and Codex through their native standalone installers.
  • Runs the latest official Hermes installer instead of pinned SHA or bundle flows.
  • Adds tx9 enter --executor for executor-container maintenance.
  • Updates generated box URLs to prefer Tailscale or LAN IPs.
  • Adjusts backup exclusions, guest profiles, health/version checks, docs, and static tests for the new layout.

Confidence Score: 5/5

Safe to merge with normal installer-drift risk.

No confirmed correctness issue was found in the changed paths. The provisioning, PATH setup, backup exclusions, and runtime checks are updated consistently around the new installer model.

provision/provision.sh and guest/profile.sh deserve normal attention because they define the new runtime and install layout.

T-Rex T-Rex Logs

What T-Rex did

    • Ran Go tests for internal/box and internal/cli and confirmed they completed successfully with exit code 0.
    • Executed static checks and confirmed they passed with exit code 0.
    • Invoked enter --help to inspect the -executor description and verified the help path returned status 1 after printing, with no Docker or provisioning side effects.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
provision/provision.sh Reworked provisioning to use Vite+ managed Node/npm, native Claude/Codex installers, unpinned Hermes installer, and Vite+-installed Executor.
guest/profile.sh Switched shell PATH and runtime environment to Vite+ and native Claude/Codex launchers, with a Codex update wrapper.
internal/cli/cmd_enter.go Added tx9 enter --executor with executor-container shell environment setup.
internal/box/box.go Changed generated URL hosts to prefer Tailscale and outbound LAN IPv4 before hostname fallback.
internal/cli/cmd_backup.go Excluded regenerated Claude/Codex self-update launchers from backups while preserving other local bin entries.
box.env Replaced NodeSource/npm and Hermes pin variables with Vite+, native installer, and channel/version configuration.

Reviews (2): Last reviewed commit: "fix: address native installer review fin..." | Re-trigger Greptile

bmdavis419 and others added 2 commits July 6, 2026 21:30
Provisioning now uses each tool's official installer instead of npm
globals:

- claude: native installer (claude.ai/install.sh) — self-contained
  binary, no Node dependency; CLAUDE_CODE_CHANNEL picks stable/latest or
  a pinned version
- codex: native installer (chatgpt.com/codex/install.sh) — standalone
  binary releases under /opt/hermes-box/tooling/codex
- hermes: the normal curl | bash setup, always latest; the pinned-SHA /
  git-bundle / installer-checksum machinery (HERMES_GIT_SHA,
  HERMES_INSTALLER_SHA256, HERMES_GIT_BUNDLE, make check-hermes-pin) is
  removed
- node/npm: managed by Vite+ (vite.plus) at
  /opt/hermes-box/tooling/vite-plus, defaulted to LTS via
  `vp env default lts`; NodeSource apt install removed
- executor: installed with `vp install -g executor` so both containers
  can update it in place with `vp install -g executor@latest`

The agent user owns the vite-plus/claude/codex tooling trees so
self-updates (claude update, installer re-runs, vp installs) work
without sudo, and /data/home/agent/.local/bin is first on PATH so
updated launchers on the durable volume win over image-baked copies.

tx9 list/create/open URLs now render the machine's Tailscale IP when
one is present (100.64.0.0/10 interface scan), else the primary LAN IP,
else hostname — bare hostnames often don't resolve from the machines
these URLs are opened on. TX9_URL_HOST still overrides.

Verified e2e: image builds, tx9 create passes all doctor checks, agent
and executor containers resolve node v24 LTS + all tools through the vp
shims, claude update to a newer version wins on PATH, and vp install -g
executor@latest updates in place.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Verified each update command in a live box and fixed the two that
failed:

- hermes update: ran `uv pip install --upgrade` into a root-owned venv
  and died with EACCES. provision.sh now chowns the hermes checkout +
  venv to agent (both fresh installs and the assets repair path).
- codex update: the standalone updater detects its install by looking
  under $CODEX_HOME/packages, but runtime CODEX_HOME points at /data
  (config/auth) while the binaries live in the image tooling dir.
  profile.sh adds a codex() wrapper that points only `codex update` at
  the install home.

Working already, no change needed: vp up -g, vp upgrade (0.2.2→0.2.3 in
place), claude update (2.1.195→2.1.202 onto /data, wins on PATH).

backup: exclude ./home/agent/.local/bin — claude/codex self-updates
drop absolute-target launcher symlinks there, which the archive
validator (correctly) refuses; the launchers are recreated by the next
self-update in a restored box.

tx9 enter <box> --executor drops into the executor container as agent
with the box profile sourced, for maintenance like
`vp install -g executor@latest` (executor has no self-update
subcommand; vp is its update path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR removes Hermes pinning, switches provisioning and runtime tooling to Vite+, updates URL host resolution to use Tailscale or outbound IP detection, and adds an executor-container entry mode with matching backup exclusions.

Changes

Remove Hermes installer pinning

Layer / File(s) Summary
Drop Hermes pin check
Makefile, box.env
Removes the check-hermes-pin target and deletes the Hermes installer SHA/pinning variables from box.env.
Update Hermes pin guidance
README.md, docs/nexus-operations.md, docs/usage-guide.html, tests/static.sh
Rewrites the README and runbook references to describe the current unpinned Hermes installer flow, updates usage-guide text, and changes the static assertions to stop checking for the removed pin flow.

Adopt Vite+ managed Node/tooling

Layer / File(s) Summary
Provision via Vite+
box.env, provision/provision.sh
Updates provisioning to use VP_HOME, vp, native Claude/Codex installers, vp-based executor installation, and vp-aware Hermes/cache/verification paths.
Wire VP_HOME into guest tooling
guest/profile.sh, guest/hb, tests/static.sh
Exports VP_HOME, adjusts PATH, adds the codex() update wrapper, includes vp in hb manifest/versions/doctor output, and updates static checks for the new provisioning layout.

Tailscale/outbound IP based URL host resolution

Layer / File(s) Summary
URLHost priority chain and IP detection
internal/box/box.go, internal/box/box_test.go
Adds Tailscale and outbound-IP detection helpers and changes URLHost() to prefer those addresses before hostname or localhost, with tests for the new selection logic.

CLI executor enter mode

Layer / File(s) Summary
Add executor enter path
internal/cli/cmd_enter.go, internal/cli/dispatch.go, internal/cli/cmd_backup.go, internal/cli/cmd_backup_test.go
Adds -executor support to tx9 enter, builds a separate exec flow for the executor container, updates help text, and excludes regenerable launcher symlinks from backup archives with a test for the exclusion list.

Possibly related PRs

  • davis7dotsh/tx9#2: Touches the same Hermes pinning variables and installer verification flow that this PR removes.
  • davis7dotsh/tx9#4: Adds the check-hermes-pin target and related pin expectations that this PR deletes.
  • davis7dotsh/tx9#13: Modifies the same Makefile Hermes pin check machinery that this PR removes.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main shift to native installers and a Vite+-managed runtime.
Description check ✅ Passed The description is directly related to the changeset and covers the major provisioning, URL, and documentation updates.

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

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

Open in Devin Review

Comment thread provision/provision.sh
Comment thread provision/provision.sh
Comment on lines +127 to +128
if curl -fsSL https://hermes-agent.nousresearch.com/install.sh \
| bash -s -- "${args[@]}" --hermes-home "$HERMES_HOME" --skip-setup --non-interactive; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Hermes installer SHA256 verification and Git SHA pinning are both removed

The old provisioning flow downloaded the Hermes installer to a temp file, verified its SHA256 against HERMES_INSTALLER_SHA256 from box.env, then ran it with a pinned --commit SHA. The new flow at provision/provision.sh:127-128 pipes curl | bash directly with no checksum verification and no commit pin — the comment says "always installing the latest Hermes release." The corresponding make check-hermes-pin target and the static test grep -q 'HERMES_INSTALLER_SHA256' are also removed. This is a deliberate simplification but represents a significant reduction in reproducibility — two builds at different times may get different Hermes versions.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread provision/provision.sh
Comment thread provision/provision.sh
Comment thread provision/provision.sh
local install_dir=/usr/local/lib/hermes-agent
# Idempotent: skip the multi-minute reinstall if hermes is already on PATH.
# Lets `assets` repair mode call this safely on every run.
if command -v hermes >/dev/null 2>&1 && [[ -x "$install_dir/venv/bin/python" ]]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium provision/provision.sh:103

The idempotency guard in install_hermes skips reinstallation whenever a venv already exists, so re-running provisioning after a newer Hermes release keeps the stale version installed. The check at line 103 (command -v hermes && [[ -x "$install_dir/venv/bin/python" ]]) treats any prior install as current and returns early, logging hermes already installed — skipping reinstall. Since the PR switched from a pinned commit to the upstream "latest" installer, a box provisioned with an older Hermes will never receive updates on subsequent provisioning runs. Consider comparing the installed version against the target release before skipping, or removing the skip when CODEX_RELEASE/the requested version differs from what is already installed.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @provision/provision.sh around line 103:

The idempotency guard in `install_hermes` skips reinstallation whenever a venv already exists, so re-running provisioning after a newer Hermes release keeps the stale version installed. The check at line 103 (`command -v hermes && [[ -x "$install_dir/venv/bin/python" ]]`) treats *any* prior install as current and returns early, logging `hermes already installed — skipping reinstall`. Since the PR switched from a pinned commit to the upstream "latest" installer, a box provisioned with an older Hermes will never receive updates on subsequent provisioning runs. Consider comparing the installed version against the target release before skipping, or removing the skip when `CODEX_RELEASE`/the requested version differs from what is already installed.

Comment thread internal/box/box.go
// interface carries one. Detected by address range rather than by shelling
// out to the tailscale CLI, so it works regardless of how tailscaled was
// installed.
func tailscaleIP() string {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium box/box.go:248

tailscaleIP() returns the first local IPv4 address in 100.64.0.0/10, treating any CGNAT address as Tailscale. That range is the general RFC 6598 carrier-grade NAT shared space, not unique to Tailscale, so a host on an ISP/mobile/other-VPN CGNAT link is misidentified as having Tailscale. URLHost() then publishes an arbitrary non-Tailscale CGNAT address in dashboard/open URLs, producing links unreachable from other machines. Consider checking that the address belongs to a Tailscale interface (e.g. by interface name or via the Tailscale API) rather than relying on the CGNAT range alone.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/box/box.go around line 248:

`tailscaleIP()` returns the first local IPv4 address in `100.64.0.0/10`, treating any CGNAT address as Tailscale. That range is the general RFC 6598 carrier-grade NAT shared space, not unique to Tailscale, so a host on an ISP/mobile/other-VPN CGNAT link is misidentified as having Tailscale. `URLHost()` then publishes an arbitrary non-Tailscale CGNAT address in dashboard/open URLs, producing links unreachable from other machines. Consider checking that the address belongs to a Tailscale interface (e.g. by interface name or via the Tailscale API) rather than relying on the CGNAT range alone.

Comment thread internal/cli/cmd_backup.go Outdated
Comment thread internal/cli/cmd_enter.go

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Medium

if b.AgentState != "running" || b.ExecutorState != "running" {

tx9 enter --executor <box> fails when the agent container is stopped or missing, even though the executor container exists and is running. The readiness check b.AgentState != "running" || b.ExecutorState != "running" unconditionally requires both containers and then calls box.Start, which rejects a box missing its agent with missing container(s). This blocks the executor-only maintenance path that --executor is meant to provide. Consider gating the agent checks on !*intoExecutor so executor entry only requires the executor container to be present and running.

-		if b.AgentState != "running" || b.ExecutorState != "running" {
+		if (!*intoExecutor && b.AgentState != "running") || b.ExecutorState != "running" {
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @internal/cli/cmd_enter.go around line 43:

`tx9 enter --executor <box>` fails when the agent container is stopped or missing, even though the executor container exists and is running. The readiness check `b.AgentState != "running" || b.ExecutorState != "running"` unconditionally requires both containers and then calls `box.Start`, which rejects a box missing its agent with `missing container(s)`. This blocks the executor-only maintenance path that `--executor` is meant to provide. Consider gating the agent checks on `!*intoExecutor` so executor entry only requires the executor container to be present and running.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
provision/provision.sh (2)

96-152: 🔒 Security & Privacy | 🟠 Major | ⚖️ Poor tradeoff

Hermes install lost its integrity check with no replacement.

Per docs/nexus-operations.md's new historical note, the prior HERMES_GIT_SHA/HERMES_GIT_BUNDLE/HERMES_INSTALLER_SHA256 pinning verified the fetched installer against a known-good hash before execution. That verification is now gone entirely — install_hermes runs curl ... | bash unconditionally with no hash/signature check. This is an intentional simplification per the PR's stated goals, but it's worth confirming the team has accepted the reduced supply-chain guarantee for this specific installer (vs. e.g. pinning the release tag/checksum while still auto-updating).

🤖 Prompt for 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.

In `@provision/provision.sh` around lines 96 - 152, The install_hermes flow now
executes the downloaded installer without any integrity verification, so restore
a safety check or explicitly gate the change. Update install_hermes to validate
the fetched script before piping it into bash, using the existing
HERMES_INSTALLER_SHA256 or equivalent pinning approach referenced by the
historical note, or document and isolate the intentional trust tradeoff if the
team wants to keep curl | bash. Keep the fix centered on install_hermes and its
curl/bash invocation so the installer still supports auto-update while retaining
a known-good checksum or signature check.

50-152: 🔒 Security & Privacy | 🟠 Major | ⚖️ Poor tradeoff

Multiple new curl-pipe-to-shell installs without integrity verification.

Static analysis flags CWE-494 across the vite.plus, uv, claude, codex, and hermes installers — all now piped directly into bash/sh with no checksum or signature check before execution. This is a broader supply-chain exposure than before: any of these five upstream endpoints being compromised or serving a tampered script results in code execution as root during provisioning. This tradeoff appears deliberate (see the "always install latest" design goal replacing pinned installs), so raising for awareness/sign-off rather than as a blocker.

🤖 Prompt for 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.

In `@provision/provision.sh` around lines 50 - 152, The installer functions
curl-pipe remote scripts directly into bash/sh without any integrity check, so
update the provisioning flow to verify each download before execution. For the
install paths in install_vite_plus, install_uv, install_claude, install_codex,
and install_hermes, add a checksum/signature validation step (or pin to a
trusted artifact) before invoking the shell installer, and fail closed if
verification does not pass. Use the existing function names and logging points
to keep the changes localized and preserve the current install behavior only
after validation succeeds.

Source: Linters/SAST tools

🧹 Nitpick comments (1)
guest/profile.sh (1)

31-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded path duplicates $HB instead of reusing it.

$HB is already in scope (used two lines above for VP_HOME), but the update path here is hardcoded as /opt/hermes-box/tooling/codex. If $HB is ever repointed, this silently falls out of sync with the rest of the file.

♻️ Proposed fix
 codex() {
   if [ "${1:-}" = "update" ]; then
-    CODEX_HOME=/opt/hermes-box/tooling/codex command codex "$@"
+    CODEX_HOME="$HB/tooling/codex" command codex "$@"
   else
     command codex "$@"
   fi
 }
🤖 Prompt for 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.

In `@guest/profile.sh` around lines 31 - 42, The update branch in codex()
hardcodes the install path instead of reusing the existing HB variable. Update
the CODEX_HOME assignment inside the update case to derive the codex tooling
path from HB so it stays consistent with the VP_HOME setup and other references
in this script. Keep the non-update command path unchanged.
🤖 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 `@internal/box/box.go`:
- Line 272: The UDP probe in box.go uses net.Dial, which triggers the noctx lint
rule and can fail CI. Update the dial in the relevant box helper to use a
net.Dialer with DialContext instead, supplying a bounded context (with context
and time imports as needed) so the behavior stays equivalent while satisfying
golangci-lint.

---

Outside diff comments:
In `@provision/provision.sh`:
- Around line 96-152: The install_hermes flow now executes the downloaded
installer without any integrity verification, so restore a safety check or
explicitly gate the change. Update install_hermes to validate the fetched script
before piping it into bash, using the existing HERMES_INSTALLER_SHA256 or
equivalent pinning approach referenced by the historical note, or document and
isolate the intentional trust tradeoff if the team wants to keep curl | bash.
Keep the fix centered on install_hermes and its curl/bash invocation so the
installer still supports auto-update while retaining a known-good checksum or
signature check.
- Around line 50-152: The installer functions curl-pipe remote scripts directly
into bash/sh without any integrity check, so update the provisioning flow to
verify each download before execution. For the install paths in
install_vite_plus, install_uv, install_claude, install_codex, and
install_hermes, add a checksum/signature validation step (or pin to a trusted
artifact) before invoking the shell installer, and fail closed if verification
does not pass. Use the existing function names and logging points to keep the
changes localized and preserve the current install behavior only after
validation succeeds.

---

Nitpick comments:
In `@guest/profile.sh`:
- Around line 31-42: The update branch in codex() hardcodes the install path
instead of reusing the existing HB variable. Update the CODEX_HOME assignment
inside the update case to derive the codex tooling path from HB so it stays
consistent with the VP_HOME setup and other references in this script. Keep the
non-update command path unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: daa996bc-7168-4d0a-a86c-b17f93d63fe7

📥 Commits

Reviewing files that changed from the base of the PR and between 1cbea5f and d142c89.

📒 Files selected for processing (13)
  • Makefile
  • README.md
  • box.env
  • docs/nexus-operations.md
  • guest/hb
  • guest/profile.sh
  • internal/box/box.go
  • internal/box/box_test.go
  • internal/cli/cmd_backup.go
  • internal/cli/cmd_enter.go
  • internal/cli/dispatch.go
  • provision/provision.sh
  • tests/static.sh
💤 Files with no reviewable changes (1)
  • README.md

Comment thread internal/box/box.go
// (and skips loopback and docker bridge addresses that a plain interface
// scan couldn't tell apart from the real LAN address).
func outboundIP() string {
conn, err := net.Dial("udp4", "192.0.2.1:9") // TEST-NET-1, never routed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

net.Dial is flagged by the noctx linter and may fail CI.

golangci-lint reports this line as an error (net.Dial must not be called. use (*net.Dialer).DialContext). The UDP dial only performs route selection and won't block, but to satisfy the configured linter, switch to a Dialer with a bounded context.

♻️ Proposed fix using DialContext
-func outboundIP() string {
-	conn, err := net.Dial("udp4", "192.0.2.1:9") // TEST-NET-1, never routed
-	if err != nil {
-		return ""
-	}
-	defer conn.Close()
+func outboundIP() string {
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	var d net.Dialer
+	conn, err := d.DialContext(ctx, "udp4", "192.0.2.1:9") // TEST-NET-1, never routed
+	if err != nil {
+		return ""
+	}
+	defer conn.Close()

Requires adding context and time imports.

📝 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
conn, err := net.Dial("udp4", "192.0.2.1:9") // TEST-NET-1, never routed
func outboundIP() string {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
var d net.Dialer
conn, err := d.DialContext(ctx, "udp4", "192.0.2.1:9") // TEST-NET-1, never routed
if err != nil {
return ""
}
defer conn.Close()
🧰 Tools
🪛 golangci-lint (2.12.2)

[error] 272-272: net.Dial must not be called. use (*net.Dialer).DialContext

(noctx)

🤖 Prompt for 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.

In `@internal/box/box.go` at line 272, The UDP probe in box.go uses net.Dial,
which triggers the noctx lint rule and can fail CI. Update the dial in the
relevant box helper to use a net.Dialer with DialContext instead, supplying a
bounded context (with context and time imports as needed) so the behavior stays
equivalent while satisfying golangci-lint.

Source: Linters/SAST tools

@bmdavis419
bmdavis419 merged commit 73f7eba into main Jul 9, 2026
4 checks passed

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 2 new potential issues.

Open in Devin Review

Comment thread guest/profile.sh
Comment on lines +36 to +42
codex() {
if [ "${1:-}" = "update" ]; then
CODEX_HOME="$HB/tooling/codex" command codex "$@"
else
command codex "$@"
fi
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Health check for codex installation always passes, even when the binary is missing

The codex wrapper shell function (codex() at guest/profile.sh:36-42) is sourced into the health-check script (guest/hb:5), so the "codex installed" check (guest/hb:483) always succeeds because command -v finds the function, not the real binary.

Impact: A box with a missing or broken codex binary will pass hb doctor with a false "ok" for the codex-installed check, hiding the problem from operators.

The shell function shadows the binary lookup in command -v

guest/hb sources guest/profile.sh at line 5. The new codex() function defined at guest/profile.sh:36-42 is now in scope for every hb invocation. The _command_exists helper at guest/hb:468 uses command -v "$1", which returns success for shell functions — not just external binaries. So _check "codex installed" _command_exists codex at guest/hb:483 will always pass, regardless of whether the actual codex binary exists on disk.

The same issue affects _unwire_legacy at guest/hb:351 and wire_mcp at guest/hb:381, where command -v codex guards calls to codex mcp .... Those paths have their own error handling so the impact is limited there, but doctor is the primary diagnostic tool and should not silently pass.

A fix would be to check for the binary explicitly, e.g. command -v codex >/dev/null 2>&1 && [[ "$(type -t codex)" != "function" ]] || command -v "$(which codex 2>/dev/null)" ..., or more simply, have _command_exists use type -P (which only finds files on PATH, ignoring functions) instead of command -v.

Prompt for agents
The codex() shell function wrapper defined in guest/profile.sh lines 36-42 is sourced by guest/hb at line 5. This causes command -v codex (used in _command_exists at guest/hb:468) to always return true, since it finds the shell function rather than the actual binary. This makes the doctor check at guest/hb:483 ineffective for detecting a missing codex binary.

Two possible approaches:
1. In guest/hb, change _command_exists to use type -P instead of command -v. type -P only searches PATH for files and ignores shell functions and builtins. This would fix the check for codex and any future wrapper functions.
2. Alternatively, export the codex function only for interactive shells (e.g. guard it with a check like [[ $- == *i* ]]) so it doesn't affect hb's non-interactive sourcing of profile.sh.

Approach 1 is simpler and more robust. The change would be in guest/hb around line 468, changing _command_exists from command -v to type -P.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread provision/provision.sh
Comment on lines +127 to +128
if curl -fsSL https://hermes-agent.nousresearch.com/install.sh \
| bash -s -- "${args[@]}" --hermes-home "$HERMES_HOME" --skip-setup --non-interactive; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟨 Removal of installer integrity verification allows undetected supply-chain tampering

The previous code verified the Hermes installer's SHA-256 checksum against a pinned value (HERMES_INSTALLER_SHA256 in box.env) before executing it as root. The new code at provision/provision.sh:127-128 pipes curl output directly into bash with no integrity check:

curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash -s -- ...

This removes a defense-in-depth measure against supply-chain attacks. If the upstream installer is compromised or modified (MITM, CDN compromise, account takeover), the tampered script executes as root during image build with no detection.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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