fix(calculator): block sympify string-eval escape without breaking safe constructors#525
Open
yonib05 wants to merge 5 commits into
Open
fix(calculator): block sympify string-eval escape without breaking safe constructors#525yonib05 wants to merge 5 commits into
yonib05 wants to merge 5 commits into
Conversation
…ator Several functions exposed in the calculator's safe locals (for example N, simplify, and solve) re-parse string arguments through SymPy's sympify, which evaluates them in a namespace separate from the restricted one used for the top-level expression. Mathematical expressions operate on numbers, symbols, and containers and never need string values, so reject string literals during AST validation. Adds regression tests covering string-literal arguments to sympify-backed functions and plain string literals in other positions.
…g system-of-equations test
Explain that rejecting string-literal arguments (a hardening measure against
SymPy re-parsing a string arg through a builtins-exposed sympify path) also
disallows string-argument constructors such as Symbol('x') and Rational('1/3');
callers should pass bare symbols and numbers. Update the module docstring, tool
docstring, and add a comment at the rejection site.
Replace the mocked test_calculator_tool_with_system_of_equations, which mocked
parse_expression and falsely implied the quoted string-list syntax works, with
unmocked tests: one exercising the supported comma-separated system syntax end
to end, and one asserting the string-list form now returns a validation error.
Permit string literals only as positional arguments to Symbol, symbols, Rational, Integer, and Float, which parse them as names or numeric literals rather than through sympify. String literals elsewhere remain rejected, keeping the sympify-backed re-parse escape (N, simplify, solve) blocked while restoring the documented constructor syntax.
Extend the literal check to bytes so the validator matches its stated intent (only positional string args to Symbol/symbols/Rational/Integer/ Float are allowed). Add tests covering keyword-position string args and bytes literals, both of which stay rejected.
Member
Author
|
/strands-ts review |
Review — LGTM ✅I reviewed the change, ran the suite, and did hands-on security probing. This is a well-scoped, correct hardening of the AST allowlist. Verification
Security probing (manual)
Why the constructor exception is safeI checked the concern that a maliciously-named symbol could later be re-parsed: Notes (non-blocking)
Test coverage is thorough (attack vectors, no-exec side-effect checks, positive math, positional-vs-keyword, bytes). No changes requested from me. |
The marker-file tests embedded tmp_path into the evaluated expression string. On Windows the path (C:\Users\...) contains backslash escapes that make ast.parse raise a unicodeescape SyntaxError, so the tests failed on Windows for the wrong reason. Use fixed payloads and mock os.getpid to prove no execution regardless of platform.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The calculator's expression evaluator validates each expression against an AST allowlist before evaluation. Some functions exposed in the safe locals (for example
N,simplify, andsolve) accept string arguments and re-parse them through SymPy'ssympify, which evaluates that string in a namespace separate from the restricted one used for the top-level expression. As a result a string-literal argument to one of those functions is not subject to the same restrictions as the surrounding expression, which allows code execution (for examplesimplify("__import__('os').system('...')")).This change rejects string literals during AST validation, except when they appear as positional arguments to the symbol/number constructors that parse their string as a plain name or numeric literal rather than through
sympify:Symbol,symbols,Rational,Integer, andFloat. These constructors do not re-evaluate their argument, so they cannot be used to escape the sandbox, and keeping them allowed avoids breaking the documentedSymbol('x')/Rational('1/3')syntax.Changes
ast.Constantnodes in_validate_expression_ast, allowing them only as positional arguments to the safe string constructors (Symbol,symbols,Rational,Integer,Float).Symbol()becomes a symbol name rather than executing.Backward compatibility
Not a breaking change. String-argument constructors that were previously valid and safe (
Symbol('x'),symbols('x y z'),Rational('1/3'),Integer('5'),Float('3.14')) continue to work. Only string literals in positions that reachsympify(for exampleN("..."),simplify("..."),solve("...")) or in other non-constructor positions are rejected.Testing
pytest tests/test_calculator.py tests/test_calculator_precision.py(81 passed)ruff format --checkandruff checkon the changed files (clean)