fix(math): restrict sympy expression parsing#200
Conversation
sympy parse_expr uses Python code execution internally and without restrictions on local_dict/global_dict allows arbitrary code execution through crafted math expression strings. Add safe_parse_expr wrapper that: - Validates input against a denylist of dangerous patterns (dunder attrs, import, exec, os, subprocess, etc.) - Restricts the namespace to only known-safe sympy objects (math functions, constants, and common symbolic variables) - Strips Python builtins from the global namespace - Enforces a maximum expression length Replace all bare parse_expr calls across the codebase: - src/qwed_new/api/main.py (/verify/math endpoint) - src/qwed_new/core/verifier.py (VerificationEngine) - src/qwed_new/core/batch.py (batch math verification) - src/qwed_new/core/validator.py (SemanticValidator) Signed-off-by: Sebastion <sebastion@sebastion.dev>
Greptile SummaryThis PR mitigates CWE-95 (code injection via SymPy's
Confidence Score: 5/5Safe to merge; the CWE-95 fix is correctly applied across all four affected call sites with well-structured defense-in-depth. All bare safe_parser.py — the denylist in Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User-supplied expression string] --> B[safe_parse_expr]
B --> C{Type check\nstr?}
C -->|No| ERR1[SafeParserError]
C -->|Yes| D{Empty or\ntoo long?}
D -->|Yes| ERR2[SafeParserError]
D -->|No| E{Denylist\nregex match?}
E -->|Match| ERR3[SafeParserError\ndisallowed construct]
E -->|No match| F[AST depth check]
F -->|Exceeds limit| ERR4[SafeParserError]
F -->|OK| G[Build safe local_dict\nSymPy symbols + functions only]
G --> H[global_dict = copy of template\n__builtins__: empty dict]
H --> I[parse_expr with\nlocal_dict + global_dict]
I --> J{Result is\nsympy.Expr?}
J -->|No| ERR5[SafeParserError]
J -->|Yes| K{SymPy tree\ndepth OK?}
K -->|Exceeds limit| ERR6[SafeParserError]
K -->|OK| L[Return sympy.Expr]
Reviews (8): Last reviewed commit: "fix: validate extra_symbols keys against..." | Re-trigger Greptile |
|
Thanks for the report, Sebastion (@sebastiondev). We independently audited the codebase against your claims and verified the vulnerability. Vulnerability Status: ConfirmedWe reproduced the exploit on our current SymPy 1.14.0 installation using a from sympy.parsing.sympy_parser import parse_expr
parse_expr('__import__(chr(111)+chr(115)).system(chr(105)+chr(100))')
# Executes os.system("id") on the hostAll 17 production The fix direction is correct and aligns with QWED's fail-closed philosophy. However, we need the following changes before we can merge: Required Changes1. Multi-letter symbolic variable support
2. Shared mutable The module-level dict is passed by reference to every 3. Validate
If you're able to address these, we're happy to re-review. If not, we may implement the fix internally based on your approach — the vulnerability is real and we want to close it promptly. Either way, thank you for the responsible report. |
Merging this PR will improve performance by 54.57%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_bench_math_algebraic_expression |
1.8 ms | 1.1 ms | +56.65% |
| ⚡ | test_bench_math_simple_arithmetic |
1.8 ms | 1.2 ms | +52.52% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing sebastiondev:fix/cwe95-main-sympy-9383 (70f0978) with main (06801f6)
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…alidate variable names
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR hardens math expression parsing across the verification system by introducing a denylist-based safe parser that replaces SymPy's unrestricted ChangesSafe Expression Parser Security Hardening
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Thank you Rahul Dass (@rahuldass19) for the thorough review and for independently confirming the vulnerability — really appreciated. All three requested changes have been addressed in bbf9ade: 1. Multi-letter symbolic variable support ✅
2. Shared mutable
|
There was a problem hiding this comment.
🧹 Nitpick comments (3)
tests/security/test_safe_parser.py (1)
126-138: ⚡ Quick winStrengthen the global dict isolation test.
The current test only verifies that multiple calls succeed without exceptions, but it doesn't validate the actual isolation mechanism. It doesn't prove that copying
global_dictper call prevents cross-contamination of SymPy transformations or symbols.Consider testing a scenario where state leakage would actually occur if the dict were shared:
- Parse an expression with
extra_symbolscontaining a custom symbol- Parse a second expression referencing that symbol name without passing
extra_symbols- Verify the second parse fails (proving the symbol didn't leak)
Alternatively, if such leakage is difficult to trigger in practice, document why the current test provides sufficient coverage.
♻️ Example strengthened test
def test_global_dict_not_shared_between_calls(self) -> None: - """Parse two different expressions and confirm no cross-contamination.""" - safe_parse_expr("x + 1") - safe_parse_expr("alpha + beta") - # If global_dict were shared mutably, SymPy transformations could - # leak symbols from one call into another. The shallow-copy fix - # prevents this. We just verify no exception is raised and both - # parse independently. - result = safe_parse_expr("y + 2") - assert str(result) == "y + 2" + """Verify that extra_symbols from one call don't leak into another.""" + from sympy import Symbol + + # First call with a custom symbol + safe_parse_expr("custom_var + 1", extra_symbols={"custom_var": Symbol("custom_var")}) + + # Second call should NOT have access to custom_var + with pytest.raises(ValueError): + safe_parse_expr("custom_var + 2") # Should fail: custom_var not in default namespace + + # Verify normal expressions still work + result = safe_parse_expr("x + 2") + assert str(result) == "x + 2"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/security/test_safe_parser.py` around lines 126 - 138, Update the test for global dict isolation to actively demonstrate leakage wouldn't occur: call safe_parse_expr once with extra_symbols including a custom symbol (e.g., safe_parse_expr("leak + 1", extra_symbols={"leak": Symbol("leak")})) and assert that parsing that expression succeeds and produces the expected result, then call safe_parse_expr again for the same symbol name without providing extra_symbols and assert that this second call raises the appropriate error (e.g., NameError or SympifyError) or does not return a Symbol — this proves that the per-call copy of _SAFE_GLOBAL_DICT prevents the first call's symbol from leaking into subsequent calls; update TestSafeParseExprGlobalDictIsolation.test_global_dict_not_shared_between_calls accordingly, keeping references to safe_parse_expr and _SAFE_GLOBAL_DICT to locate the implementation if needed.src/qwed_new/core/safe_parser.py (2)
198-203: 💤 Low valueIncluding
Symbolin local_dict is necessary but warrants documentation.
Symbolis needed because SymPy's transformations may emit code referencingSymbol(). However, this allows users to create symbols with arbitrary names via expressions likeSymbol('anything'). The denylist protects against dangerous attribute accesses on the resulting symbol, but consider adding a comment documenting this security boundary.# Sympy internal types emitted by standard_transformations "Integer": Integer, "Float": Float, "Rational": Rational, + # Symbol is required by transformations; denylist prevents dangerous + # attribute access on created symbols. "Symbol": Symbol,🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/qwed_new/core/safe_parser.py` around lines 198 - 203, Add a short in-source comment by the local_dict entry that maps "Symbol" (the dictionary that includes Integer, Float, Rational, Symbol used with standard_transformations) explaining why Symbol is required (SymPy transformations may emit Symbol()), the security boundary it creates (users can call Symbol('name') to create arbitrary symbol names) and that the existing denylist/attribute-access protections are relied on to mitigate risks; keep the comment concise and reference the local_dict and Symbol so future readers know this is intentional and what to review if tightening is needed.
40-66: 💤 Low valueDenylist-based filtering is a reasonable mitigation but inherently weaker than allowlist.
The pattern set covers key attack vectors (dunders, code execution primitives, system modules). However, denylist approaches can be bypassed by novel attack vectors or encoding tricks. Since SymPy's
parse_exprultimately useseval(), consider adding defense-in-depth measures in future iterations:
- AST complexity/depth limits
- Symbolic expression tree validation post-parse
For this PR, the combination of denylist + restricted namespace + stripped builtins is a substantial improvement and aligns with fail-closed philosophy.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/qwed_new/core/safe_parser.py` around lines 40 - 66, The denylist in _DANGEROUS_PATTERNS is useful but brittle; add defense‑in‑depth by (1) enforcing an AST complexity/depth limit on parsed expressions (e.g., run ast.parse on the input and reject if node count/depth exceeds a safe threshold) before calling SymPy's parse_expr, and (2) validating the resulting SymPy expression tree after parse_expr to ensure it contains only allowed node/symbol types (reject Function, Call, or unexpected Name nodes). Update the code paths that call parse_expr/use _DANGEROUS_PATTERNS to perform the pre-parse AST checks and the post-parse symbolic validation (leave the denylist in place as an additional filter).Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/qwed_new/core/safe_parser.py`:
- Around line 198-203: Add a short in-source comment by the local_dict entry
that maps "Symbol" (the dictionary that includes Integer, Float, Rational,
Symbol used with standard_transformations) explaining why Symbol is required
(SymPy transformations may emit Symbol()), the security boundary it creates
(users can call Symbol('name') to create arbitrary symbol names) and that the
existing denylist/attribute-access protections are relied on to mitigate risks;
keep the comment concise and reference the local_dict and Symbol so future
readers know this is intentional and what to review if tightening is needed.
- Around line 40-66: The denylist in _DANGEROUS_PATTERNS is useful but brittle;
add defense‑in‑depth by (1) enforcing an AST complexity/depth limit on parsed
expressions (e.g., run ast.parse on the input and reject if node count/depth
exceeds a safe threshold) before calling SymPy's parse_expr, and (2) validating
the resulting SymPy expression tree after parse_expr to ensure it contains only
allowed node/symbol types (reject Function, Call, or unexpected Name nodes).
Update the code paths that call parse_expr/use _DANGEROUS_PATTERNS to perform
the pre-parse AST checks and the post-parse symbolic validation (leave the
denylist in place as an additional filter).
In `@tests/security/test_safe_parser.py`:
- Around line 126-138: Update the test for global dict isolation to actively
demonstrate leakage wouldn't occur: call safe_parse_expr once with extra_symbols
including a custom symbol (e.g., safe_parse_expr("leak + 1",
extra_symbols={"leak": Symbol("leak")})) and assert that parsing that expression
succeeds and produces the expected result, then call safe_parse_expr again for
the same symbol name without providing extra_symbols and assert that this second
call raises the appropriate error (e.g., NameError or SympifyError) or does not
return a Symbol — this proves that the per-call copy of _SAFE_GLOBAL_DICT
prevents the first call's symbol from leaking into subsequent calls; update
TestSafeParseExprGlobalDictIsolation.test_global_dict_not_shared_between_calls
accordingly, keeping references to safe_parse_expr and _SAFE_GLOBAL_DICT to
locate the implementation if needed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b93953c5-3fb4-47f0-93ea-c266f7c3684a
📒 Files selected for processing (7)
src/qwed_new/api/main.pysrc/qwed_new/core/batch.pysrc/qwed_new/core/safe_parser.pysrc/qwed_new/core/validator.pysrc/qwed_new/core/verifier.pytests/security/test_safe_parser.pytests/test_api_exceptions.py
- Add get_safe_symbol() to return consistent Symbol objects matching safe_parse_expr namespace (fixes 'n' variable mismatch in calculus) - Add AST depth limit and post-parse sympy.Basic type validation - Add comment documenting Symbol in allow-list boundary - Strengthen global dict isolation test - Update verifier.py calculus methods to use get_safe_symbol
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/qwed_new/core/safe_parser.py`:
- Around line 127-131: The extra_symbols processing loop currently silently
ignores non-SymPy values instead of failing closed. In the loop over
extra_symbols.items(), modify the code so that when a value is not an instance
of Symbol or sympy.Basic, raise an appropriate exception (such as ValueError or
TypeError) with a clear error message indicating which key contains an invalid
value. This ensures caller mistakes are surfaced immediately rather than
silently suppressed, maintaining fail-closed behavior at this security boundary.
- Around line 178-179: The exception handler in the except block is raising
ValueError with raw, unsanitized parser exception text. Replace this ValueError
with SafeParserError to provide a consistent boundary exception type for parser
failures. Sanitize or generalize the error message instead of directly including
the raw exception details (the exc variable). This ensures sensitive information
is not exposed and maintains a deterministic, fail-closed error boundary as per
the coding guidelines.
- Around line 56-61: The _check_ast_depth function has a fail-open vulnerability
where SyntaxError exceptions (which occur for implicit multiplication syntax
like "sin sin x") cause early return without depth checking. Instead of
returning on SyntaxError, attempt to parse the expression using SymPy's
implicit_multiplication_application transformation and validate the resulting
AST depth. Additionally, in the extra_symbols validation (around lines 127-131),
replace the silent filtering of non-sympy.Basic values with explicit error
raising to enforce fail-closed behavior. Finally, at line 179, replace the
generic exception wrapping that includes raw exception text with a sanitized
error message that does not leak parser internals, following QWED error message
guidelines.
In `@tests/security/test_safe_parser.py`:
- Around line 189-190: The test for deep expression parsing is too permissive by
allowing both SafeParserError and ValueError to be raised. To enforce
fail-closed behavior explicitly, modify the pytest.raises call for the
safe_parse_expr test to only expect SafeParserError, removing ValueError from
the exception tuple. This ensures that if a ValueError is raised instead of the
expected SafeParserError when deep_expr is parsed, the test will fail and catch
boundary regressions.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 1f7a3e8c-63f5-4334-95dc-15178e0d6ff7
📒 Files selected for processing (4)
.coveragesrc/qwed_new/core/safe_parser.pysrc/qwed_new/core/verifier.pytests/security/test_safe_parser.py
- Remove unused validate_variable_name import from verifier.py (CodeQL) - Fail-closed on SyntaxError in _check_ast_depth (Sentry HIGH) - Raise SafeParserError for invalid extra_symbols (CodeRabbit) - Use SafeParserError with sanitized message (CodeRabbit) - Tighten tests to only expect SafeParserError
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/qwed_new/core/safe_parser.py`:
- Around line 61-63: The SafeParserError being raised in the SyntaxError handler
is missing exception chaining suppression. Add `from None` to the raise
SafeParserError statement to suppress the exception chain, matching the pattern
used at line 185 and preventing parser internals from leaking. This will make
the code consistent with other error paths in the file and satisfy the Ruff B904
rule.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d8c4f92e-0c23-44e6-b792-546a3ea4fac3
📒 Files selected for processing (3)
src/qwed_new/core/safe_parser.pysrc/qwed_new/core/verifier.pytests/security/test_safe_parser.py
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/security/test_safe_parser.py
- Skip pre-parse AST depth for implicit-mult syntax (fixes 2x regression) - Add post-parse SymPy expression tree depth check (catches all syntax)
- e is commonly used as a free symbol; only uppercase E maps to Euler's number - Removes silent semantic change from original parse_expr behavior
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/qwed_new/core/safe_parser.py (1)
64-65:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFail closed when the pre-parse validator rejects the input.
Lines 64-65 swallow
SyntaxErrorand continue intoparse_expr(). Even with_sympy_tree_depth, that is still a syntax-dependent fallback: one class of inputs is validated before parsing, while another proceeds after a failed verification step. At this boundary, QWED rules require a sanitized failure instead of continuing on a later “safe enough” check.Suggested minimal hardening
try: tree = ast.parse(expression, mode="eval") except SyntaxError: - return + raise SafeParserError( + "Expression uses syntax that cannot be validated for safety" + ) from NoneAs per coding guidelines, "Fail-closed security boundary: if verification/parsing fails, block and do not continue execution on “safe enough”" and "No fallback execution: do not add any backup parsing/execution path."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/qwed_new/core/safe_parser.py` around lines 64 - 65, The exception handler for SyntaxError at lines 64-65 silently returns instead of failing closed as required by QWED security guidelines. When the pre-parse validator rejects the input and raises a SyntaxError, the code must not continue execution or allow fallback parsing. Replace the silent return statement in the except SyntaxError block with an exception that propagates the validation failure and prevents any further execution or fallback parsing attempts through parse_expr().Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/qwed_new/core/safe_parser.py`:
- Around line 95-106: The _validate_sympy_result function currently accepts any
sympy.Basic type, which includes relational expressions like `x < y` that are
not arithmetic expressions. This causes TypeError downstream when code attempts
arithmetic operations on these relationals. Change the isinstance check in
_validate_sympy_result to validate against sympy.Expr instead of sympy.Basic to
restrict the validation to arithmetic expressions only and enforce the
arithmetic-only contract at the parser boundary.
---
Duplicate comments:
In `@src/qwed_new/core/safe_parser.py`:
- Around line 64-65: The exception handler for SyntaxError at lines 64-65
silently returns instead of failing closed as required by QWED security
guidelines. When the pre-parse validator rejects the input and raises a
SyntaxError, the code must not continue execution or allow fallback parsing.
Replace the silent return statement in the except SyntaxError block with an
exception that propagates the validation failure and prevents any further
execution or fallback parsing attempts through parse_expr().
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ab0955f6-361e-4142-aad9-47325ff675c6
📒 Files selected for processing (1)
src/qwed_new/core/safe_parser.py
- Preserve original parse error messages for domain error detection - Check sympy.Expr instead of sympy.Basic to reject relationals (x < y)
- Keys must be strings - Keys checked against _DENYLIST_PATTERN before being used
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
|
Hey Sebastion (@sebastiondev), thanks again for the report and the initial fix proposal. The core direction was correct, and we built directly on top of it. Given the severity of the issue (confirmed RCE affecting verification paths), we chose to complete the remaining hardening work directly in-tree so we could review, test, release, and coordinate disclosure without delay. The final remediation includes additional fixes and tests covering:
Your report and remediation proposal were instrumental in identifying and closing the vulnerability family, and we’ll ensure appropriate credit is reflected in the advisory and release notes. Appreciate the contribution and the responsible disclosure. |
6066b68
into
QWED-AI:main
|
Appreciate the review — thanks for getting it merged. |
|
Thanks for the merge. Glad it landed cleanly. |
QWED Enforcement Checklist
eval/execusage introducedSummary
This change fixes a CWE-95 code injection issue in math expression verification. User-controlled expression strings from the
POST /verify/mathendpoint,VerificationEnginemath helpers,SemanticValidator, and math batch verification were passed directly to SymPyparse_expr(). SymPy's parser evaluates generated Python code internally, so calling it without restricted namespaces allows a tenant-supplied expression to reach Python execution primitives.The affected public endpoint is authenticated with
get_current_tenant, but that is not a sufficient mitigation for a multi-tenant API: any tenant with a valid API key can submit math verification input, while a valid API key should not grant arbitrary code execution on the service host.The fix adds
qwed_new.core.safe_parser.safe_parse_expr()and replaces the bare parser calls in the affected math paths. The wrapper rejects dangerous constructs before parsing, supplies an allow-listed local namespace containing expected SymPy math symbols/functions only, removes Python builtins from the parser globals, and enforces basic input validation including empty/non-string checks and a length limit. This preserves deterministic symbolic verification while narrowing the parser boundary to math expressions.Vulnerability details
src/qwed_new/api/main.py,verify_math, requestexpression->parse_expr()src/qwed_new/core/verifier.py,VerificationEnginemath/identity/derivative/integral/limit helpers ->parse_expr()src/qwed_new/core/batch.py, math batch itemquery->parse_expr()src/qwed_new/core/validator.py, semantic math validation ->parse_expr()Proof of Concept
The underlying unsafe behavior can be reproduced against the previous implementation with the same sink used by the affected code paths:
For the API path, a tenant with a valid key could send the malicious expression to the existing math verification route:
After this patch, the same payload is rejected by
safe_parse_expr()before SymPy evaluation. Normal expressions such as2+2,x**2 + 2*x + 1,sin(x),sqrt(16), andfactorial(5)still parse successfully.Validation
I validated the changed parser boundary and the endpoint exception path with:
Results:
tests/security/test_safe_parser.py: 36 passedtests/test_api_exceptions.py::test_verify_math_exception_handling: 1 passedI also checked for remaining direct production uses of SymPy
parse_expr()outside the new wrapper. The remainingsympy.parse_exprtext is in documentation/example text, not an active parser call.A broader local run of
tests/security/test_safe_parser.py tests/test_api_exceptions.pypassed the new security tests but exposed an unrelated Python 3.14 compatibility failure intests/test_api_exceptions.py::test_verify_stats_exception_handlingcaused byast.Numremoval while importingstats_verifier.py. That failure is outside this parser change.Security analysis
The exploit works because
parse_expr()is not just a passive math grammar parser: it transforms input and evaluates Python code. Without a constrainedglobal_dictandlocal_dict, identifiers such as__import__can resolve to Python runtime capabilities and invoke OS commands. The request body fieldexpressionand the corresponding engine/batch inputs are controlled by the caller, so the attacker controls the string that reaches the evaluation sink.This patch mitigates the issue in depth. The denylist blocks common Python execution, reflection, import, filesystem, and process-spawning constructs before parsing. The
__builtins__removal prevents fallback access to builtins during evaluation. The allow-listed local dictionary limits successful names to expected mathematical symbols, constants, and SymPy functions. The length/type checks also keep malformed or oversized inputs fail-closed instead of reaching the parser.Before submitting, I attempted to disprove the issue by checking whether authentication or routing protections would remove exploitability. The route does require
get_current_tenant, but the project exposes tenant API keys for verification workloads; that precondition does not already grant code execution. I also checked for existing input validation before the parser and did not find a sanitizer that would block the payload before it reachedparse_expr().Notes
This remains compliant with QWED's deterministic verification boundary: the patch does not add fallback execution, retries, or LLM trust. It tightens the symbolic math parser so verification continues to run through SymPy, but only with a constrained math namespace.
Submitted by Sebastion — autonomous open-source security research from Foundation Machines. Free for public repos via the Sebastion AI GitHub App.
Summary by CodeRabbit