fix: validate openshell binary to prevent npm package shadowing#970
fix: validate openshell binary to prevent npm package shadowing#970SaiSharan2005 wants to merge 2 commits intoNVIDIA:mainfrom
Conversation
- Add isOpenshellCLI() check to verify the resolved binary is the real OpenShell CLI (via --version output), not the npm gateway package - Surface clear error message with manual install steps when install-openshell.sh fails Fixes NVIDIA#967
📝 WalkthroughWalkthroughAdds CLI verification to resolve candidate OpenShell binaries by invoking Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Caller Code
participant Resolve as resolveOpenshell()
participant CheckExec as checkExecutable()
participant CheckCLI as isOpenshellCLI()
participant Binary as openshell Binary
Caller->>Resolve: resolveOpenshell(opts)
loop for each candidate path
Resolve->>CheckExec: checkExecutable(path)
CheckExec-->>Resolve: executable?
alt executable
Resolve->>CheckCLI: opts.checkCLI || isOpenshellCLI(path)
CheckCLI->>Binary: path --version
Binary-->>CheckCLI: version output
CheckCLI-->>Resolve: matches /^openshell\s+\d+/? (true/false)
alt matches
Resolve-->>Caller: return verified path
else no match
Resolve->>Resolve: continue to next candidate
end
else not executable
Resolve->>Resolve: continue to next candidate
end
end
alt no valid candidate found
Resolve-->>Caller: return null
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 (1)
bin/lib/resolve-openshell.js (1)
54-56:⚠️ Potential issue | 🟠 Major
commandVResultoverride path skips CLI validation.Lines [54]-[56] return
opts.commandVResultwithoutcheckCLI(...), which bypasses the new binary-type guard in DI/test mode.Suggested fix
- } else if (opts.commandVResult && opts.commandVResult.startsWith("/")) { - return opts.commandVResult; + } else if (opts.commandVResult && opts.commandVResult.startsWith("/")) { + if (checkCLI(opts.commandVResult)) return opts.commandVResult; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@bin/lib/resolve-openshell.js` around lines 54 - 56, The branch that returns opts.commandVResult when it starts with "/" bypasses the CLI validation; update this path to pass opts.commandVResult through checkCLI(...) before returning so the new binary-type guard runs in DI/test mode—specifically, locate the branch handling opts.commandVResult (the code that currently returns opts.commandVResult) and replace the bare return with a return of checkCLI(opts.commandVResult) (or otherwise validate via the existing checkCLI helper) so checkCLI is applied consistently.
🤖 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/resolve-openshell.js`:
- Around line 17-22: Replace the unsafe shell interpolation of binPath
(execSync(`"${binPath}" --version`)) with a direct process execution using
execFileSync and argv (e.g., execFileSync(binPath, ['--version'], ...)) to
eliminate command injection risks and preserve encoding/timeout/stdio options;
also ensure the DI-injected path returned as commandVResult is validated by
calling the existing checkCLI function (the same validation used for the
`command -v` branch) before returning it to avoid false-positive resolutions.
---
Outside diff comments:
In `@bin/lib/resolve-openshell.js`:
- Around line 54-56: The branch that returns opts.commandVResult when it starts
with "/" bypasses the CLI validation; update this path to pass
opts.commandVResult through checkCLI(...) before returning so the new
binary-type guard runs in DI/test mode—specifically, locate the branch handling
opts.commandVResult (the code that currently returns opts.commandVResult) and
replace the bare return with a return of checkCLI(opts.commandVResult) (or
otherwise validate via the existing checkCLI helper) so checkCLI is applied
consistently.
🪄 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: Pro
Run ID: f307f723-e422-4169-be3b-13d4b5685e2d
📒 Files selected for processing (2)
bin/lib/onboard.jsbin/lib/resolve-openshell.js
| const out = execSync(`"${binPath}" --version`, { | ||
| encoding: "utf-8", | ||
| timeout: 5000, | ||
| stdio: ["ignore", "pipe", "ignore"], | ||
| }).trim(); | ||
| return /^openshell\s+\d+/.test(out); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C3 'execSync\(`"\$\{binPath\}" --version`' bin/lib/resolve-openshell.jsRepository: NVIDIA/NemoClaw
Length of output: 268
🏁 Script executed:
cat -n bin/lib/resolve-openshell.js | head -80Repository: NVIDIA/NemoClaw
Length of output: 3189
Fix command injection vulnerability and add CLI validation to DI override path.
Line 17 interpolates binPath into a shell command string, which is vulnerable if the path contains shell metacharacters. Execute the binary directly with argv using execFileSync. Additionally, lines 54–56 return the DI-injected commandVResult without validating it with checkCLI, unlike the real command -v path at line 52, which creates an inconsistency that could produce false-positive resolution in tests.
Suggested fixes
-const { execSync } = require("child_process");
+const { execSync, execFileSync } = require("child_process");At line 17:
- const out = execSync(`"${binPath}" --version`, {
+ const out = execFileSync(binPath, ["--version"], {
encoding: "utf-8",
timeout: 5000,
stdio: ["ignore", "pipe", "ignore"],
}).trim();At lines 54–56:
} else if (opts.commandVResult && opts.commandVResult.startsWith("/")) {
- return opts.commandVResult;
+ if (checkCLI(opts.commandVResult)) return opts.commandVResult;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bin/lib/resolve-openshell.js` around lines 17 - 22, Replace the unsafe shell
interpolation of binPath (execSync(`"${binPath}" --version`)) with a direct
process execution using execFileSync and argv (e.g., execFileSync(binPath,
['--version'], ...)) to eliminate command injection risks and preserve
encoding/timeout/stdio options; also ensure the DI-injected path returned as
commandVResult is validated by calling the existing checkCLI function (the same
validation used for the `command -v` branch) before returning it to avoid
false-positive resolutions.
|
✨ Thanks for submitting this fix with a detailed summary, it identifies a bug in the OpenShell binary validation and proposes a solution to prevent npm package shadowing, which could improve the stability and reliability of NemoClaw. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/onboard.js`:
- Around line 1386-1393: The fallback OpenShell manual-install message and
commands are platform- and arch-incorrect and ignore install permissions; update
the fallback logic in the OpenShell install block (where the manual
curl/tar/install lines are emitted) to mirror scripts/install-openshell.sh:
detect OS (Darwin vs Linux) and choose the correct release asset prefix, map
uname -m to release arch names (e.g., amd64→x86_64, arm64→aarch64), and check
writability of /usr/local/bin (fall back to ~/.local/bin if not writable) before
printing the manual install commands so the curl URL and target path are correct
for the user’s platform and permissions.
🪄 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: Pro
Run ID: 88dd1a39-d3d2-4628-8e02-c34777ed9d22
📒 Files selected for processing (1)
bin/lib/onboard.js
| console.error(""); | ||
| console.error(" OpenShell CLI binary failed to install."); | ||
| console.error(" You can install it manually:"); | ||
| console.error(""); | ||
| console.error(' curl -fsSL "https://github.com/NVIDIA/OpenShell/releases/latest/download/openshell-$(uname -m)-unknown-linux-musl.tar.gz" -o /tmp/openshell.tar.gz'); | ||
| console.error(" tar xzf /tmp/openshell.tar.gz -C /tmp"); | ||
| console.error(" install -m 755 /tmp/openshell /usr/local/bin/openshell"); | ||
| console.error(""); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Changed fallback block in bin/lib/onboard.js =="
nl -ba bin/lib/onboard.js | sed -n '1378,1405p'
echo
echo "== Locate install-openshell.sh =="
SCRIPT_PATH="$(fd -HI 'install-openshell\.sh$' | head -n1)"
echo "$SCRIPT_PATH"
echo
echo "== Installer script (first 260 lines) =="
nl -ba "$SCRIPT_PATH" | sed -n '1,260p'
echo
echo "== Platform/arch handling tokens in installer =="
rg -n 'darwin|linux|musl|uname -m|uname -s|aarch64|arm64|x86_64|amd64|release' "$SCRIPT_PATH"Repository: NVIDIA/NemoClaw
Length of output: 169
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Changed fallback block in bin/lib/onboard.js (lines 1378-1405) =="
cat -n bin/lib/onboard.js | sed -n '1378,1405p'
echo ""
echo "== Locate install-openshell.sh =="
SCRIPT_PATH="$(fd -HI 'install-openshell\.sh$' | head -n1)"
echo "Found: $SCRIPT_PATH"
echo ""
echo "== Installer script (first 300 lines) =="
cat -n "$SCRIPT_PATH" | sed -n '1,300p'
echo ""
echo "== Platform/arch handling tokens in installer =="
rg -n 'darwin|linux|musl|uname|aarch64|arm64|x86_64|amd64|release' "$SCRIPT_PATH" | head -30Repository: NVIDIA/NemoClaw
Length of output: 6127
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Search for installOpenshell invocation points =="
rg -n 'installOpenshell' bin/lib/onboard.js | head -20
echo ""
echo "== Check if onboard.js is platform-specific or universal =="
rg -n -A5 'function installOpenshell\|const installOpenshell' bin/lib/onboard.js | head -30
echo ""
echo "== Check for any macOS exclusions in onboarding logic =="
rg -n 'Darwin|uname -s|process.platform.*darwin|process.platform.*win32' bin/lib/onboard.js | head -20Repository: NVIDIA/NemoClaw
Length of output: 254
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Context around installOpenshell call (line 1532) =="
cat -n bin/lib/onboard.js | sed -n '1520,1545p'
echo ""
echo "== Function signature and early lines of installOpenshell =="
cat -n bin/lib/onboard.js | sed -n '1373,1395p'
echo ""
echo "== Check broader context for platform filtering =="
cat -n bin/lib/onboard.js | sed -n '1500,1550p'
echo ""
echo "== Check if process.platform is used to gate onboarding steps =="
rg -n 'process\.platform|process\.env\.OSTYPE' bin/lib/onboard.js | head -20Repository: NVIDIA/NemoClaw
Length of output: 5528
Fallback install instructions are platform-incompatible and may fail due to incorrect asset naming and missing permission checks.
The hardcoded manual install commands (lines 1390–1392) assume Linux-musl and will fail on macOS where NemoClaw onboarding is supported. The asset name uses $(uname -m) directly, but uname -m returns amd64 or arm64 on some systems, whereas OpenShell releases use x86_64 and aarch64. Additionally, the fallback always attempts installation to /usr/local/bin without checking write permissions; the installer script properly handles this by falling back to ~/.local/bin if needed.
Align the fallback logic with scripts/install-openshell.sh by:
- Detecting OS (Darwin vs. Linux) and mapping to correct asset naming
- Mapping architecture labels (
uname -m→ release-compatible names) - Checking
/usr/local/binwritability; fall back to~/.local/binif needed
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@bin/lib/onboard.js` around lines 1386 - 1393, The fallback OpenShell
manual-install message and commands are platform- and arch-incorrect and ignore
install permissions; update the fallback logic in the OpenShell install block
(where the manual curl/tar/install lines are emitted) to mirror
scripts/install-openshell.sh: detect OS (Darwin vs Linux) and choose the correct
release asset prefix, map uname -m to release arch names (e.g., amd64→x86_64,
arm64→aarch64), and check writability of /usr/local/bin (fall back to
~/.local/bin if not writable) before printing the manual install commands so the
curl URL and target path are correct for the user’s platform and permissions.
…A#506) The npmjs.org `nemoclaw` package (0.1.0) is a broken 249-byte placeholder that clobbers the real CLI. This commit: - Removes the bare `npm install -g nemoclaw` reference from docs - Adds `is_real_nemoclaw_cli()` behavioural check (mirrors PR NVIDIA#970 isOpenshellCLI pattern) — verifies `nemoclaw --version` outputs `nemoclaw v<semver>` - Adds `remove_broken_nemoclaw()` to auto-detect and uninstall any globally installed nemoclaw that fails the behavioural check - Updates `verify_nemoclaw()` to validate every candidate binary - Adds test for placeholder detection and updates all existing fake nemoclaw stubs to output the correct version format Supersedes NVIDIA#761. Fixes NVIDIA#506. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…A#506) The npmjs.org `nemoclaw` package (0.1.0) is a broken 249-byte placeholder that clobbers the real CLI. This commit: - Removes the bare `npm install -g nemoclaw` reference from docs - Adds `is_real_nemoclaw_cli()` behavioural check (mirrors PR NVIDIA#970 isOpenshellCLI pattern) — verifies `nemoclaw --version` outputs `nemoclaw v<semver>` - Adds `remove_broken_nemoclaw()` to auto-detect and uninstall any globally installed nemoclaw that fails the behavioural check - Updates `verify_nemoclaw()` to validate every candidate binary - Adds test for placeholder detection and updates all existing fake nemoclaw stubs to output the correct version format Supersedes NVIDIA#761. Fixes NVIDIA#506. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary - Remove bare `npm install -g nemoclaw` from docs — it points to a broken 249-byte placeholder on npmjs.org that only contains package.json - Add `is_real_nemoclaw_cli()` behavioural validation to `verify_nemoclaw()` — runs `nemoclaw --version` and verifies the output matches `nemoclaw v<semver>` (mirrors the `isOpenshellCLI()` pattern from PR #970) - Update `verify_nemoclaw()` to validate every candidate binary before accepting it, and auto-uninstall if the binary fails the check Supersedes #761 (docs-only fix, now stale with merge conflicts). Fixes #506 Relates to #737, #967 ## Test plan Updated all existing fake nemoclaw stubs to output `nemoclaw v<version>` format so they pass `is_real_nemoclaw_cli()`. Existing test `prints the HTTPS GitHub remediation when the binary is missing` still asserts bare `npm install -g nemoclaw` never appears in output. Signed-off-by: Dongni Yang <dongniy@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Updated CLI installation docs to show the shell-script installer (curl ... | bash) as the recommended method instead of npm global install. * **Bug Fixes** * Improved installer checks to verify the CLI binary is genuine and functional. * Added clearer recovery steps and warnings for broken or missing installations to reduce installation failures. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Dongni Yang <dongniy@nvidia.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
I attempted to port this branch across the JS→TS migration and merge the latest Please start with: git fetch origin
git merge origin/main
npx tsx scripts/ts-migration-assist.ts --base origin/main --write
npm run build:cli
npm run typecheck:cli
npm run lint
npm test |
Summary
Add
isOpenshellCLI()validation to prevent the npmopenshellgateway package from shadowing the real OpenShell CLI binary during install. Also surface a clear error with manual install steps wheninstall-openshell.shfails.Related Issue
Fixes #967
Changes
isOpenshellCLI()inbin/lib/resolve-openshell.js— runsopenshell --versionand verifies output matchesopenshell <version>command -vlookup and fallback candidate paths now verified withisOpenshellCLI()opts.checkCLIDI override for testabilityinstallOpenshell()inbin/lib/onboard.jsnow prints manual install commands on failureType of Change
Testing
isOpenshellCLI()correctly identifies the OpenShell CLI binary (openshell --version→openshell x.y.z)isOpenshellCLI()rejects the npm gateway packageinstallOpenshell()prints manual install steps on failureChecklist
General
Code Changes
Summary by CodeRabbit