"use strict"; const { SessionError } = require("./sessionManager"); // Runs fixed PowerShell scripts over an existing SSH session to Windows OpenSSH. The command line is only // a short -EncodedCommand bootstrap (valid for cmd.exe, Windows PowerShell and PowerShell 7 default shells and // far below the 8191 character cmd.exe limit). The script and one JSON argument object follow on stdin as // base64 lines, so user input is never concatenated into a command and secrets never reach a command line. // Windows has no sudo: the result reports whether the session token is elevated, and the scripts refuse // administrative work otherwise instead of trying to bypass UAC. const BOOTSTRAP = "$ErrorActionPreference='Stop';$ProgressPreference='SilentlyContinue';$i=[Console]::In;" + "$c=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i.ReadLine()));" + "$a=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i.ReadLine()));" + "& ([ScriptBlock]::Create($c)) $a"; const POWERSHELL = "powershell.exe"; function encodedCommand(source, executable = POWERSHELL) { return `${executable} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand ${Buffer.from(source, "utf16le").toString("base64")}`; } function bootstrapInput(script, args) { const encode = (text) => Buffer.from(text, "utf8").toString("base64"); return `${encode(script)}\n${encode(JSON.stringify(args))}\n`; } function cleanMessage(text, limit = 600) { return String(text || "").replace(/[\x00-\x08\x0b-\x1f\x7f]/g, " ").replace(/\s+/g, " ").trim().slice(0, limit); } // Parses the single result line; everything else (CLIXML progress, banners) is ignored. function parseWindowsOutput(stdout, stderr, code, errorPrefix = "USERS_") { const lines = String(stdout || "").split(/\r?\n/); const result = lines.find((line) => line.startsWith("CHJ_RESULT:")); if (result) { try { return JSON.parse(Buffer.from(result.slice(11), "base64").toString("utf8")); } catch { throw new SessionError(`${errorPrefix}PROTOCOL_ERROR`, "The Windows helper returned unreadable data."); } } const error = lines.find((line) => line.startsWith("CHJ_ERR:")); if (error) { const match = error.match(/^CHJ_ERR:([A-Z0-9_]{2,64}):([A-Za-z0-9+/=]*)\s*$/); if (match) throw new SessionError(`${errorPrefix}${match[1]}`, cleanMessage(Buffer.from(match[2], "base64").toString("utf8")) || match[1]); } const text = `${stdout || ""}\n${stderr || ""}`; if (code === 9009 || /is not recognized as (an internal or external command|the name of a cmdlet)|powershell(\.exe)?: (command )?not found/i.test(text)) { throw new SessionError(`${errorPrefix}POWERSHELL_UNAVAILABLE`, "Windows PowerShell (powershell.exe) could not be started over this SSH session."); } if (/language mode|ConstrainedLanguage|cannot be loaded because running scripts is disabled/i.test(text)) { throw new SessionError(`${errorPrefix}POWERSHELL_RESTRICTED`, "PowerShell is restricted by policy (Constrained Language Mode or script blocking); CH-J does not bypass it."); } throw new SessionError(`${errorPrefix}PROTOCOL_ERROR`, `The Windows helper did not return a result (exit ${code}). ${cleanMessage(stderr.replace(/<[^>]+>/g, " "), 300)}`); } class WindowsExecutor { constructor(options) { this.sessionManager = options.sessionManager; this.logger = options.logger; } // args.op selects one fixed operation inside the script; the script validates every field again. async run(sessionId, { script, args, timeoutMs = 120000, purpose, errorPrefix = "USERS_" }) { const record = this.sessionManager._requireConnected(sessionId); const channel = this.sessionManager._execStream(record, encodedCommand(BOOTSTRAP), { timeoutMs, outputLimit: 16 * 1024 * 1024, onUpdate: (controller, kind) => { if (kind === "open") { controller.write(bootstrapInput(script, args)); controller.end(); } } }); const result = await channel.done; try { return parseWindowsOutput(result.stdout, result.stderr, result.code, errorPrefix); } catch (error) { this.logger?.warn("Windows remote operation failed.", { sessionId, purpose, code: error.code }); throw error; } } } module.exports = { BOOTSTRAP, WindowsExecutor, bootstrapInput, encodedCommand, parseWindowsOutput };