AST 安全求值指的是把表达式/代码先解析成抽象语法树AST然后不直接eval/compile执行而是自己遍历 AST只允许白名单内的节点并按预定语义解释执行。核心目标是避免任意代码执行、沙箱逃逸和拒绝服务。一句话解析成 AST 白名单解释器 资源限制 默认拒绝。1. 为什么不直接用 evalPython 的eval、JS 的eval/new Function都极其危险pythoneval(__import__(os).system(rm -rf /))即使限制__builtins__也可能通过属性链、类继承、生成器等方式逃逸。所以安全求值不能依赖“过滤字符串”而应基于 AST 白名单。2. 安全求值的基本原则默认拒绝没显式允许的 AST 节点一律报错。不调用、不导入、不访问属性禁止Call、Import、Attribute、Subscript等。变量来自受控环境只允许简单类型如int/float/str/bool/None。运算符白名单只允许 - * / // % **、比较、布尔等并限制**。资源限制输入长度、AST 节点数、递归深度、数值大小、字符串长度、执行时间。纯函数、无副作用求值不能修改外部状态。3. Python 示例安全表达式求值只允许变量、常量、四则运算、比较、布尔、三元表达式。pythonimport ast import operator as op _BIN { ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul, ast.Div: op.truediv, ast.FloorDiv: op.floordiv, ast.Mod: op.mod, } _UNARY { ast.UAdd: op.pos, ast.USub: op.neg, ast.Not: op.not_, } _CMP { ast.Eq: op.eq, ast.NotEq: op.ne, ast.Lt: op.lt, ast.LtE: op.le, ast.Gt: op.gt, ast.GtE: op.ge, ast.In: lambda a, b: a in b, ast.NotIn: lambda a, b: a not in b, } _ALLOWED_TYPES (int, float, str, bool, type(None)) def safe_eval(expr: str, variablesNone, max_nodes1000, max_pow100): variables variables or {} tree ast.parse(expr, modeeval) if sum(1 for _ in ast.walk(tree)) max_nodes: raise ValueError(表达式过大) return _eval(tree, variables, max_pow) def _eval(node, env, max_pow): if isinstance(node, ast.Expression): return _eval(node.body, env, max_pow) if isinstance(node, ast.Constant): if isinstance(node.value, _ALLOWED_TYPES): return node.value raise ValueError(不允许的常量) if isinstance(node, ast.Name): if node.id in env and isinstance(env[node.id], _ALLOWED_TYPES): return env[node.id] raise ValueError(f未知或不允许的变量: {node.id}) if isinstance(node, ast.BinOp): left _eval(node.left, env, max_pow) right _eval(node.right, env, max_pow) if isinstance(node.op, ast.Pow): if not isinstance(right, (int, float)) or abs(right) max_pow: raise ValueError(指数过大) if isinstance(left, (int, float)) and abs(left) 1e6: raise ValueError(底数过大) return left ** right fn _BIN.get(type(node.op)) if fn is None: raise ValueError(不允许的二元运算) # 防止字符串乘法耗尽内存 if isinstance(node.op, ast.Mult): if isinstance(left, str) and isinstance(right, int) and len(left) * right 10000: raise ValueError(字符串过长) if isinstance(right, str) and isinstance(left, int) and len(right) * left 10000: raise ValueError(字符串过长) return fn(left, right) if isinstance(node, ast.UnaryOp): fn _UNARY.get(type(node.op)) if fn is None: raise ValueError(不允许的一元运算) return fn(_eval(node.operand, env, max_pow)) if isinstance(node, ast.BoolOp): if isinstance(node.op, ast.And): result True for v in node.values: result _eval(v, env, max_pow) if not result: return result return result if isinstance(node.op, ast.Or): result False for v in node.values: result _eval(v, env, max_pow) if result: return result return result raise ValueError(不允许的布尔运算) if isinstance(node, ast.Compare): left _eval(node.left, env, max_pow) for op_node, comparator in zip(node.ops, node.comparators): right _eval(comparator, env, max_pow) fn _CMP.get(type(op_node)) if fn is None or not fn(left, right): return False left right return True if isinstance(node, ast.IfExp): cond _eval(node.test, env, max_pow) return _eval(node.body if cond else node.orelse, env, max_pow) raise ValueError(f不允许的语法: {type(node).__name__})使用pythonsafe_eval(a 2 * (b - 1), {a: 3, b: 5}) # 11被禁止的语法会直接报错pythonsafe_eval(__import__(os).system(ls)) # ValueError: 不允许的语法: Call4. 如果只处理字面量Python 自带ast.literal_evalpythonimport ast ast.literal_eval({a: [1, 2, 3]})它只允许字面量安全边界较窄但不能计算变量、表达式。5. JS 中的思路不要用jseval(expr) new Function(expr)应使用acorn、espree等解析成 ESTree然后白名单解释允许Literal、Identifier、BinaryExpression、LogicalExpression、UnaryExpression、ConditionalExpression禁止CallExpression、MemberExpression、AssignmentExpression、NewExpression、FunctionExpression、ArrowFunctionExpression、TemplateLiteral、ImportExpression、ThisExpression6. 关键限制与结论AST 安全求值不是“绝对安全”只是大幅缩小攻击面。生产环境还应限制表达式长度、AST 节点数、执行时间、内存禁止Call、Attribute、Subscript、Import、Lambda、推导式、f-string不把模块、函数、对象、类实例传入变量环境必要时放到独立进程、容器、WASM 或 seccomp 沙箱中执行。结论AST 安全求值 解析 白名单遍历 纯解释器 资源限制 默认拒绝。复杂场景建议使用专用表达式引擎如 CEL、JsonLogic、simpleeval、asteval等而不是自己从零实现。