moo.core.tests.test_security_builtins

Security tests: restricted builtin functions.

Covers: type/__metaclass__, dir, getattr/hasattr, setattr/delattr, callable, isinstance, dunder attribute syntax, safe_hasattr INSPECT_ATTRIBUTES gap, AttributeError.obj disclosure, and allowed builtin return values.

Functions

test_attribute_error_obj_is_none_for_guard_raised_errors()

AttributeErrors raised by our guards (e.g. safe_getattr raising AttributeError('__class__')) do not set e.obj — we construct the error manually without specifying the object.

test_attribute_error_obj_returns_object_verb_already_had()

Python 3.12 added AttributeError.obj — the object on which a C-level attribute lookup failed.

test_callable_builtin_accessible_but_harmless()

callable() is in safe_builtins.

test_delattr_builtin_blocks_delete_on_objects()

safe_builtins includes guarded_delattr from RestrictedPython.

test_dir_builtin_removed()

dir() is not in ALLOWED_BUILTINS and must raise NameError in verb code.

test_dunder_syntax_blocked()

Dunder attribute syntax (obj.__class__) is rejected at compile time by RestrictedPython and raises SyntaxError.

test_enumerate_iteration_safe()

enumerate() returns an enumerate object whose __next__ and __iter__ are underscore-prefixed and blocked.

test_enumerate_next_dunder_blocked()

enumerate.__next__ is underscore-prefixed and must raise AttributeError.

test_exception_traceback_blocked_by_underscore_guard()

Exception objects have __traceback__, __context__, and __cause__ attributes that could expose frame references.

test_generator_gi_code_blocked_via_getattr()

getattr(gen, 'gi_code') must raise AttributeError (INSPECT_ATTRIBUTES).

test_generator_gi_frame_blocked_via_getattr()

CVE-2023-37271 style: RestrictedPython 8.1 blocks gen.gi_frame at compile time (AST transform), but getattr(gen, 'gi_frame') bypasses the AST check and goes through our runtime safe_getattr.

test_getattr_normal_names_still_work()

getattr on a normal (non-underscore) name must still work.

test_getattr_underscore_blocked()

getattr(obj, '__class__') must raise AttributeError, not return the class.

test_hasattr_normal_names_still_work()

hasattr on a normal name must still work.

test_hasattr_underscore_returns_false()

hasattr(obj, '__class__') must return False, not True.

test_inspect_attributes_covers_attack_chain()

INSPECT_ATTRIBUTES must include all frame/generator attributes used in sandbox-escape chains.

test_isinstance_accessible_but_cannot_probe_dunder_classes()

isinstance() is in safe_builtins and is used by legitimate verb code.

test_iter_does_not_expose_generator_frame()

iter(gen) returns the generator itself; gi_frame stays sealed.

test_iter_dunder_next_still_blocked()

The iterator from iter() still hides __next__ behind the underscore guard.

test_iter_returns_iterator_over_safe_elements()

iter() returns an iterator over the supplied iterable.

test_iter_two_arg_callable_sentinel_bounded()

The two-arg iter(callable, sentinel) form calls a sandbox-defined callable until it returns the sentinel.

test_metaclass_not_in_globals()

__metaclass__ was a Python 2 artifact; it must not appear in the sandbox globals.

test_next_default_on_exhaustion()

next(it, default) returns the default on StopIteration — no leak, no escape.

test_next_does_not_expose_generator_frame()

Advancing a generator with next() yields the value, never the frame.

test_next_generator_frame_getattr_still_blocked()

getattr(gen, 'gi_frame') stays blocked even when gen has been advanced by next().

test_next_on_non_iterator_raises_typeerror()

next() on a non-iterator raises TypeError — it cannot be used to coerce an arbitrary object into yielding internals.

test_next_returns_safe_element()

next() returns the iterator's next yielded element.

test_safe_hasattr_does_not_leak_f_locals()

safe_hasattr returns False for 'f_locals' (in INSPECT_ATTRIBUTES).

test_safe_hasattr_does_not_leak_gi_code()

safe_hasattr returns False for 'gi_code' (in INSPECT_ATTRIBUTES).

test_safe_hasattr_does_not_leak_gi_frame()

safe_hasattr only blocked underscore-prefixed names before this fix.

test_set_operations_safe()

set() exposes add/remove/discard/union etc.

test_setattr_builtin_blocks_underscore_write()

guarded_setattr from RestrictedPython blocks ALL writes to objects that lack _guarded_writes, including underscore-prefixed names.

test_setattr_builtin_blocks_write_to_objects()

safe_builtins includes guarded_setattr from RestrictedPython.

test_sorted_returns_plain_list()

sorted() returns a plain list — no new attack surface over list literals.