diff --git a/bin/nemoclaw.js b/bin/nemoclaw.js index 6b7bc61ca..c423224fd 100755 --- a/bin/nemoclaw.js +++ b/bin/nemoclaw.js @@ -904,34 +904,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() { @@ -1290,7 +1267,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 fc07849ff..6d853d695 100644 --- a/test/runner.test.js +++ b/test/runner.test.js @@ -746,4 +746,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."); + }); + }); });