Skip to content

Commit 0b988ed

Browse files
committed
fix(cli): replace shell-string commands with argv arrays
Closes shell-injection attack surface in the legacy CJS layer by replacing all user-controlled run() / runCapture() shell strings with the new argv-safe runArgv() / runCaptureArgv() helpers. assertSafeName() guards every user-supplied sandbox/instance/preset name before it enters any command. bin/lib/onboard.js -- all openshell/bash/brew calls -> runArgv; file copies -> fs.cpSync/fs.rmSync (no cp shell) bin/lib/nim.js -- docker pull/rm/run/stop/inspect -> runArgv/runCaptureArgv; assertSafeName guard on sandboxName bin/lib/policies.js -- openshell policy get/set -> runCaptureArgv/runArgv; assertSafeName on sandboxName and presetName; temp policy file written with mode 0o600 bin/nemoclaw.js -- setupSpark: remove inline NVIDIA_API_KEY=VALUE from sudo argv (sudo -E already inherits env); deploy: assertSafeName on instanceName; sandbox connect/status/logs/destroy -> runArgv Supersedes PRs: NVIDIA#148 (shell injection), part of NVIDIA#330 (credential leak).
1 parent 8fc752a commit 0b988ed

4 files changed

Lines changed: 157 additions & 155 deletions

File tree

bin/lib/nim.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// NIM container management — pull, start, stop, health-check NIM images.
55

6-
const { run, runCapture } = require("./runner");
6+
const { run, runArgv, runCapture, runCaptureArgv, assertSafeName } = require("./runner");
77
const nimImages = require("./nim-images.json");
88

99
function containerName(sandboxName) {
@@ -121,11 +121,12 @@ function pullNimImage(model) {
121121
process.exit(1);
122122
}
123123
console.log(` Pulling NIM image: ${image}`);
124-
run(`docker pull ${image}`);
124+
runArgv("docker", ["pull", image]);
125125
return image;
126126
}
127127

128128
function startNimContainer(sandboxName, model, port = 8000) {
129+
assertSafeName(sandboxName, "sandbox name");
129130
const name = containerName(sandboxName);
130131
const image = getImageForModel(model);
131132
if (!image) {
@@ -134,12 +135,17 @@ function startNimContainer(sandboxName, model, port = 8000) {
134135
}
135136

136137
// Stop any existing container with same name
137-
run(`docker rm -f ${name} 2>/dev/null || true`, { ignoreError: true });
138+
runArgv("docker", ["rm", "-f", name], { ignoreError: true });
138139

139140
console.log(` Starting NIM container: ${name}`);
140-
run(
141-
`docker run -d --gpus all -p ${port}:8000 --name ${name} --shm-size 16g ${image}`
142-
);
141+
runArgv("docker", [
142+
"run", "-d",
143+
"--gpus", "all",
144+
"-p", `${port}:8000`,
145+
"--name", name,
146+
"--shm-size", "16g",
147+
image,
148+
]);
143149
return name;
144150
}
145151

@@ -150,7 +156,7 @@ function waitForNimHealth(port = 8000, timeout = 300) {
150156

151157
while ((Date.now() - start) / 1000 < timeout) {
152158
try {
153-
const result = runCapture(`curl -sf http://localhost:${port}/v1/models`, {
159+
const result = runCaptureArgv("curl", ["-sf", `http://localhost:${port}/v1/models`], {
154160
ignoreError: true,
155161
});
156162
if (result) {
@@ -168,27 +174,27 @@ function waitForNimHealth(port = 8000, timeout = 300) {
168174
function stopNimContainer(sandboxName) {
169175
const name = containerName(sandboxName);
170176
console.log(` Stopping NIM container: ${name}`);
171-
run(`docker stop ${name} 2>/dev/null || true`, { ignoreError: true });
172-
run(`docker rm ${name} 2>/dev/null || true`, { ignoreError: true });
177+
runArgv("docker", ["stop", name], { ignoreError: true });
178+
runArgv("docker", ["rm", name], { ignoreError: true });
173179
}
174180

175181
function nimStatus(sandboxName) {
176182
const name = containerName(sandboxName);
177183
try {
178-
const state = runCapture(
179-
`docker inspect --format '{{.State.Status}}' ${name} 2>/dev/null`,
184+
const state = runCaptureArgv(
185+
"docker", ["inspect", "--format", "{{.State.Status}}", name],
180186
{ ignoreError: true }
181187
);
182188
if (!state) return { running: false, container: name };
183189

184190
let healthy = false;
185-
if (state === "running") {
186-
const health = runCapture(`curl -sf http://localhost:8000/v1/models 2>/dev/null`, {
191+
if (state.trim() === "running") {
192+
const health = runCaptureArgv("curl", ["-sf", "http://localhost:8000/v1/models"], {
187193
ignoreError: true,
188194
});
189195
healthy = !!health;
190196
}
191-
return { running: state === "running", healthy, container: name, state };
197+
return { running: state.trim() === "running", healthy, container: name, state: state.trim() };
192198
} catch {
193199
return { running: false, container: name };
194200
}

0 commit comments

Comments
 (0)