CVE-2026-55546: qwed-mcp Remote Code Execution via Unsafe SymPy parse_expr()
The math verification function in qwed-mcp passes user-supplied strings straight into SymPy's expression parser, which internally calls Python's eval() with no sandbox, letting any caller run…

The problem
The public function verify_math_expression() in src/qwed_mcp/engines/math_engine.py forwards both the expression and claimed_result arguments directly to sympy.parsing.sympy_parser.parse_expr(). No allowlist, AST validation, or sandboxing is applied before either call.
SymPy's parse_expr() internally calls Python's built-in eval(). Because no global_dict is set, Python injects the full built-in namespace automatically, making __import__, open, exec, and every other built-in available inside the evaluated string. An unauthenticated caller who controls either argument achieves arbitrary OS command execution as the process user, confirmed as root inside Docker.
Proof of concept
A working proof-of-concept for CVE-2026-55546 in qwed-mcp, with the exact payload below.
__import__('os').system('id > /tmp/out.txt && hostname >> /tmp/out.txt && touch /tmp/pwned')The local_dict passed to parse_expr() adds math symbols (x, y, z, pi, e) but does not set __builtins__ to an empty dict. Python's eval() contract is: when globals is omitted or lacks __builtins__, the interpreter silently injects the current module's full builtins.
That makes __import__ available, so the payload above resolves and executes before SymPy ever tries to interpret the return value as an expression.
The patch (commit 362e618) fixes both sites by adding a pre-validation step and passing global_dict={"__builtins__": {}} to every parse_expr() call. It also adds an AST walker (_validate_math_syntax) that raises ValueError on any node type, name, or function not in a strict allowlist, blocking the attack even if the __builtins__ restriction were somehow bypassed.
Root cause is CWE-94 (Improper Control of Generation of Code).
The fix
Upgrade qwed-mcp to v0.2.1. The fix in commit 362e618 adds global_dict={"__builtins__": {}} to both parse_expr() calls and introduces _validate_math_syntax(), an AST allowlist walker that rejects any expression containing non-math nodes, unknown names, or unapproved function calls before the string ever reaches parse_expr().
Related research
- high · 8.8CVE-2026-55585CVE-2026-55585: qwed Authenticated Remote Code Execution via Unsafe SymPy parse_expr()
- high · 7.8CVE-2026-68508CVE-2026-68508: hydra-core Unsafe Instantiation Code Injection
- highCVE-2026-53951CVE-2026-53951: copier Trust-Prefix Bypass via Path Traversal Leads to Arbitrary Command Execution
- high · 8.4CVE-2026-55071CVE-2026-55071: stata-mcp Stata Command Injection via Unsanitized Package Name