diff --git a/bin/lib/runner.js b/bin/lib/runner.js index 53ec88996..3ce8647ff 100644 --- a/bin/lib/runner.js +++ b/bin/lib/runner.js @@ -58,4 +58,17 @@ function runCapture(cmd, opts = {}) { } } -module.exports = { ROOT, SCRIPTS, run, runCapture, runInteractive }; +/** + * Validate a sandbox or instance name to prevent shell injection. + * Names must be 1–63 chars, alphanumeric with dots, hyphens, and underscores, + * and must start with a letter or digit. + */ +function validateName(name) { + if (!name || !/^[a-zA-Z0-9][a-zA-Z0-9._-]{0,62}$/.test(name)) { + console.error(` Invalid name: '${String(name).slice(0, 40)}'`); + console.error(" Names must be 1–63 characters: letters, digits, hyphens, underscores, dots."); + process.exit(1); + } +} + +module.exports = { ROOT, SCRIPTS, run, runCapture, runInteractive, validateName }; diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 1d108632b..d795c1780 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -7,7 +7,7 @@ const path = require("path"); const fs = require("fs"); const os = require("os"); -const { ROOT, SCRIPTS, run, runCapture, runInteractive } = require("./lib/runner"); +const { ROOT, SCRIPTS, run, runCapture, runInteractive, validateName } = require("./lib/runner"); const { ensureApiKey, ensureGithubToken, @@ -101,6 +101,7 @@ async function deploy(instanceName) { console.error(" nemoclaw deploy nemoclaw-test"); process.exit(1); } + validateName(instanceName); await ensureApiKey(); if (isRepoPrivate("NVIDIA/OpenShell")) { await ensureGithubToken(); @@ -427,6 +428,7 @@ const [cmd, ...args] = process.argv.slice(2); } // Sandbox-scoped commands: nemoclaw + validateName(cmd); const sandbox = registry.getSandbox(cmd); if (sandbox) { const action = args[0] || "connect";