Stop Patching, Start Parsing: Why Regex Is Not Enough for Security
How to build a hardened AST sandbox that handles messy financial notations without exposing your system to arbitrary code execution.
"($100 - $80) / 20%"
If you pass this string to a standard Python eval() function, your application doesn’t just give a wrong answer—it crashes. For a developer building financial agents, this is a recurring nightmare. LLMs are remarkably expressive, but they are fundamentally unreliable at arithmetic, especially when the input involves the non-standard characters typical of accounting, like parentheses for negative losses or currency symbols.
By the end of this section, you’ll understand how to move beyond brittle eval() calls to a hardened AST-based execution model that handles real-world financial data safely.
When building these systems, the “amateur fix” is to simply expose a raw Python REPL to the LLM. It feels like the right path until it hits production. This approach introduces two critical failure modes:
-
Security Vulnerabilities: A raw execution tool is a massive surface for prompt injection. If an LLM (or a malicious user providing input to the LLM) can influence the string passed to
eval(), they can execute arbitrary code—fromos.systemcalls to exfiltrating environment variables. -
Brittle Execution: Financial data is “dirty.” An LLM might produce
"($100 - $80) / 20%"because it’s trying to be helpful with symbols. A standard Python interpreter will throw aSyntaxErrorimmediately because of the$and%characters. You end up in a cycle of the agent trying the same broken string repeatedly, wasting tokens and stalling your pipeline.
The “FDE Fix” is to implement a hardened Abstract Syntax Tree (AST) sandbox. Instead of treating the input as a raw script, we treat it as a tree of operations. This allows us to intercept the logic before it ever touches the processor.
In the AI-FDE-Playbook, I implemented a hardened AST sandbox tailored for financial QA based on three core design principles:
- Pre-processing Sanitization: We automatically strip currency symbols and normalize percentages (e.g., converting
%to/100) before the expression ever reaches the parser. This aligns the LLM’s “expressive” output with the “strict” requirements of the interpreter. - AST Node Whitelisting: We use
ast.parseto convert the string into a tree. We then traverse this tree and only allow a strict whitelist of safe binary operations, such asast.Addandast.Sub. This prevents the LLM from calling any functions, accessing attributes, or importing modules.
# Snippet from src/solver/tools.py
import ast
def safe_eval_expression(expr: str) -> float:
# 1. Clean dirty financial strings
# We handle the 'expressive' symbols LLMs favor but Python hates.
expr = expr.replace("$", "").replace("%", "/100")
# 2. Parse into AST
# mode='eval' ensures we are looking at an expression, not a statement.
node = ast.parse(expr, mode='eval').body
# 3. Whitelisted evaluation
# This is where we enforce the security boundary by recursively
# evaluating only permitted nodes.
return _eval_node(node)
# Internal helper to recursively visit whitelisted nodes
def _eval_node(node):
if isinstance(node, ast.Num): # Use ast.Constant in newer Python versions
return node.n
elif isinstance(node, ast.BinOp):
left = _eval_node(node.left)
right = _eval_node(node.right)
if isinstance(node.op, ast.Add): return left + right
if isinstance(node.op, ast.Sub): return left - right
if isinstance(node.op, ast.Mult): return left * right
if isinstance(node.op, ast.Div): return left / right
raise ValueError("Operation not permitted")
Error-as-Value Resilience
One of the most important nuances for AI engineers is treating errors as values rather than exceptions. You must never let a tool exception crash your main Agent loop. If the sandbox catches a syntax error or a forbidden operation, don’t just throw an exception. Instead, return the stack trace as a soft text string with a hint: error: invalid syntax. Hint: Use '*' for multiplication, not 'x'.
By feeding this string back into the LLM context, you transform a hard failure into a feedback loop. The model sees the specific reason it failed and can self-correct on the next iteration. This is how you build bulletproof, production-grade architectures.
Honest Limit: While AST parsing is significantly safer than eval(), it introduces a slight performance overhead and requires a more complex mapping of natural language to valid expressions. It is not a silver bullet for all math, but it is the standard for secure execution.\n
Dive into the open-source implementation at AI-FDE-Playbook!