mirror of
https://github.com/calesthio/OpenMontage.git
synced 2026-08-16 21:51:24 +08:00
fix(math_animate): block all reflection dunders, not an enumerated set
The prior dunder denylist was still bypassable via print.__self__ (the builtins module) -> .open(...), reachable with no import and no bare open/__builtins__/ getattr name. Enumerating dangerous dunders is whack-a-mole, so block ALL dunder attribute access generically and allow only the tiny set legitimate scenes need (super().__init__, occasional Type.__name__). This closes the print.__self__ / .__class__ / .__globals__ introspection-escape class at once. Static analysis still has a ceiling — a real subprocess sandbox is the complete fix — but the default path no longer executes the reported secret-read payloads. Adds regression tests for print.__self__ and for super().__init__ staying allowed. Refs #219
This commit is contained in:
@@ -103,8 +103,38 @@ def test_blocks_sandbox_escape_dunders():
|
||||
" ().__class__.__bases__[0].__subclasses__()\n"
|
||||
)
|
||||
violations = MathAnimate._scan_scene_code(code)
|
||||
assert "attribute access '.__bases__'" in violations
|
||||
assert "attribute access '.__subclasses__'" in violations
|
||||
assert "dunder attribute access '.__class__'" in violations
|
||||
assert "dunder attribute access '.__bases__'" in violations
|
||||
assert "dunder attribute access '.__subclasses__'" in violations
|
||||
|
||||
|
||||
def test_blocks_builtins_module_via_print_self():
|
||||
# Regression for the reported no-import bypass: print.__self__ is the
|
||||
# builtins module, reachable without an import, a bare open/__builtins__/
|
||||
# getattr, or a blocked name. Blocking all reflection dunders closes it.
|
||||
code = (
|
||||
"from manim import *\n"
|
||||
"class S(Scene):\n"
|
||||
" def construct(self):\n"
|
||||
" print.__self__.open('.env').read()\n"
|
||||
)
|
||||
assert "dunder attribute access '.__self__'" in MathAnimate._scan_scene_code(code)
|
||||
|
||||
|
||||
def test_super_init_is_allowed():
|
||||
# A legitimate custom Mobject with super().__init__() must not be blocked —
|
||||
# __init__ (and __name__) are the only permitted dunders.
|
||||
code = (
|
||||
"from manim import *\n"
|
||||
"class Widget(VGroup):\n"
|
||||
" def __init__(self, **kwargs):\n"
|
||||
" super().__init__(**kwargs)\n"
|
||||
" self.add(Circle())\n"
|
||||
"class S(Scene):\n"
|
||||
" def construct(self):\n"
|
||||
" self.add(Widget())\n"
|
||||
)
|
||||
assert MathAnimate._scan_scene_code(code) == []
|
||||
|
||||
|
||||
def test_syntax_error_defers_to_manim():
|
||||
|
||||
@@ -54,12 +54,21 @@ _BLOCKED_NAMES = frozenset({
|
||||
"__builtins__", "__loader__", "globals", "locals", "vars",
|
||||
"getattr", "setattr", "delattr",
|
||||
})
|
||||
# Sandbox-escape / reflection dunders blocked as attribute access.
|
||||
_BLOCKED_ATTRS = frozenset({
|
||||
"__globals__", "__builtins__", "__subclasses__", "__bases__", "__base__",
|
||||
"__mro__", "__code__", "__class__", "__dict__", "__getattribute__",
|
||||
"__closure__", "__reduce__", "__reduce_ex__", "__subclasshook__",
|
||||
})
|
||||
# Reflection via dunder attributes is the general escape hatch: `().__class__`,
|
||||
# `print.__self__` (the builtins module), `x.__globals__`, `f.__reduce__`, etc.
|
||||
# Enumerating dangerous dunders one by one is whack-a-mole, so block ALL dunder
|
||||
# *attribute access* and allow only a tiny set that legitimate scenes use
|
||||
# (`super().__init__(...)`, occasional `Type.__name__`). A dunder is any name
|
||||
# that starts and ends with double underscores.
|
||||
_ALLOWED_DUNDER_ATTRS = frozenset({"__init__", "__name__"})
|
||||
|
||||
|
||||
def _is_blocked_dunder(attr: str) -> bool:
|
||||
return (
|
||||
attr.startswith("__")
|
||||
and attr.endswith("__")
|
||||
and attr not in _ALLOWED_DUNDER_ATTRS
|
||||
)
|
||||
|
||||
|
||||
# Quality presets mapping to Manim CLI flags
|
||||
@@ -244,8 +253,8 @@ class MathAnimate(BaseTool):
|
||||
if node.id in _BLOCKED_NAMES:
|
||||
violations.append(f"use of '{node.id}'")
|
||||
elif isinstance(node, ast.Attribute):
|
||||
if node.attr in _BLOCKED_ATTRS:
|
||||
violations.append(f"attribute access '.{node.attr}'")
|
||||
if _is_blocked_dunder(node.attr):
|
||||
violations.append(f"dunder attribute access '.{node.attr}'")
|
||||
|
||||
seen: set[str] = set()
|
||||
deduped: list[str] = []
|
||||
|
||||
Reference in New Issue
Block a user