Skip to content

fix: validate openshell binary to prevent npm package shadowing#970

Open
SaiSharan2005 wants to merge 1 commit intoNVIDIA:mainfrom
SaiSharan2005:fix/openshell-binary-detection
Open

fix: validate openshell binary to prevent npm package shadowing#970
SaiSharan2005 wants to merge 1 commit intoNVIDIA:mainfrom
SaiSharan2005:fix/openshell-binary-detection

Conversation

@SaiSharan2005
Copy link
Copy Markdown

@SaiSharan2005 SaiSharan2005 commented Mar 26, 2026

Summary

Add isOpenshellCLI() validation to prevent the npm openshell gateway package from shadowing the real OpenShell CLI binary during install. Also surface a clear error with manual install steps when install-openshell.sh fails.

Related Issue

Fixes #967

Changes

  • Added isOpenshellCLI() in bin/lib/resolve-openshell.js — runs openshell --version and verifies output matches openshell <version>
  • Both command -v lookup and fallback candidate paths now verified with isOpenshellCLI()
  • Added opts.checkCLI DI override for testability
  • installOpenshell() in bin/lib/onboard.js now prints manual install commands on failure

Type of Change

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

Testing

  • Verified isOpenshellCLI() correctly identifies the OpenShell CLI binary (openshell --versionopenshell x.y.z)
  • Verified isOpenshellCLI() rejects the npm gateway package
  • Verified installOpenshell() prints manual install steps on failure

Checklist

General

Code Changes

  • No secrets, API keys, or credentials committed.

Summary by CodeRabbit

  • Improvements
    • Enhanced the OpenShell CLI installation failure experience by providing users with detailed troubleshooting guidance and comprehensive manual installation instructions when the automated installation process encounters errors or failures.
    • Added validation checks to verify that resolved OpenShell CLI binaries are legitimate, authentic, and functional before they are executed, improving overall system reliability and security.

- 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
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Walkthrough

Enhanced OpenShell CLI installation failure messaging with manual recovery instructions and added CLI version verification to resolve-openshell to ensure the correct Rust binary is detected, preventing npm package shadowing on PATH.

Changes

Cohort / File(s) Summary
Installation Failure Messaging
bin/lib/onboard.js
Expanded installOpenshell() failure path to print diagnostic message and explicit manual-install commands (curl download, extraction, installation to /usr/local/bin/openshell) when OpenShell CLI binary installation fails.
CLI Verification & Resolution
bin/lib/resolve-openshell.js
Added new exported isOpenshellCLI(binPath) helper that verifies binary by running --version and matching /^openshell\s+\d+/. Integrated verification into resolveOpenshell() to validate each candidate with both executable check and CLI version check before returning. Extended testing surface with opts.checkCLI override.

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 if executable
            Resolve->>CheckCLI: opts.checkCLI || isOpenshellCLI(path)
            CheckCLI->>Binary: path --version
            Binary-->>CheckCLI: version output
            CheckCLI-->>CheckCLI: match /^openshell\s+\d+/?
            CheckCLI-->>Resolve: is valid CLI?
            
            alt if valid CLI
                Resolve-->>Caller: return verified path
            else if invalid CLI
                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
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 The binary was hiding, obscured by the npm,
But now we peek inside with a version-check whim!
True OpenShell found, no package can fool,
Installation tells all when it breaks—what a tool!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: validate openshell binary to prevent npm package shadowing' accurately and specifically describes the main change: adding validation to ensure the resolved OpenShell CLI binary is the real Rust CLI and not the npm package.
Linked Issues check ✅ Passed The pull request meets the primary objectives from #967: isOpenshellCLI() validates the resolved binary against the npm gateway package [967], and installOpenshell() surfaces clear error messages with manual install steps when install-openshell.sh fails [967].
Out of Scope Changes check ✅ Passed All changes are directly scoped to the linked issue #967: validation logic in resolve-openshell.js prevents npm package shadowing, and error handling in onboard.js surfaces installation failures as required.

✏️ 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.

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

commandVResult override path skips CLI validation.

Lines [54]-[56] return opts.commandVResult without checkCLI(...), 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

📥 Commits

Reviewing files that changed from the base of the PR and between f0f53e4 and 9785e4f.

📒 Files selected for processing (2)
  • bin/lib/onboard.js
  • bin/lib/resolve-openshell.js

Comment on lines +17 to +22
const out = execSync(`"${binPath}" --version`, {
encoding: "utf-8",
timeout: 5000,
stdio: ["ignore", "pipe", "ignore"],
}).trim();
return /^openshell\s+\d+/.test(out);
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.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C3 'execSync\(`"\$\{binPath\}" --version`' bin/lib/resolve-openshell.js

Repository: NVIDIA/NemoClaw

Length of output: 268


🏁 Script executed:

cat -n bin/lib/resolve-openshell.js | head -80

Repository: 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.

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.

install.sh fails at step 3/7 — OpenShell CLI binary not installed, npm package shadows it

1 participant