Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bin/lib/onboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -2187,7 +2187,7 @@ async function createSandbox(
console.log(" Waiting for NemoClaw dashboard to become ready...");
for (let i = 0; i < 15; i++) {
const readyMatch = runCapture(
`openshell sandbox exec ${sandboxName} curl -sf http://localhost:18789/ 2>/dev/null || echo "no"`,
`openshell sandbox exec ${shellQuote(sandboxName)} curl -sf http://localhost:18789/ 2>/dev/null || echo "no"`,
{ ignoreError: true },
);
if (readyMatch && !readyMatch.includes("no")) {
Expand Down Expand Up @@ -2216,7 +2216,7 @@ async function createSandbox(
// sandbox namespace can resolve hostnames (fixes #626).
console.log(" Setting up sandbox DNS proxy...");
run(
`bash "${path.join(SCRIPTS, "setup-dns-proxy.sh")}" ${GATEWAY_NAME} "${sandboxName}" 2>&1 || true`,
`bash "${path.join(SCRIPTS, "setup-dns-proxy.sh")}" ${shellQuote(GATEWAY_NAME)} ${shellQuote(sandboxName)} 2>&1 || true`,
{ ignoreError: true },
);

Expand Down
43 changes: 43 additions & 0 deletions test/shellquote-sandbox.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

// 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.

import fs from "fs";
import path from "path";
import { describe, it, expect } from "vitest";

describe("sandboxName shell quoting in onboard.js", () => {
const src = fs.readFileSync(
path.join(import.meta.dirname, "..", "bin", "lib", "onboard.js"),
"utf-8",
);

it("quotes sandboxName in openshell sandbox exec command", () => {
expect(src).toMatch(/openshell sandbox exec \$\{shellQuote\(sandboxName\)\}/);
});

it("quotes sandboxName in setup-dns-proxy.sh command", () => {
expect(src).toMatch(
/setup-dns-proxy\.sh.*\$\{shellQuote\(GATEWAY_NAME\)\}.*\$\{shellQuote\(sandboxName\)\}/,
);
});

it("does not have unquoted sandboxName in runCapture or run calls", () => {
// Match run()/runCapture() calls that span multiple lines and contain
// template literals, so multiline invocations are not missed.
const callPattern = /\b(run|runCapture)\s*\(\s*`([^`]*)`/g;
const violations = [];
let match;
while ((match = callPattern.exec(src)) !== null) {
const template = match[2];
if (
template.includes("${sandboxName}") &&
!template.includes("shellQuote(sandboxName)")
) {
const line = src.slice(0, match.index).split("\n").length;
violations.push(`Line ${line}: ${match[0].slice(0, 120).trim()}`);
}
}
expect(violations).toEqual([]);
});
});
Loading