Skip to content

fix(security): shell-quote sandboxName in exec and DNS proxy commands#1392

Merged
brandonpelfrey merged 3 commits intoNVIDIA:mainfrom
latenighthackathon:fix/shellquote-sandbox-name
Apr 7, 2026
Merged

fix(security): shell-quote sandboxName in exec and DNS proxy commands#1392
brandonpelfrey merged 3 commits intoNVIDIA:mainfrom
latenighthackathon:fix/shellquote-sandbox-name

Conversation

@latenighthackathon
Copy link
Copy Markdown
Contributor

@latenighthackathon latenighthackathon commented Apr 3, 2026

Summary

  • Wrap sandboxName with shellQuote() in the dashboard readiness check (openshell sandbox exec)
  • Wrap both GATEWAY_NAME and sandboxName with shellQuote() in the DNS proxy setup command
  • Add regression test that scans onboard.js for any unquoted sandboxName in run()/runCapture() calls

Problem

sandboxName is interpolated into shell command strings passed to run()/runCapture() (which execute via bash -c) without shellQuote(). The same file imports and uses shellQuote() for other user-controlled values (lines 460, 1438, 3005) but omits it here.

Without quoting:

openshell sandbox exec test; rm -rf / curl ...

With quoting:

openshell sandbox exec 'test; rm -rf /' curl ...

Test plan

  • 3 regression tests pass (vitest): quoted exec, quoted DNS proxy, no-unquoted-sandboxName scan
  • Verified shellQuote() output in Docker (node:22-slim) for semicolon, subshell, and pipe injection payloads
  • All 46 policy + security tests pass

Closes #1391

Signed-off-by: latenighthackathon latenighthackathon@users.noreply.github.com

Summary by CodeRabbit

  • Bug Fixes

    • Onboarding commands now correctly quote sandbox and gateway identifiers, preventing failures when names contain spaces or special characters and avoiding command parsing issues.
  • Tests

    • Added tests that verify onboarding shell commands include required quoting for sandbox and gateway identifiers to guard against injection and parsing regressions.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Applied shell quoting to sandboxName and GATEWAY_NAME in shell command template literals in bin/lib/onboard.js and added a Vitest (test/shellquote-sandbox.test.js) that verifies those values are quoted and scans for any remaining unquoted ${sandboxName} occurrences.

Changes

Cohort / File(s) Summary
Security Fix
bin/lib/onboard.js
Replaced unquoted ${sandboxName} and ${GATEWAY_NAME} in shell command template literals with shellQuote(sandboxName) and shellQuote(GATEWAY_NAME) for the openshell sandbox exec ... curl readiness check and the setup-dns-proxy.sh invocation.
Test Coverage
test/shellquote-sandbox.test.js
Added a Vitest suite that loads bin/lib/onboard.js, asserts shellQuote(sandboxName) appears in the openshell readiness command and that both shellQuote(GATEWAY_NAME) and shellQuote(sandboxName) appear in the DNS proxy command, and scans run/runCapture template usages to fail on unquoted ${sandboxName} occurrences (reports line numbers).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 I tucked each name in quotes so shells won’t bite,

A careful hop through lines to keep the night polite,
Tests on watchful paws, scanning every lit path,
I twitch a whisker, guard the script from wrath,
🥕 snug names, safe runs — the sandbox sleeps tight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: adding shell quoting to sandboxName in exec and DNS proxy commands for security.
Linked Issues check ✅ Passed All coding requirements from issue #1391 are met: sandboxName is wrapped with shellQuote() in the dashboard readiness check, GATEWAY_NAME and sandboxName are quoted in DNS proxy setup, and regression tests verify the fix.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #1391: shell-quoting vulnerable variables in onboard.js and adding targeted regression tests to prevent recurrence.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

🧹 Nitpick comments (1)
test/shellquote-sandbox.test.js (1)

22-35: Strengthen the scanner to handle multiline run/runCapture calls.

The current per-line check can miss unquoted ${sandboxName} when the call and template literal span different lines. A block-level scan would make this regression test reliable.

