From e0df32f42b1dd7537054030d495c88cba6204e74 Mon Sep 17 00:00:00 2001 From: MarcoPad Dev Date: Fri, 17 Jul 2026 22:59:17 -0700 Subject: [PATCH] fix: app-launch macros on Windows (shlex quote handling), bump to 1.1.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When shell=True was removed for security, the command was parsed with shlex.split(posix=False) on Windows, which keeps the quote characters inside the tokens — so a quoted path like "C:\Program Files\app.exe" became an argv[0] containing literal quotes and CreateProcess couldn't find it, so app macros silently did nothing. Fix: on Windows pass the command string to Popen (shell=False) and let CreateProcess parse it (handles quoted paths, still no shell/metacharacter chaining); on POSIX keep shlex.split. Verified a real launch works and shell redirection stays blocked. Co-Authored-By: Claude Opus 4.8 (1M context) --- config.py | 2 +- macro_manager.py | 20 ++++++++++++++------ version.txt | 2 +- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/config.py b/config.py index abadd7c..afaadf7 100644 --- a/config.py +++ b/config.py @@ -1,6 +1,6 @@ # Configuration and constants for MacroPad Server -VERSION = "1.1.0" +VERSION = "1.1.1" DEFAULT_PORT = 40000 SETTINGS_FILE = "settings.json" diff --git a/macro_manager.py b/macro_manager.py index 4c94b19..6c9e2c4 100644 --- a/macro_manager.py +++ b/macro_manager.py @@ -416,15 +416,23 @@ class MacroManager: elif cmd_type == "app": # Launch application. - # SECURITY: shell=True was removed to kill shell-metacharacter - # injection (no ; && | $() chaining). We tokenize the command - # and exec the program directly without a shell. + # SECURITY: never use shell=True — that would allow shell-metacharacter + # injection (; && | $() chaining, redirection). Both branches below run + # without a shell, so the command can only launch a program with args. command = cmd.get("command", "") if command: try: - args = shlex.split(command, posix=(os.name != "nt")) - if args: - subprocess.Popen(args) + if os.name == "nt": + # Windows: pass the string so CreateProcess parses it + # (correctly handling quoted paths like "C:\Program + # Files\app.exe"). shell=False means no cmd.exe, so no + # metacharacter chaining. + subprocess.Popen(command) + else: + # POSIX: split into an argv list; no shell involved. + args = shlex.split(command) + if args: + subprocess.Popen(args) except Exception as e: print(f"Error launching app command: {e}") diff --git a/version.txt b/version.txt index 1cc5f65..8cfbc90 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.0 \ No newline at end of file +1.1.1 \ No newline at end of file