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
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. |
|
Python 3.12 added AttributeError.obj — the object on which a C-level attribute lookup failed. |
|
callable() is in safe_builtins. |
|
safe_builtins includes guarded_delattr from RestrictedPython. |
|
dir() is not in ALLOWED_BUILTINS and must raise NameError in verb code. |
|
Dunder attribute syntax (obj.__class__) is rejected at compile time by RestrictedPython and raises SyntaxError. |
|
enumerate() returns an enumerate object whose __next__ and __iter__ are underscore-prefixed and blocked. |
|
enumerate.__next__ is underscore-prefixed and must raise AttributeError. |
|
Exception objects have __traceback__, __context__, and __cause__ attributes that could expose frame references. |
|
getattr(gen, 'gi_code') must raise AttributeError (INSPECT_ATTRIBUTES). |
|
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. |
|
getattr on a normal (non-underscore) name must still work. |
|
getattr(obj, '__class__') must raise AttributeError, not return the class. |
|
hasattr on a normal name must still work. |
|
hasattr(obj, '__class__') must return False, not True. |
|
INSPECT_ATTRIBUTES must include all frame/generator attributes used in sandbox-escape chains. |
|
|
isinstance() is in safe_builtins and is used by legitimate verb code. |
iter(gen) returns the generator itself; gi_frame stays sealed. |
|
The iterator from iter() still hides __next__ behind the underscore guard. |
|
iter() returns an iterator over the supplied iterable. |
|
The two-arg iter(callable, sentinel) form calls a sandbox-defined callable until it returns the sentinel. |
|
__metaclass__ was a Python 2 artifact; it must not appear in the sandbox globals. |
|
next(it, default) returns the default on StopIteration — no leak, no escape. |
|
Advancing a generator with next() yields the value, never the frame. |
|
getattr(gen, 'gi_frame') stays blocked even when gen has been advanced by next(). |
|
next() on a non-iterator raises TypeError — it cannot be used to coerce an arbitrary object into yielding internals. |
|
next() returns the iterator's next yielded element. |
|
safe_hasattr returns False for 'f_locals' (in INSPECT_ATTRIBUTES). |
|
safe_hasattr returns False for 'gi_code' (in INSPECT_ATTRIBUTES). |
|
safe_hasattr only blocked underscore-prefixed names before this fix. |
|
set() exposes add/remove/discard/union etc. |
|
guarded_setattr from RestrictedPython blocks ALL writes to objects that lack _guarded_writes, including underscore-prefixed names. |
|
safe_builtins includes guarded_setattr from RestrictedPython. |
|
sorted() returns a plain list — no new attack surface over list literals. |