dineshb's picture
Deploy DataPilot AI production Docker Space
9c1c0ef verified
Raw
History Blame Contribute Delete
1.99 kB
from __future__ import annotations
import ast
BLOCKED_NODES = (
ast.Import,
ast.ImportFrom,
ast.Global,
ast.Nonlocal,
ast.With,
ast.AsyncWith,
ast.Try,
ast.Raise,
ast.ClassDef,
ast.FunctionDef,
ast.AsyncFunctionDef,
)
BLOCKED_CALLS = {
"eval",
"exec",
"compile",
"open",
"input",
"__import__",
"breakpoint",
"getattr",
"setattr",
"delattr",
}
class UnsafeCodeError(ValueError):
pass
def validate_generated_expression(expression: str, max_characters: int = 2_000) -> ast.Expression:
"""Validate a calculation expression before it is sent to an isolated worker.
DataPilot's primary workflow does not execute LLM-generated Python. This validator
exists for an optional restricted calculation worker and accepts expressions only.
"""
if len(expression) > max_characters:
raise UnsafeCodeError("Expression exceeds the configured size limit.")
try:
tree = ast.parse(expression, mode="eval")
except SyntaxError as exc:
raise UnsafeCodeError("Expression is not valid Python.") from exc
for node in ast.walk(tree):
if isinstance(node, BLOCKED_NODES):
raise UnsafeCodeError(f"Blocked syntax: {type(node).__name__}.")
if isinstance(node, ast.Attribute) and node.attr.startswith("__"):
raise UnsafeCodeError("Dunder attribute access is blocked.")
if isinstance(node, ast.Name) and node.id.startswith("__"):
raise UnsafeCodeError("Dunder names are blocked.")
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name) and node.func.id in BLOCKED_CALLS:
raise UnsafeCodeError(f"Blocked call: {node.func.id}.")
if isinstance(node.func, ast.Attribute) and node.func.attr in BLOCKED_CALLS:
raise UnsafeCodeError(f"Blocked call: {node.func.attr}.")
return tree