Proposed refactor
   it("does not have unquoted sandboxName in runCapture or run calls", () => {
-    const lines = src.split("\n");
     const violations = [];
-    for (let i = 0; i < lines.length; i++) {
-      const line = lines[i];
-      if (
-        (line.includes("run(") || line.includes("runCapture(")) &&
-        line.includes("${sandboxName}") &&
-        !line.includes("shellQuote(sandboxName)")
-      ) {
-        violations.push(`Line ${i + 1}: ${line.trim()}`);
-      }
-    }
+    const callRegex = /\b(run|runCapture)\(\s*`([\s\S]*?)`\s*(?:,|\))/g;
+    let match;
+    while ((match = callRegex.exec(src)) !== null) {
+      const command = match[2];
+      if (command.includes("${sandboxName}") && !command.includes("${shellQuote(sandboxName)}")) {
+        const line = src.slice(0, match.index).split("\n").length;
+        violations.push(`Line ${line}: ${match[1]}(...) contains unquoted \${sandboxName}`);
+      }
+    }
     expect(violations).toEqual([]);
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/shellquote-sandbox.test.js` around lines 22 - 35, The test currently
scans line-by-line so it misses multiline run/runCapture calls; instead treat
the file as a single string and search block-wise: iterate matches of
/\\b(run|runCapture)\\s*\\((?:[\\s\\S]*?)`([\\s\\S]*?)`[\\s\\S]*?\\)/g (or
equivalent) against src and for each match check if the captured template
contains "${sandboxName}" and does not contain "shellQuote(sandboxName)"; push a
violation with context if so and assert violations is empty. Update references
in the test to use src (the full file string), the run/runCapture regex matcher,
and the violations array logic accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@test/shellquote-sandbox.test.js`:
- Around line 22-35: The test currently scans line-by-line so it misses
multiline run/runCapture calls; instead treat the file as a single string and
search block-wise: iterate matches of
/\\b(run|runCapture)\\s*\\((?:[\\s\\S]*?)`([\\s\\S]*?)`[\\s\\S]*?\\)/g (or
equivalent) against src and for each match check if the captured template
contains "${sandboxName}" and does not contain "shellQuote(sandboxName)"; push a
violation with context if so and assert violations is empty. Update references
in the test to use src (the full file string), the run/runCapture regex matcher,
and the violations array logic accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 378a2e84-f191-436d-b392-67ab422ed1ca

📥 Commits

Reviewing files that changed from the base of the PR and between 6386bf8 and cf4ce32.

📒 Files selected for processing (2)
  • bin/lib/onboard.js
  • test/shellquote-sandbox.test.js

@wscurran wscurran added security Something isn't secure priority: high Important issue that should be resolved in the next release OpenShell Support for OpenShell, a safe, private runtime for autonomous AI agents labels Apr 3, 2026
13ernkastel added a commit to 13ernkastel/NemoClaw that referenced this pull request Apr 3, 2026
Builds on NVIDIA#1392 by validating sandboxName overrides at the createSandbox boundary, moving the dashboard readiness check onto the structured OpenShell helper path, and running the DNS proxy setup through argv-style execution instead of bash -c interpolation.

Adds regression coverage for the new runner helper, invalid sandboxNameOverride rejection, and the createSandbox command paths.

Co-authored-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Signed-off-by: 13ernkastel <LennonCMJ@live.com>
Copy link
Copy Markdown
Contributor

@ericksoa ericksoa left a comment

Choose a reason for hiding this comment

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

Clean fix — the two-line change is exactly right, and the regression test catching future unquoted usages is a nice touch.

One ask before merge: please rebase onto current main so we can run E2E cleanly. Once that's done this is ready for an approving review.

@latenighthackathon latenighthackathon force-pushed the fix/shellquote-sandbox-name branch from 01d86b9 to dc69c6a Compare April 5, 2026 23:08
@latenighthackathon
Copy link
Copy Markdown
Contributor Author

Rebased onto current main - ready for E2E. Thanks for the review @ericksoa!

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.

🧹 Nitpick comments (1)
bin/lib/onboard.js (1)

2105-2107: Consider quoting GATEWAY_NAME here for consistency.

For consistency with the defense-in-depth approach applied to the DNS proxy command (line 2513), you may want to also apply shellQuote(GATEWAY_NAME) here and at line 2167. While GATEWAY_NAME is currently a safe constant, consistent quoting reduces cognitive load and future-proofs against changes.

♻️ Suggested change
-    run(`bash "${path.join(SCRIPTS, "fix-coredns.sh")}" ${GATEWAY_NAME} 2>&1 || true`, {
+    run(`bash "${path.join(SCRIPTS, "fix-coredns.sh")}" ${shellQuote(GATEWAY_NAME)} 2>&1 || true`, {
       ignoreError: true,
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 2105 - 2107, Wrap the GATEWAY_NAME argument
to the run call in a shell-safe quoted form to match the defense-in-depth used
elsewhere: update the run invocation that calls the fix-coredns.sh script (the
call using run(`bash "${path.join(SCRIPTS, "fix-coredns.sh")}" ${GATEWAY_NAME}
2>&1 || true`, { ignoreError: true })) to pass shellQuote(GATEWAY_NAME) instead
of raw GATEWAY_NAME; apply the same change at the other occurrence noted (the
similar run call around line 2167) so both uses of GATEWAY_NAME consistently use
shellQuote before interpolating into the command string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@bin/lib/onboard.js`:
- Around line 2105-2107: Wrap the GATEWAY_NAME argument to the run call in a
shell-safe quoted form to match the defense-in-depth used elsewhere: update the
run invocation that calls the fix-coredns.sh script (the call using run(`bash
"${path.join(SCRIPTS, "fix-coredns.sh")}" ${GATEWAY_NAME} 2>&1 || true`, {
ignoreError: true })) to pass shellQuote(GATEWAY_NAME) instead of raw
GATEWAY_NAME; apply the same change at the other occurrence noted (the similar
run call around line 2167) so both uses of GATEWAY_NAME consistently use
shellQuote before interpolating into the command string.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4efba74e-6c27-424e-8324-9d9cbc824823

📥 Commits

Reviewing files that changed from the base of the PR and between dc69c6a and 8c70241.

📒 Files selected for processing (1)
  • bin/lib/onboard.js

@brandonpelfrey
Copy link
Copy Markdown
Collaborator

@latenighthackathon It looks like some of the onboarding tests are timing out. Can you merge latest main and try running locally? If you have any questions please let us know!

sandboxName and GATEWAY_NAME are interpolated into shell command
strings passed to run()/runCapture() without shellQuote(), which
is inconsistent with other user-controlled values in the same file
(lines 460, 1438, 3005).

Wrap both values with shellQuote() to prevent shell metacharacter
interpretation. Add regression test that scans onboard.js for any
unquoted sandboxName in run/runCapture calls.

Closes NVIDIA#1391

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
Line-by-line scanning misses multiline run()/runCapture() calls.
Switch to a regex that matches the full call including the template
literal, so violations spanning multiple lines are caught.

Signed-off-by: latenighthackathon <latenighthackathon@users.noreply.github.com>
@latenighthackathon latenighthackathon force-pushed the fix/shellquote-sandbox-name branch from 1f62f31 to 7f24f7b Compare April 6, 2026 23:01
@latenighthackathon
Copy link
Copy Markdown
Contributor Author

latenighthackathon commented Apr 6, 2026

@brandonpelfrey Thanks for the heads up! Rebased onto current main. CI should pick up a fresh run. Let me know if the timeout persists and I'll dig into it locally.

@brandonpelfrey brandonpelfrey self-assigned this Apr 7, 2026
@@ -0,0 +1,40 @@
// Verify shellQuote is applied to sandboxName in shell commands
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Sorry for missing this before. SPDX header is missing. Should be automatically applied by the pre-commit hook.

@brandonpelfrey
Copy link
Copy Markdown
Collaborator

@latenighthackathon looks good, just one last request re: SPDX header in the new file. Thank you again

@latenighthackathon latenighthackathon force-pushed the fix/shellquote-sandbox-name branch from 57501bc to 361bc58 Compare April 7, 2026 00:55
@latenighthackathon
Copy link
Copy Markdown
Contributor Author

@brandonpelfrey Thanks! Added the SPDX header — should be good to go now.

@brandonpelfrey brandonpelfrey merged commit 8f8b4e7 into NVIDIA:main Apr 7, 2026
3 of 4 checks passed
ericksoa added a commit that referenced this pull request Apr 7, 2026
#1566)

## Summary

- Update 6 `runCapture` mock matchers in `test/onboard.test.js` to match
the shell-quoted sandbox name format introduced by 8f8b4e7
- The security fix wrapped `sandboxName` with `shellQuote()` in exec and
DNS proxy commands, but the test mocks still matched the old unquoted
format (`sandbox exec my-assistant curl` vs `sandbox exec 'my-assistant'
curl`)
- This caused 4 CI failures: 3 timeouts (dashboard readiness loop never
matched) and 1 assertion error (`null !== 0`)

## Test plan

- [x] `npx vitest run` — all 1126 tests pass (67 files, 0 failures)
- [x] All pre-commit and pre-push hooks pass

Fixes the CI break introduced by #1392.

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Updated test matchers to correctly detect sandbox invocations that use
a quoted sandbox name in command strings.
* Simplified test logic for scanning run/runCapture templates into an
equivalent single-line expression, preserving existing violation
detection and assertions.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
@latenighthackathon latenighthackathon deleted the fix/shellquote-sandbox-name branch April 8, 2026 02:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

OpenShell Support for OpenShell, a safe, private runtime for autonomous AI agents priority: high Important issue that should be resolved in the next release security Something isn't secure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(security): sandboxName not shell-quoted in onboard.js exec and DNS proxy commands

4 participants