fix: replace exec() with execFile() in status command to prevent command injection#762
fix: replace exec() with execFile() in status command to prevent command injection#762thebenignhacker wants to merge 2 commits intoNVIDIA:mainfrom
Conversation
…dboxStatus The sandboxStatus function passed the sandbox name through `run()` which uses `bash -c` for command execution. While shellQuote() and validateName() provide defense-in-depth, the proper fix eliminates shell interpretation entirely by using spawnSync with an argument array. Detection: npx hackmyagent secure . References: - PSIRT disclosure: tickets 6009892-6010011 - CWE-78: Improper Neutralization of Special Elements used in an OS Command
📝 WalkthroughWalkthroughReplaced shell-based execution in Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 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
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@test/runner.test.js`:
- Around line 204-213: The test currently passes without exercising
sandboxStatus(); update the test to guarantee the successful sandbox lookup and
invocation of sandboxStatus by seeding a registry/fixture or stubbing the lookup
to return a valid sandbox before calling the CLI so the code path reaches
sandboxStatus(), then tighten the static assertions to catch indirect usages
(e.g., detect template-literal interpolation even when assigned to a variable
like const cmd or passed via run(cmd)) by asserting that sandboxStatus's body
uses spawnSync("openshell", ...) and does not call run(...) with a template
interpolation anywhere in the function; do the same for the related test at the
other location to ensure the e2e case is backed by a registry fixture or a
narrower seam that provably hits the status action.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8f510470-cd02-4387-88e1-6845fb3916ef
📒 Files selected for processing (2)
bin/nemoclaw.jstest/runner.test.js
| it("sandboxStatus does not pass sandbox name through bash -c", () => { | ||
| const src = fs.readFileSync(path.join(import.meta.dirname, "..", "bin", "nemoclaw.js"), "utf-8"); | ||
| const fn = src.match(/function sandboxStatus\([\s\S]*?\n\}/); | ||
| expect(fn).toBeTruthy(); | ||
| const body = fn[0]; | ||
| // Must not use run() with template-literal sandbox name interpolation | ||
| expect(body).not.toMatch(/run\s*\(\s*`[^`]*\$\{/); | ||
| // Should use spawnSync with array args | ||
| expect(body).toMatch(/spawnSync\s*\(\s*"openshell"/); | ||
| }); |
There was a problem hiding this comment.
These regression tests can pass without ever exercising sandboxStatus().
nemoclaw.js only routes status through sandboxStatus() after the sandbox lookup succeeds on Lines 480-499; otherwise it exits through the unknown-command path on Lines 501-515. This test never seeds or asserts that precondition, so test; touch ... status can pass without touching the new spawnSync("openshell", ...) call. The static check is also narrow enough that const cmd = ...; run(cmd) would slip through.
🔧 Tighten the static guard
- // Must not use run() with template-literal sandbox name interpolation
- expect(body).not.toMatch(/run\s*\(\s*`[^`]*\$\{/);
- // Should use spawnSync with array args
- expect(body).toMatch(/spawnSync\s*\(\s*"openshell"/);
+ expect(body).not.toMatch(/spawnSync\s*\(\s*"bash"\s*,\s*\[\s*"-c"/);
+ expect(body).not.toMatch(/shell\s*:\s*true/);
+ expect(body).not.toMatch(/\brun(?:Capture|Interactive)?\s*\(/);
+ expect(body).toMatch(/spawnSync\s*\(\s*"openshell"\s*,\s*\[\s*"sandbox"\s*,\s*"get"\s*,\s*sandboxName\s*\]/);Please also back the e2e case with a registry fixture or a narrower seam so it provably hits the status action.
Also applies to: 215-233
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@test/runner.test.js` around lines 204 - 213, The test currently passes
without exercising sandboxStatus(); update the test to guarantee the successful
sandbox lookup and invocation of sandboxStatus by seeding a registry/fixture or
stubbing the lookup to return a valid sandbox before calling the CLI so the code
path reaches sandboxStatus(), then tighten the static assertions to catch
indirect usages (e.g., detect template-literal interpolation even when assigned
to a variable like const cmd or passed via run(cmd)) by asserting that
sandboxStatus's body uses spawnSync("openshell", ...) and does not call run(...)
with a template interpolation anywhere in the function; do the same for the
related test at the other location to ensure the e2e case is backed by a
registry fixture or a narrower seam that provably hits the status action.
Broaden the static analysis assertions to detect bash -c, shell: true, and any run/runCapture/runInteractive variant — not just template-literal interpolation. Addresses CodeRabbit review feedback.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
test/runner.test.js (1)
217-235:⚠️ Potential issue | 🟠 MajorThis mostly re-tests
validateName(), notsandboxStatus().Lines 123-129 already prove names containing
;are rejected, so the payload on Line 223 can fail before thestatuspath executes. That leaves Lines 230-231 able to pass without touching the newspawnSync("openshell", ...)path. Seed a known sandbox/registry fixture or stub the lookup so this case definitely enterssandboxStatus()first.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/runner.test.js` around lines 217 - 235, The test "sandboxStatus rejects shell metacharacters in sandbox name (e2e)" is re-testing validateName() instead of exercising sandboxStatus() because the malformed name is rejected earlier; update the test so the payload reaches sandboxStatus() by seeding or stubbing the lookup that resolves sandbox names (e.g., stub the registry lookup or provide a known sandbox fixture) and ensure the spawnSync call that uses openshell/`sandboxStatus()` path is invoked (refer to the test name, spawnSync invocation, and the sandboxStatus/validateName functions) so the assertion verifies sandboxStatus() rejects shell metacharacters rather than failing at validateName().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@test/runner.test.js`:
- Around line 217-235: The test "sandboxStatus rejects shell metacharacters in
sandbox name (e2e)" is re-testing validateName() instead of exercising
sandboxStatus() because the malformed name is rejected earlier; update the test
so the payload reaches sandboxStatus() by seeding or stubbing the lookup that
resolves sandbox names (e.g., stub the registry lookup or provide a known
sandbox fixture) and ensure the spawnSync call that uses
openshell/`sandboxStatus()` path is invoked (refer to the test name, spawnSync
invocation, and the sandboxStatus/validateName functions) so the assertion
verifies sandboxStatus() rejects shell metacharacters rather than failing at
validateName().
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f4aab967-119d-438b-b64e-d71059c38764
📒 Files selected for processing (1)
test/runner.test.js
Summary
sandboxStatus()passes the sandbox name throughrun()which usesspawnSync("bash", ["-c", cmd])for command execution. WhileshellQuote()and
validateName()provide defense-in-depth, the proper fix eliminatesshell interpretation entirely by using
spawnSyncwith an argument array.Change
Replace
run()bash -c call with directspawnSync("openshell", [...args])in
sandboxStatus. This removes shell interpretation from the code paththat handles sandbox names.
Detection
This vulnerability class is detectable via HackMyAgent:
References
Summary by CodeRabbit
Bug Fixes
Tests