diff --git a/server/utils/plugin-process-manager.js b/server/utils/plugin-process-manager.js index d5fa493e..f7e325af 100644 --- a/server/utils/plugin-process-manager.js +++ b/server/utils/plugin-process-manager.js @@ -7,6 +7,41 @@ const runningPlugins = new Map(); // Map> — in-flight start operations const startingPlugins = new Map(); +/** + * Build the environment handed to a plugin server subprocess. + * + * Intentionally minimal: only non-secret essentials, never the host's full + * environment. On Windows a handful of system variables are required for any + * child to bootstrap (Node itself, and any Python or CLI a plugin shells out + * to). Without APPDATA a `pip install --user` tool cannot locate its + * site-packages and fails to import; SystemRoot, PATHEXT and TEMP are needed to + * resolve system DLLs, executable extensions and a temp directory. None of + * these carry secrets, so the ones that are set get passed straight through. + */ +function buildPluginEnv(name) { + const env = { + PATH: process.env.PATH, + HOME: process.env.HOME, + NODE_ENV: process.env.NODE_ENV || 'production', + PLUGIN_NAME: name, + }; + + if (process.platform === 'win32') { + const WINDOWS_ESSENTIALS = [ + 'SystemRoot', 'windir', 'SystemDrive', + 'USERPROFILE', 'APPDATA', 'LOCALAPPDATA', + 'TEMP', 'TMP', 'PATHEXT', + ]; + for (const key of WINDOWS_ESSENTIALS) { + if (process.env[key] !== undefined) { + env[key] = process.env[key]; + } + } + } + + return env; +} + /** * Start a plugin's server subprocess. * The plugin's server entry must print a JSON line with { ready: true, port: } @@ -26,15 +61,9 @@ export function startPluginServer(name, pluginDir, serverEntry) { const serverPath = path.join(pluginDir, serverEntry); - // Restricted env — only essentials, no host secrets const pluginProcess = spawn('node', [serverPath], { cwd: pluginDir, - env: { - PATH: process.env.PATH, - HOME: process.env.HOME, - NODE_ENV: process.env.NODE_ENV || 'production', - PLUGIN_NAME: name, - }, + env: buildPluginEnv(name), stdio: ['ignore', 'pipe', 'pipe'], });