From 64d2243e52c371d8712ac175274acc00b9a823e3 Mon Sep 17 00:00:00 2001 From: 11cookies11 <84768452+11cookies11@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:30:41 +0800 Subject: [PATCH] Fix unsafe attribute access in DSL evaluator --- dsl_runtime/lang/expression.py | 39 +++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/dsl_runtime/lang/expression.py b/dsl_runtime/lang/expression.py index 46f8ee1..687f98f 100644 --- a/dsl_runtime/lang/expression.py +++ b/dsl_runtime/lang/expression.py @@ -1,6 +1,7 @@ from __future__ import annotations import ast +from collections.abc import Mapping import operator import time from typing import Any, Dict @@ -28,6 +29,29 @@ ast.UAdd: operator.pos, } +_BLOCKED_ATTRS = frozenset( + { + "__class__", + "__dict__", + "__globals__", + "__mro__", + "__subclasses__", + "__bases__", + "__base__", + "__code__", + "__closure__", + "__func__", + "__self__", + "__module__", + } +) + + +def _validate_attr_name(attr: str) -> None: + """Reject private/dunder attributes and non-identifier attribute names.""" + if not attr.isidentifier() or attr.startswith("_") or attr in _BLOCKED_ATTRS: + raise ValueError(f"不允许访问属性: {attr}") + class SafeEvaluator(ast.NodeVisitor): """安全表达式求值器,支持算术/比较/逻辑/变量。""" @@ -86,8 +110,21 @@ def visit_IfExp(self, node: ast.IfExp) -> Any: # type: ignore[override] return self.visit(node.body) if self.visit(node.test) else self.visit(node.orelse) def visit_Attribute(self, node: ast.Attribute) -> Any: # type: ignore[override] + attr = node.attr + _validate_attr_name(attr) value = self.visit(node.value) - return getattr(value, node.attr) + + if isinstance(value, Mapping): + if attr in value: + return value[attr] + raise KeyError(attr) + + # Only expose data attributes that are explicitly present on an instance. + # This avoids reaching descriptors/classes such as __class__, mro, or methods. + instance_attrs = getattr(value, "__dict__", {}) + if isinstance(instance_attrs, dict) and attr in instance_attrs: + return instance_attrs[attr] + raise ValueError(f"不允许访问属性: {attr}") def visit_Subscript(self, node: ast.Subscript) -> Any: # type: ignore[override] value = self.visit(node.value)