huggingface/smolagents · error · InterpreterError

Forbidden access to dunder attribute: {expression.attr}

Error message

Forbidden access to dunder attribute: {expression.attr}

What it means

While evaluating attribute access (obj.attr), the executor rejects any attribute whose name both starts and ends with '__' (dunder attributes). This blocks sandbox escapes like obj.__class__.__bases__[0].__subclasses__() or func.__globals__ that use dunders to reach privileged machinery.

Source

Thrown at src/smolagents/local_python_executor.py:391

    current_node = build_import_tree(authorized_imports)
    for part in import_to_check.split("."):
        if "*" in current_node:
            return True
        if part not in current_node:
            return False
        current_node = current_node[part]
    return True


def evaluate_attribute(
    expression: ast.Attribute,
    state: dict[str, Any],
    static_tools: dict[str, Callable],
    custom_tools: dict[str, Callable],
    authorized_imports: list[str],
) -> Any:
    if expression.attr.startswith("__") and expression.attr.endswith("__"):
        raise InterpreterError(f"Forbidden access to dunder attribute: {expression.attr}")
    value = evaluate_ast(expression.value, state, static_tools, custom_tools, authorized_imports)
    return getattr(value, expression.attr)


def evaluate_unaryop(
    expression: ast.UnaryOp,
    state: dict[str, Any],
    static_tools: dict[str, Callable],
    custom_tools: dict[str, Callable],
    authorized_imports: list[str],
) -> Any:
    operand = evaluate_ast(expression.operand, state, static_tools, custom_tools, authorized_imports)
    if isinstance(expression.op, ast.USub):
        return -operand
    elif isinstance(expression.op, ast.UAdd):
        return operand
    elif isinstance(expression.op, ast.Not):
        return not operand

View on GitHub (pinned to 30bb116109)

Solutions

  1. Replace dunder introspection with safe equivalents: type(x) instead of x.__class__, getattr-free public attributes, vars(x) where allowed
  2. Use isinstance checks for type detection instead of __class__ chains
  3. If you control the code, remove any __-wrapped attribute access entirely — there is no whitelist option in the executor

Example fix

# before
code = "final_answer(item.__class__.__name__)"

# after
code = "final_answer(type(item).__name__)"
Defensive patterns

Strategy: validation

Validate before calling

import ast
for node in ast.walk(ast.parse(code)):
    if isinstance(node, ast.Attribute) and node.attr.startswith('__') and node.attr.endswith('__'):
        raise ValueError(f'dunder access not allowed: {node.attr} (line {node.lineno})')

Try / catch

from smolagents.local_python_executor import InterpreterError
try:
    evaluate_python(code)
except InterpreterError as e:
    if 'dunder attribute' in str(e):
        code = code.replace('.__class__', ' type(')  # rewrite to safe equivalents

Prevention

When it happens

Trigger: Generated code accesses a dunder attribute: `x.__class__`, `f.__globals__`, `obj.__dict__`, `instance.__init__`, etc. The check fires before the value is even evaluated.

Common situations: LLM tries introspection to escape the sandbox; benign-but-fancy code uses __class__ to check types or __name__ for logging; serialization helpers reaching for __dict__.

Understand the failure class

Related errors


AI-assisted analysis of huggingface/smolagents@30bb116109 (2026-08-28). Data as JSON: /api/errors/4ff10fcdefeef400. Report an issue: GitHub.