Skip to content

Commit 3f1aa65

Browse files
committed
added restricted python again
1 parent 711b27d commit 3f1aa65

3 files changed

Lines changed: 180 additions & 42 deletions

File tree

effectful/handlers/llm/evaluation.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
from types import CodeType
66
from typing import Any
77

8+
from RestrictedPython import (
9+
Eval,
10+
Guards,
11+
compile_restricted,
12+
safe_globals,
13+
)
14+
815
from effectful.ops.syntax import ObjectInterpretation, defop, implements
916

1017

@@ -86,3 +93,61 @@ def exec(
8693

8794
# Execute module-style so top-level defs land in `env`.
8895
builtins.exec(bytecode, env, env)
96+
97+
98+
class RestrictedEvalProvider(ObjectInterpretation):
99+
"""
100+
Safer provider using RestrictedPython.
101+
102+
RestrictedPython is not a complete sandbox, but it enforces a restricted
103+
language subset and expects you to provide a constrained exec environment.
104+
"""
105+
106+
config: dict[str, Any]
107+
108+
def __init__(self, **kwargs):
109+
self.config = kwargs
110+
111+
@implements(parse)
112+
def parse(self, source: str, filename: str) -> ast.Module:
113+
# Keep inspect.getsource() working for dynamically-defined objects.
114+
linecache.cache[filename] = (
115+
len(source),
116+
None,
117+
source.splitlines(True),
118+
filename,
119+
)
120+
return ast.parse(source, filename=filename, mode="exec")
121+
122+
@implements(compile)
123+
def compile(self, module: ast.Module, filename: str) -> CodeType:
124+
# RestrictedPython can compile from an AST directly.
125+
return compile_restricted(module, filename=filename, mode="exec", **self.config)
126+
127+
@implements(exec)
128+
def exec(
129+
self,
130+
bytecode: CodeType,
131+
env: dict[str, Any],
132+
) -> None:
133+
# Build restricted globals from RestrictedPython's defaults
134+
rglobals: dict[str, Any] = safe_globals.copy()
135+
136+
# Enable class definitions (required for Python 3)
137+
rglobals["__metaclass__"] = type
138+
rglobals["__name__"] = "restricted"
139+
140+
# Layer `env` on top (without letting callers replace the restricted builtins).
141+
rglobals.update({k: v for k, v in env.items() if k != "__builtins__"})
142+
143+
# Enable for loops and comprehensions
144+
rglobals["_getiter_"] = Eval.default_guarded_getiter
145+
146+
# Enable sequence unpacking in comprehensions and for loops
147+
rglobals["_iter_unpack_sequence_"] = Guards.guarded_iter_unpack_sequence
148+
rglobals["getattr"] = Guards.safer_getattr
149+
rglobals["setattr"] = Guards.guarded_setattr
150+
rglobals["_write_"] = lambda x: x
151+
152+
# Execute with locals=env so top-level defs land in `env`.
153+
builtins.exec(bytecode, rglobals, env)

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ classifiers = [
2828
"Programming Language :: Python :: 3.12",
2929
"Programming Language :: Python :: 3.13",
3030
]
31-
dependencies = []
31+
dependencies = [
32+
"restrictedpython>=8.1",
33+
]
3234

3335
[project.urls]
3436
Homepage = "https://www.basis.ai/"

0 commit comments

Comments
 (0)