Verified on main today.
Evidence
src/orchestrator/control_flow/auto_resolver.py:712:
resolved_expr = self._replace_variables(expr_to_eval, context)
The expression string is rewritten as text, substituting variable values, before being evaluated.
Why this is wrong
This is the same defect class that produced the original eval() bug documented in core/expressions.py: substituting names as text corrupts unrelated substrings. A context variable named a rewrites the a inside max(...). The result is silent, incorrect evaluation — not an error.
It is no longer an RCE vector (names now resolve from context inside the constrained evaluator), so this is a correctness bug, not a security one. But it is the last surviving instance of the pattern.
Proposed fix
Delete _replace_variables from this path and pass context to evaluate_expression(expr, context) directly — the evaluator already resolves names from a context mapping, which is precisely what this code is hand-rolling incorrectly.
Acceptance criteria
_safe_eval performs no textual substitution
- A test proves
max(a, 10) with {"a": 3} returns 10, not a corrupted expression
- Existing
auto_resolver tests still pass
Verified on
maintoday.Evidence
src/orchestrator/control_flow/auto_resolver.py:712:The expression string is rewritten as text, substituting variable values, before being evaluated.
Why this is wrong
This is the same defect class that produced the original
eval()bug documented incore/expressions.py: substituting names as text corrupts unrelated substrings. A context variable namedarewrites theainsidemax(...). The result is silent, incorrect evaluation — not an error.It is no longer an RCE vector (names now resolve from context inside the constrained evaluator), so this is a correctness bug, not a security one. But it is the last surviving instance of the pattern.
Proposed fix
Delete
_replace_variablesfrom this path and passcontexttoevaluate_expression(expr, context)directly — the evaluator already resolves names from a context mapping, which is precisely what this code is hand-rolling incorrectly.Acceptance criteria
_safe_evalperforms no textual substitutionmax(a, 10)with{"a": 3}returns10, not a corrupted expressionauto_resolvertests still pass