From c06c54418fb663a0ae4983f91ebabcb7de4be023 Mon Sep 17 00:00:00 2001 From: VijayKumar383 <> Date: Sat, 4 Apr 2026 13:36:32 -0400 Subject: [PATCH] fix(security): disable remote uninstall execution fallback Stop executing downloaded uninstall scripts when local uninstall.sh is missing. Require manual review/download instead and add a regression guard to prevent reintroducing remote execution fallback. Made-with: Cursor --- bin/nemoclaw.js | 35 ++++++----------------------------- test/runner.test.js | 10 ++++++++++ 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 8dd37d6bf..9c181c5d8 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -1015,34 +1015,11 @@ function uninstall(args) { exitWithSpawnResult(result); } - // Download to file before execution — prevents partial-download execution. - // Upstream URL is a rolling release so SHA-256 pinning isn't practical. - console.log(` Local uninstall script not found; falling back to ${REMOTE_UNINSTALL_URL}`); - const uninstallDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-uninstall-")); - const uninstallScript = path.join(uninstallDir, "uninstall.sh"); - let result; - let downloadFailed = false; - try { - try { - execFileSync("curl", ["-fsSL", REMOTE_UNINSTALL_URL, "-o", uninstallScript], { - stdio: "inherit", - }); - } catch { - console.error(` Failed to download uninstall script from ${REMOTE_UNINSTALL_URL}`); - downloadFailed = true; - } - if (!downloadFailed) { - result = spawnSync("bash", [uninstallScript, ...args], { - stdio: "inherit", - cwd: ROOT, - env: process.env, - }); - } - } finally { - fs.rmSync(uninstallDir, { recursive: true, force: true }); - } - if (downloadFailed) process.exit(1); - exitWithSpawnResult(result); + console.error(" Local uninstall script not found."); + console.error(" Remote uninstall fallback is disabled for security."); + console.error(` Download and review manually: ${REMOTE_UNINSTALL_URL}`); + console.error(" Then run: bash uninstall.sh [flags]"); + process.exit(1); } function showStatus() { @@ -1400,7 +1377,7 @@ function help() { nemoclaw debug --output FILE Save diagnostics tarball for GitHub issues Cleanup: - nemoclaw uninstall [flags] Run uninstall.sh (local first, curl fallback) + nemoclaw uninstall [flags] Run uninstall.sh (local only; no remote fallback) ${G}Uninstall flags:${R} --yes Skip the confirmation prompt diff --git a/test/runner.test.js b/test/runner.test.js index 53885210f..295d63a33 100644 --- a/test/runner.test.js +++ b/test/runner.test.js @@ -660,4 +660,14 @@ describe("regression guards", () => { expect(findJsViolations(src)).toEqual([]); }); }); + + describe("uninstall fallback hardening (#577)", () => { + it("bin/nemoclaw.js does not execute remote uninstall script fallback", () => { + const src = fs.readFileSync(path.join(import.meta.dirname, "..", "bin", "nemoclaw.js"), "utf-8"); + const uninstallBlock = src.split("function uninstall(args)")[1].split("function showStatus")[0]; + expect(uninstallBlock).not.toMatch(/execFileSync\("curl"/); + expect(uninstallBlock).not.toMatch(/spawnSync\("bash", \[uninstallScript/); + expect(uninstallBlock).toContain("Remote uninstall fallback is disabled for security."); + }); + }); });