fix: app-launch macros on Windows (shlex quote handling), bump to 1.1.1

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) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 22:59:17 -07:00
parent d409b64efa
commit e0df32f42b
3 changed files with 16 additions and 8 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
# Configuration and constants for MacroPad Server # Configuration and constants for MacroPad Server
VERSION = "1.1.0" VERSION = "1.1.1"
DEFAULT_PORT = 40000 DEFAULT_PORT = 40000
SETTINGS_FILE = "settings.json" SETTINGS_FILE = "settings.json"
+14 -6
View File
@@ -416,15 +416,23 @@ class MacroManager:
elif cmd_type == "app": elif cmd_type == "app":
# Launch application. # Launch application.
# SECURITY: shell=True was removed to kill shell-metacharacter # SECURITY: never use shell=True — that would allow shell-metacharacter
# injection (no ; && | $() chaining). We tokenize the command # injection (; && | $() chaining, redirection). Both branches below run
# and exec the program directly without a shell. # without a shell, so the command can only launch a program with args.
command = cmd.get("command", "") command = cmd.get("command", "")
if command: if command:
try: try:
args = shlex.split(command, posix=(os.name != "nt")) if os.name == "nt":
if args: # Windows: pass the string so CreateProcess parses it
subprocess.Popen(args) # (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: except Exception as e:
print(f"Error launching app command: {e}") print(f"Error launching app command: {e}")
+1 -1
View File
@@ -1 +1 @@
1.1.0 1.1.1