QWED
v7.2.2 GitHub 60

Type to search pages, engines and advisories.

    Looking for API details?Search the docs

    GHSA-4v5r-g7f4-vvgc

    SymPy allow-list bypass in qwed_sdk local math verification lets model output reach sympify() (incomplete fix for GHSA-xmm6-8r3x-j567)

    Summary

    QWEDLocal.verify_math() asks the configured LLM for a SymPy expression and evaluates it with _safe_eval_sympy_expr(). The validator rejects string arguments to SymPy functions that internally call sympify() (which uses eval). However, _has_string_arg() inspects only the direct positional and keyword arguments of a call. A string literal placed inside a container argument is not detected. It reaches sympify() and is evaluated outside the SDK allow-list and outside the CodeGuard check.

    GHSA-xmm6-8r3x-j567 hardened the answer path (_math_answers_match → safe_parse_expr). The expression path (qwed_sdk/qwed_local.py, verify_math, call to _safe_eval_sympy_expr) still accepts this form.

    Details

    • qwed_sdk/qwed_local.py _has_string_arg() (~L328): loops over node.args / node.keywords only.
    • _is_safe_sympy_ast() permits ast.Tuple / ast.List / ast.Constant(str) anywhere in the tree.
    • _evaluate_sympy_ast() then calls e.g. sympy.simplify(<tuple containing str>), and SymPy sympifies each element.

    Benign proof (no dangerous payload used)

    factorial is not in _ALLOWED_SYMPY_FUNCS:

    Input to _safe_eval_sympy_expr Result
    sympy.simplify(factorial(5)) DisallowedExpressionError (control)
    sympy.simplify(("factorial(5)",)) accepted → (120,)
    sympy.simplify(("binomial(10, 3)",)) accepted → (120,)

    So an arbitrary string reaches SymPy's string evaluator, and names outside the allow-list execute.

    Impact

    An attacker who can influence the LLM response used by QWEDLocal.verify_math() can feed attacker-chosen text to sympify() on the user's machine. That is the same sink class as GHSA-xmm6-8r3x-j567. In addition, the local verifier can be made to report a value derived from an expression outside the allow-list.

    Patches

    • Walk the whole call subtree (ast.walk) and reject any str constant unless it is the direct argument of Symbol/symbols/Rational/Integer.
    • Reject ast.Tuple/ast.List containing non-numeric constants for every function in _SYMPY_FUNCS_REJECTING_STRING_ARGS.
    • Regression tests: the three rows above, plus nested list/tuple/keyword forms, must raise DisallowedExpressionError.

    Workarounds

    Use the server-side /verify/math (which uses safe_parse_expr) instead of QWEDLocal.verify_math() with untrusted or third-party LLM endpoints.